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 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; } }