private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { var invocation = context.Node as InvocationExpressionSyntax; var info = context.SemanticModel.GetSymbolInfo(invocation); var method = info.Symbol as IMethodSymbol; if (method == null) { return; } // is serilog even present in the compilation? var messageTemplateAttribute = context.SemanticModel.Compilation.GetTypeByMetadataName("Serilog.Core.MessageTemplateFormatMethodAttribute"); if (messageTemplateAttribute == null) { return; } // is it a serilog logging method? var attributes = method.GetAttributes(); var attributeData = attributes.FirstOrDefault(x => x.AttributeClass == messageTemplateAttribute); if (attributeData == null) { return; } string messageTemplateName = attributeData.ConstructorArguments.First().Value as string; // check for errors in the MessageTemplate var arguments = new List <SourceArgument>(); var properties = new List <PropertyToken>(); var hasErrors = false; var literalSpan = default(TextSpan); var exactPositions = true; var stringText = default(string); foreach (var argument in invocation.ArgumentList.Arguments) { var paramter = RoslynHelper.DetermineParameter(argument, context.SemanticModel, true, context.CancellationToken); if (paramter.Name == messageTemplateName) { string messageTemplate; // is it a simple string literal? var literal = argument.Expression as LiteralExpressionSyntax; if (literal == null) { // can we at least get a computed constant value for it? var constantValue = context.SemanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constantValue.HasValue || !(constantValue.Value is string)) { context.ReportDiagnostic(Diagnostic.Create(ConstantMessageTemplateRule, argument.Expression.GetLocation(), argument.Expression.ToString())); continue; } // we can't map positions back from the computed string into the real positions exactPositions = false; messageTemplate = constantValue.Value as string; } else { stringText = literal.Token.Text; exactPositions = true; messageTemplate = literal.Token.ValueText; } literalSpan = argument.Expression.GetLocation().SourceSpan; var messageTemplateDiagnostics = AnalyzingMessageTemplateParser.Analyze(messageTemplate); foreach (var templateDiagnostic in messageTemplateDiagnostics) { var property = templateDiagnostic as PropertyToken; if (property != null) { properties.Add(property); continue; } var diagnostic = templateDiagnostic as MessageTemplateDiagnostic; if (diagnostic != null) { hasErrors = true; ReportDiagnostic(ref context, ref literalSpan, stringText, exactPositions, TemplateRule, diagnostic); } } } else if (paramter.Name.StartsWith("propertyValue", StringComparison.Ordinal)) { var location = argument.GetLocation().SourceSpan; arguments.Add(new SourceArgument { StartIndex = location.Start, Length = location.Length }); } } // do properties match up? if (!hasErrors && literalSpan != default(TextSpan) && (arguments.Count > 0 || properties.Count > 0)) { var diagnostics = PropertyBindingAnalyzer.AnalyzeProperties(properties, arguments); foreach (var diagnostic in diagnostics) { ReportDiagnostic(ref context, ref literalSpan, stringText, exactPositions, PropertyBindingRule, diagnostic); } // are there duplicate property names? var usedNames = new HashSet <string>(); foreach (var property in properties) { if (!property.IsPositional && !usedNames.Add(property.PropertyName)) { ReportDiagnostic(ref context, ref literalSpan, stringText, exactPositions, UniquePropertyNameRule, new MessageTemplateDiagnostic(property.StartIndex, property.Length, property.PropertyName)); } } } // is this an overload where the exception argument is used? var exception = context.SemanticModel.Compilation.GetTypeByMetadataName("System.Exception"); if (method.Parameters.First().Type == exception) { return; } // is there an overload with the exception argument? if (!method.ContainingType.GetMembers().OfType <IMethodSymbol>().Any(x => x.Name == method.Name && x.Parameters.FirstOrDefault()?.Type == exception)) { return; } // check wether any of the format arguments is an exception foreach (var argument in invocation.ArgumentList.Arguments) { var arginfo = context.SemanticModel.GetTypeInfo(argument.Expression); if (IsException(exception, arginfo.Type)) { context.ReportDiagnostic(Diagnostic.Create(ExceptionRule, argument.GetLocation(), argument.Expression.ToFullString())); } } }
private static void TryAddLoggingLevelSwitch(SemanticModel semanticModel, IdentifierNameSyntax identifier, LoggerConfiguration configuration, CancellationToken cancellationToken) { string name = "$" + identifier.Identifier.Value; if (configuration.LevelSwitches.ContainsKey(name)) { return; } var symbol = semanticModel.GetSymbolInfo(identifier, cancellationToken).Symbol; if (symbol == null) { configuration.AddError("Failed to analyze parameter", identifier); return; } var reference = symbol.DeclaringSyntaxReferences.FirstOrDefault() as SyntaxReference; if (reference == null) { configuration.AddError("Could not find declaration of LoggingLevelSwitch", identifier); return; } var declarator = reference.GetSyntax(cancellationToken) as VariableDeclaratorSyntax; if (declarator == null) { configuration.AddError("Could not find declaration of LoggingLevelSwitch", identifier); return; } var initializer = declarator.Initializer.Value as ObjectCreationExpressionSyntax; if (initializer == null) { configuration.AddError("Could not find initialization of LoggingLevelSwitch", identifier); return; } IParameterSymbol parameter; object value; var argument = initializer.ArgumentList.Arguments.FirstOrDefault(); if (argument == null) { var constructor = semanticModel.GetSymbolInfo(initializer, cancellationToken).Symbol; if (constructor == null) { configuration.AddError("Could not analyze LoggingLevelSwitch constructor", identifier); return; } parameter = (symbol as IMethodSymbol)?.Parameters.FirstOrDefault(); if (parameter == null) { configuration.AddError("Could not analyze LoggingLevelSwitch constructor", identifier); return; } value = parameter.ExplicitDefaultValue; } else { parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, cancellationToken); if (parameter == null) { configuration.AddError("Failed to analyze parameter", argument); return; } var constValue = semanticModel.GetConstantValue(argument.Expression, cancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument); return; } value = constValue.Value; } long enumIntegralValue; try { enumIntegralValue = Convert.ToInt64(value); } catch { configuration.AddError($"Value {value} is not within expected range", argument); return; } // Roslyn returns enum constant values as integers, convert it back to the enum member name var enumMember = parameter.Type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == enumIntegralValue); configuration.LevelSwitches.Add(name, enumMember.Name); }
private static LoggerConfiguration GetLoggerConfigurationFromSyntax(CodeRefactoringContext context, SemanticModel semanticModel, List <MemberAccessExpressionSyntax> configurationProperties) { var configuration = new LoggerConfiguration(); foreach (var property in configurationProperties) { var invokedMethod = property.Ancestors().FirstOrDefault() as MemberAccessExpressionSyntax; // Just in case we're looking at syntax that has similiar names to LoggerConfiguration (ReadFrom, WriteTo, ...) but isn't related to Serilog if (invokedMethod == null) { continue; } if (String.IsNullOrEmpty(invokedMethod?.Name?.ToString())) { configuration.AddError("Failed to get name of method", invokedMethod); continue; } string configAction = property.Name.ToString(); if (configAction == "MinimumLevel") { string value; var logLevel = invokedMethod.Name.ToString(); if (logLevel == "Is") { // Ask roslyn what's the constant argument value passed to this method var argument = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments.FirstOrDefault(); if (argument == null) { configuration.AddError("Can't get parameter value for MinimumLevel.Is(...)", invokedMethod); continue; } var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, context.CancellationToken); if (parameter == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } var accessExpression = argument?.Expression as MemberAccessExpressionSyntax; if (accessExpression == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } var constValue = semanticModel.GetConstantValue(accessExpression, context.CancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument); continue; } long enumIntegralValue; try { enumIntegralValue = Convert.ToInt64(constValue.Value); } catch { configuration.AddError($"Value {constValue.Value} is not within expected range", argument); continue; } // Roslyn returns enum constant values as integers, convert it back to the enum member name var enumMember = parameter.Type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == enumIntegralValue); value = enumMember.Name; } else if (logLevel == "Override") { var arguments = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments ?? default(SeparatedSyntaxList <ArgumentSyntax>); string key = null; string level = null; foreach (var argument in arguments) { var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, context.CancellationToken); if (parameter == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } if (parameter.Name == "source") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument.Expression); value = NotAConstantReplacementValue; continue; } key = constValue.Value?.ToString(); } else if (parameter.Name == "minimumLevel") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument.Expression); value = NotAConstantReplacementValue; continue; } var enumMember = parameter.Type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == Convert.ToInt64(constValue.Value)); level = enumMember.Name; } } if (key != null && level != null) { configuration.MinimumLevelOverrides[key] = level; } continue; } else if (logLevel == "ControlledBy") { var argument = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments.FirstOrDefault(); if (argument == null) { configuration.AddError("Can't get parameter value for MinimumLevel.ControlledBy(...)", invokedMethod); continue; } var identifier = argument?.Expression as IdentifierNameSyntax; if (identifier == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } TryAddLoggingLevelSwitch(semanticModel, identifier, configuration, context.CancellationToken); configuration.MinimumLevelControlledBy = "$" + identifier.Identifier.Value; continue; } else if (LogLevels.Contains(logLevel)) { value = logLevel; } else { configuration.AddError("Unknown MinimumLevel method", invokedMethod); continue; } configuration.MinimumLevel = value; } else if (configAction == "Enrich") { if (invokedMethod.Name.ToString() == "WithProperty") { var arguments = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments ?? default(SeparatedSyntaxList <ArgumentSyntax>); string key = null; string value = null; foreach (var argument in arguments) { var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, context.CancellationToken); if (parameter == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } if (parameter.Name == "name") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument.Expression); value = NotAConstantReplacementValue; continue; } key = constValue.Value?.ToString(); } else if (parameter.Name == "value") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument.Expression); value = NotAConstantReplacementValue; continue; } value = constValue.Value?.ToString(); } } if (key != null && value != null) { configuration.EnrichWithProperty[key] = value; } } else { AddExtensibleMethod(semanticModel, invokedMethod, configuration, configuration.Enrich.Add, context.CancellationToken); } } else if (configAction == "Destructure") { AddExtensibleMethod(semanticModel, invokedMethod, configuration, configuration.Destructure.Add, context.CancellationToken); } else if (configAction == "Filter") { AddExtensibleMethod(semanticModel, invokedMethod, configuration, configuration.Filter.Add, context.CancellationToken); } else if (configAction == "WriteTo") { AddExtensibleMethod(semanticModel, invokedMethod, configuration, configuration.WriteTo.Add, context.CancellationToken); } else if (configAction == "AuditTo") { AddExtensibleMethod(semanticModel, invokedMethod, configuration, configuration.AuditTo.Add, context.CancellationToken); } } return(configuration); }
private static void AddExtensibleMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax invokedMethod, LoggerConfiguration configuration, Action <ExtensibleMethod> addMethod, CancellationToken cancellationToken) { var methodSymbol = semanticModel.GetSymbolInfo(invokedMethod).Symbol as IMethodSymbol; var method = new ExtensibleMethod { AssemblyName = methodSymbol?.ContainingAssembly?.Name, MethodName = invokedMethod.Name.Identifier.ToString() }; if (String.IsNullOrEmpty(method.AssemblyName)) { configuration.AddError("Failed to get semantic informations for this method", invokedMethod); return; } // Check for explicitly given type arguments that are not part of the normal arguments if (methodSymbol.TypeArguments.Length > 0 && methodSymbol.TypeArguments.Length == methodSymbol.TypeParameters.Length) { for (int i = 0; i < methodSymbol.TypeArguments.Length; i++) { var typeParamter = methodSymbol.TypeParameters[i]; var typeArgument = methodSymbol.TypeArguments[i]; if (methodSymbol.Parameters.Any(x => x.Type == typeParamter)) { continue; } // Synthesize an System.Type argument if a generic version was used switch (typeParamter.Name) { case "TSink": // WriteTo/AuditTo.Sink<TSink>(...) method.Arguments["sink"] = GetAssemblyQualifiedTypeName(typeArgument); break; case "TEnricher": // Enrich.With<TEnricher>() method.Arguments["enricher"] = GetAssemblyQualifiedTypeName(typeArgument); break; case "TFilter": // Filter.With<TFilter>() method.Arguments["filter"] = GetAssemblyQualifiedTypeName(typeArgument); break; case "TDestructuringPolicy": // Destructure.With<TDestructuringPolicy>() method.Arguments["policy"] = GetAssemblyQualifiedTypeName(typeArgument); break; case "TScalar": // Destructure.AsScalar<TScalar>() method.Arguments["scalarType"] = GetAssemblyQualifiedTypeName(typeArgument); break; } } } var arguments = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments ?? default(SeparatedSyntaxList <ArgumentSyntax>); foreach (var argument in arguments) { var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, cancellationToken); if (parameter == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } string parameterName = parameter.Name; // Configuration Surrogates if (method.MethodName == "Sink" && parameterName == "logEventSink") // WriteTo/AuditTo.Sink(ILogEventSink logEventSink, ...) { parameterName = "sink"; // Sink(this LoggerSinkConfiguration loggerSinkConfiguration, ILogEventSink sink, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, LoggingLevelSwitch levelSwitch = null) } else if (method.MethodName == "With" && parameterName == "filters") // Filter.With(params ILogEventFilter[] filters) { parameterName = "filter"; // With(this LoggerFilterConfiguration loggerFilterConfiguration, ILogEventFilter filter) } else if (method.MethodName == "With" && parameterName == "destructuringPolicies") // Destructure.With(params IDestructuringPolicy[] destructuringPolicies) { parameterName = "policy"; // With(this LoggerDestructuringConfiguration loggerDestructuringConfiguration, IDestructuringPolicy policy) } else if (method.MethodName == "With" && parameterName == "enrichers") // Enrich.With(params ILogEventEnricher[] enrichers) { parameterName = "enricher"; // With(this LoggerEnrichmentConfiguration loggerEnrichmentConfiguration, ILogEventEnricher enricher) } ITypeSymbol type = parameter.Type; if (parameter.IsParams && type is IArrayTypeSymbol array) { type = array.ElementType; } if (type.ToString() == "System.Type") { method.Arguments[parameterName] = NotAConstantReplacementValue; var typeofExpression = argument.Expression as TypeOfExpressionSyntax; if (typeofExpression == null) { configuration.AddError("I need a typeof(T) expression for Type arguments", argument.Expression); continue; } var typeInfo = semanticModel.GetTypeInfo(typeofExpression.Type).Type as INamedTypeSymbol; if (typeInfo == null) { configuration.AddError("Failed to get semantic informations for typeof expression", typeofExpression); return; } // generate the assembly qualified name for usage with Type.GetType(string) string name = GetAssemblyQualifiedTypeName(typeInfo); method.Arguments[parameterName] = name; continue; } else if (type.TypeKind == TypeKind.Interface || type.TypeKind == TypeKind.Class && type.IsAbstract) { method.Arguments[parameterName] = NotAConstantReplacementValue; var expressionSymbol = semanticModel.GetSymbolInfo(argument.Expression).Symbol; if (expressionSymbol != null && (expressionSymbol.Kind == SymbolKind.Property || expressionSymbol.Kind == SymbolKind.Field)) { if (!expressionSymbol.IsStatic) { configuration.AddError("Only static fields and properties can be used", argument.Expression); continue; } if (expressionSymbol.DeclaredAccessibility != Accessibility.Public || expressionSymbol is IPropertySymbol property && property.GetMethod.DeclaredAccessibility != Accessibility.Public) { configuration.AddError("Fields and properties must be public and properties must have public getters", argument.Expression); continue; } method.Arguments[parameterName] = GetAssemblyQualifiedTypeName(expressionSymbol.ContainingType, "::" + expressionSymbol.Name); continue; } var objectCreation = argument.Expression as ObjectCreationExpressionSyntax; if (objectCreation == null) { configuration.AddError("I can only infer types from `new T()` expressions", argument.Expression); continue; } // check if there are explicit arguments which are unsupported if (objectCreation.ArgumentList?.Arguments.Count > 0) { configuration.AddError("The configuration supports only parameterless constructors for interface or abstract type parameters", argument.Expression); continue; } var typeInfo = semanticModel.GetTypeInfo(objectCreation).Type as INamedTypeSymbol; if (typeInfo == null) { configuration.AddError("Failed to get semantic informations for this constructor", objectCreation); return; } // generate the assembly qualified name for usage with Type.GetType(string) string name = GetAssemblyQualifiedTypeName(typeInfo); method.Arguments[parameterName] = name; continue; } else if (type.ToString() == "Serilog.Core.LoggingLevelSwitch") { var identifier = argument?.Expression as IdentifierNameSyntax; if (identifier == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } TryAddLoggingLevelSwitch(semanticModel, identifier, configuration, cancellationToken); method.Arguments[parameterName] = "$" + identifier.Identifier.Value; continue; } string value = null; var constValue = semanticModel.GetConstantValue(argument.Expression, cancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument.Expression); value = NotAConstantReplacementValue; } else { if (type.TypeKind == TypeKind.Enum) { var enumMember = type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == Convert.ToInt64(constValue.Value)); value = enumMember.Name; } else { value = constValue.Value?.ToString(); } } method.Arguments[parameterName] = value; } addMethod(method); }
private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context) { var invocation = context.Node as InvocationExpressionSyntax; var info = context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken); var method = info.Symbol as IMethodSymbol; if (method == null) { return; } // is serilog even present in the compilation? var messageTemplateAttribute = context.SemanticModel.Compilation.GetTypeByMetadataName("Serilog.Core.MessageTemplateFormatMethodAttribute"); if (messageTemplateAttribute == null) { return; } // check if ForContext<T> / ForContext(typeof(T)) calls use the containing type as T if (method.Name == ForContext && method.ReturnType.ToString() == ILogger) { CheckForContextCorrectness(ref context, invocation, method); } // is it a serilog logging method? var attributes = method.GetAttributes(); var attributeData = attributes.FirstOrDefault(x => x.AttributeClass == messageTemplateAttribute); if (attributeData == null) { return; } string messageTemplateName = attributeData.ConstructorArguments.First().Value as string; // check for errors in the MessageTemplate var arguments = default(List <SourceArgument>); var properties = new List <PropertyToken>(); var hasErrors = false; var literalSpan = default(TextSpan); var exactPositions = true; var stringText = default(string); var invocationArguments = invocation.ArgumentList.Arguments; foreach (var argument in invocationArguments) { var parameter = RoslynHelper.DetermineParameter(argument, context.SemanticModel, true, context.CancellationToken); if (parameter.Name == messageTemplateName) { string messageTemplate; // is it a simple string literal? if (argument.Expression is LiteralExpressionSyntax literal) { stringText = literal.Token.Text; exactPositions = true; messageTemplate = literal.Token.ValueText; } else { // can we at least get a computed constant value for it? var constantValue = context.SemanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constantValue.HasValue || !(constantValue.Value is string constString)) { INamedTypeSymbol StringType() => context.SemanticModel.Compilation.GetTypeByMetadataName("System.String"); if (context.SemanticModel.GetSymbolInfo(argument.Expression, context.CancellationToken).Symbol is IFieldSymbol field && field.Name == "Empty" && field.Type == StringType()) { constString = ""; } else { context.ReportDiagnostic(Diagnostic.Create(ConstantMessageTemplateRule, argument.Expression.GetLocation(), argument.Expression.ToString())); continue; } } // we can't map positions back from the computed string into the real positions exactPositions = false; messageTemplate = constString; } literalSpan = argument.Expression.GetLocation().SourceSpan; var messageTemplateDiagnostics = AnalyzingMessageTemplateParser.Analyze(messageTemplate); foreach (var templateDiagnostic in messageTemplateDiagnostics) { if (templateDiagnostic is PropertyToken property) { properties.Add(property); continue; } if (templateDiagnostic is MessageTemplateDiagnostic diagnostic) { hasErrors = true; ReportDiagnostic(ref context, ref literalSpan, stringText, exactPositions, TemplateRule, diagnostic); } } var messageTemplateArgumentIndex = invocationArguments.IndexOf(argument); arguments = invocationArguments.Skip(messageTemplateArgumentIndex + 1).Select(x => { var location = x.GetLocation().SourceSpan; return(new SourceArgument { Argument = x, StartIndex = location.Start, Length = location.Length }); }).ToList(); break; } }
private static LoggerConfiguration GetLoggerConfigurationFromSyntax(CodeRefactoringContext context, SemanticModel semanticModel, List <MemberAccessExpressionSyntax> configurationProperties) { var configuration = new LoggerConfiguration(); foreach (var property in configurationProperties) { var invokedMethod = property.Ancestors().FirstOrDefault() as MemberAccessExpressionSyntax; if (String.IsNullOrEmpty(invokedMethod?.Name?.ToString())) { continue; } string configAction = property.Name.ToString(); if (configAction == "MinimumLevel") { string value; var logLevel = invokedMethod.Name.ToString(); if (logLevel == "Is") { // Ask roslyn what's the constant argument value passed to this method var argument = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments.FirstOrDefault(); if (argument == null) { continue; } var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, context.CancellationToken); if (parameter == null) { continue; } var accessExpression = argument?.Expression as MemberAccessExpressionSyntax; if (accessExpression == null) { continue; } var constValue = semanticModel.GetConstantValue(accessExpression, context.CancellationToken); if (!constValue.HasValue) { continue; } long enumIntegralValue; try { enumIntegralValue = Convert.ToInt64(constValue.Value); } catch { continue; } // Roslyn returns enum constant values as integers, convert it back to the enum member name var enumMember = parameter.Type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == enumIntegralValue); value = enumMember.Name; } else if (logLevel == "Override") { var arguments = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments ?? default(SeparatedSyntaxList <ArgumentSyntax>); string key = null; string level = null; foreach (var argument in arguments) { var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, context.CancellationToken); if (parameter == null) { continue; } if (parameter.Name == "source") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { continue; } key = constValue.Value?.ToString(); } else if (parameter.Name == "minimumLevel") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { continue; } var enumMember = parameter.Type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == Convert.ToInt64(constValue.Value)); level = enumMember.Name; } } if (key != null && level != null) { configuration.MinimumLevelOverrides[key] = level; } continue; } else if (LogLevels.Contains(logLevel)) { value = logLevel; } else { continue; } configuration.MinimumLevel = value; } else if (configAction == "Enrich") { if (invokedMethod.Name.ToString() == "WithProperty") { var arguments = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments ?? default(SeparatedSyntaxList <ArgumentSyntax>); string key = null; string value = null; foreach (var argument in arguments) { var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, context.CancellationToken); if (parameter == null) { continue; } if (parameter.Name == "name") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { continue; } key = constValue.Value?.ToString(); } else if (parameter.Name == "value") { var constValue = semanticModel.GetConstantValue(argument.Expression, context.CancellationToken); if (!constValue.HasValue) { continue; } value = constValue.Value?.ToString(); } } if (key != null && value != null) { configuration.EnrichWithProperty[key] = value; } } else { var method = GetExtensibleMethod(semanticModel, invokedMethod, context.CancellationToken); if (method != null) { configuration.Enrich.Add(method); } } } else if (configAction == "WriteTo") { var method = GetExtensibleMethod(semanticModel, invokedMethod, context.CancellationToken); if (method != null) { configuration.WriteTo.Add(method); } } else if (configAction == "AuditTo") { var method = GetExtensibleMethod(semanticModel, invokedMethod, context.CancellationToken); if (method != null) { configuration.AuditTo.Add(method); } } } return(configuration); }
private static ExtensibleMethod GetExtensibleMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax invokedMethod, CancellationToken cancellationToken) { var method = new ExtensibleMethod { AssemblyName = semanticModel.GetSymbolInfo(invokedMethod).Symbol?.ContainingAssembly?.Name, MethodName = invokedMethod.Name.ToString() }; if (String.IsNullOrEmpty(method.AssemblyName)) { return(null); } var arguments = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments ?? default(SeparatedSyntaxList <ArgumentSyntax>); foreach (var argument in arguments) { var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, cancellationToken); if (parameter == null) { continue; } if (parameter.Type?.TypeKind == TypeKind.Interface) { var objectCreation = argument.Expression as ObjectCreationExpressionSyntax; // check if there are explicit arguments which are unsupported if (objectCreation.ArgumentList?.Arguments.Count > 0) { continue; } var typeInfo = semanticModel.GetTypeInfo(objectCreation).Type as INamedTypeSymbol; if (typeInfo == null) { continue; } // generate the assembly qualified name for usage with Type.GetType(string) string name = GetAssemblyQualifiedTypeName(typeInfo); method.Arguments[parameter.Name] = name; continue; } var constValue = semanticModel.GetConstantValue(argument.Expression, cancellationToken); if (!constValue.HasValue) { continue; } string value = null; if (parameter.Type.TypeKind == TypeKind.Enum) { var enumMember = parameter.Type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == Convert.ToInt64(constValue.Value)); value = enumMember.Name; } else { value = constValue.Value?.ToString(); } method.Arguments[parameter.Name] = value; } return(method); }
private static void AddExtensibleMethod(SemanticModel semanticModel, MemberAccessExpressionSyntax invokedMethod, LoggerConfiguration configuration, Action <ExtensibleMethod> addMethod, CancellationToken cancellationToken) { var method = new ExtensibleMethod { AssemblyName = semanticModel.GetSymbolInfo(invokedMethod).Symbol?.ContainingAssembly?.Name, MethodName = invokedMethod.Name.ToString() }; if (String.IsNullOrEmpty(method.AssemblyName)) { configuration.AddError("Failed to get semantic informations for this method", invokedMethod); return; } var arguments = (invokedMethod?.Parent as InvocationExpressionSyntax)?.ArgumentList?.Arguments ?? default(SeparatedSyntaxList <ArgumentSyntax>); foreach (var argument in arguments) { var parameter = RoslynHelper.DetermineParameter(argument, semanticModel, false, cancellationToken); if (parameter == null) { configuration.AddError("Failed to analyze parameter", argument); continue; } if (parameter.Type?.TypeKind == TypeKind.Interface) { method.Arguments[parameter.Name] = NotAConstantReplacementValue; var objectCreation = argument.Expression as ObjectCreationExpressionSyntax; if (objectCreation == null) { configuration.AddError("I can only infer types from `new T()` expressions", argument.Expression); continue; } // check if there are explicit arguments which are unsupported if (objectCreation.ArgumentList?.Arguments.Count > 0) { configuration.AddError("The configuration supports only parameterless constructors for interface parameters", argument.Expression); continue; } var typeInfo = semanticModel.GetTypeInfo(objectCreation).Type as INamedTypeSymbol; if (typeInfo == null) { configuration.AddError("Failed to get semantic informations for this constructor", objectCreation); return; } // generate the assembly qualified name for usage with Type.GetType(string) string name = GetAssemblyQualifiedTypeName(typeInfo); method.Arguments[parameter.Name] = name; continue; } string value = null; var constValue = semanticModel.GetConstantValue(argument.Expression, cancellationToken); if (!constValue.HasValue) { configuration.AddNonConstantError(argument.Expression); value = NotAConstantReplacementValue; } else { if (parameter.Type.TypeKind == TypeKind.Enum) { var enumMember = parameter.Type.GetMembers().OfType <IFieldSymbol>().FirstOrDefault(x => Convert.ToInt64(x.ConstantValue) == Convert.ToInt64(constValue.Value)); value = enumMember.Name; } else { value = constValue.Value?.ToString(); } } method.Arguments[parameter.Name] = value; } addMethod(method); }