/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Test/Core/Diagnostics/CommonDiagnosticAnalyzers.cs
3 248 строк
151 KB
Jason Malinowski
Fix issue where analyzers are missing when computing diagnostics OOP
09 авг 2025, 01:28
09 авг 2025, 01:28
3fcabbd
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis { public static class CommonDiagnosticAnalyzers { [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class AnalyzerForErrorLogTest : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Descriptor1 = new DiagnosticDescriptor( "ID1", "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Description1", helpLinkUri: "HelpLink1", customTags: new[] { "1_CustomTag1", "1_CustomTag2" }); public static readonly DiagnosticDescriptor Descriptor2 = new DiagnosticDescriptor( "ID2", "Title2", "Message2", "Category2", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "Description2", helpLinkUri: "HelpLink2", customTags: new[] { "2_CustomTag1", "2_CustomTag2" }); private static readonly ImmutableDictionary<string, string> s_properties = new Dictionary<string, string> { { "Key1", "Value1" }, { "Key2", "Value2" } }.ToImmutableDictionary(); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Descriptor1, Descriptor2); } } public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(context => { // With location diagnostic. context.RegisterSyntaxTreeAction(context => { var location = context.Tree.GetRoot().GetLocation(); context.ReportDiagnostic(Diagnostic.Create(Descriptor1, location, s_properties)); }); // No location diagnostic. context.RegisterCompilationEndAction(context => context.ReportDiagnostic(Diagnostic.Create(Descriptor2, Location.None, s_properties))); }); } private static string GetExpectedPropertiesMapText() { var expectedText = @" ""customProperties"": {"; foreach (var kvp in s_properties.OrderBy(kvp => kvp.Key)) { if (!expectedText.EndsWith("{")) { expectedText += ","; } expectedText += string.Format(@" ""{0}"": ""{1}""", kvp.Key, kvp.Value); } expectedText += @" }"; return expectedText; } public static string GetExpectedV1ErrorLogResultsAndRulesText(Compilation compilation, bool warnAsError = false) { var tree = compilation.SyntaxTrees.First(); var root = tree.GetRoot(); var expectedLineSpan = root.GetLocation().GetLineSpan(); var filePath = GetUriForPath(tree.FilePath); var effectiveSeverity1 = warnAsError || Descriptor1.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning"; var effectiveSeverity2 = warnAsError || Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning"; var warningLevelText = warnAsError ? string.Empty : @" ""warningLevel"": 1,"; return @" ""results"": [ { ""ruleId"": """ + Descriptor1.Id + @""", ""level"": """ + effectiveSeverity1 + @""", ""message"": """ + Descriptor1.MessageFormat + @""", ""locations"": [ { ""resultFile"": { ""uri"": """ + filePath + @""", ""region"": { ""startLine"": " + (expectedLineSpan.StartLinePosition.Line + 1) + @", ""startColumn"": " + (expectedLineSpan.StartLinePosition.Character + 1) + @", ""endLine"": " + (expectedLineSpan.EndLinePosition.Line + 1) + @", ""endColumn"": " + (expectedLineSpan.EndLinePosition.Character + 1) + @" } } } ], ""properties"": {" + warningLevelText + GetExpectedPropertiesMapText() + @" } }, { ""ruleId"": """ + Descriptor2.Id + @""", ""level"": """ + effectiveSeverity2 + @""", ""message"": """ + Descriptor2.MessageFormat + @""", ""properties"": {" + GetExpectedPropertiesMapText() + @" } } ], ""rules"": { """ + Descriptor1.Id + @""": { ""id"": """ + Descriptor1.Id + @""", ""shortDescription"": """ + Descriptor1.Title + @""", ""fullDescription"": """ + Descriptor1.Description + @""", ""defaultLevel"": """ + (Descriptor1.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""helpUri"": """ + Descriptor1.HelpLinkUri + @""", ""properties"": { ""category"": """ + Descriptor1.Category + @""", ""isEnabledByDefault"": " + (Descriptor2.IsEnabledByDefault ? "true" : "false") + @", ""tags"": [ " + String.Join("," + Environment.NewLine + " ", Descriptor1.CustomTags.Select(s => $"\"{s}\"")) + @" ] } }, """ + Descriptor2.Id + @""": { ""id"": """ + Descriptor2.Id + @""", ""shortDescription"": """ + Descriptor2.Title + @""", ""fullDescription"": """ + Descriptor2.Description + @""", ""defaultLevel"": """ + (Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""helpUri"": """ + Descriptor2.HelpLinkUri + @""", ""properties"": { ""category"": """ + Descriptor2.Category + @""", ""isEnabledByDefault"": " + (Descriptor2.IsEnabledByDefault ? "true" : "false") + @", ""tags"": [ " + String.Join("," + Environment.NewLine + " ", Descriptor2.CustomTags.Select(s => $"\"{s}\"")) + @" ] } } } } ] }"; } public static string GetExpectedV1ErrorLogWithSuppressionResultsAndRulesText(Compilation compilation) { var tree = compilation.SyntaxTrees.First(); var root = tree.GetRoot(); var expectedLineSpan = root.GetLocation().GetLineSpan(); var filePath = GetUriForPath(tree.FilePath); return @" ""results"": [ { ""ruleId"": """ + Descriptor1.Id + @""", ""level"": """ + (Descriptor1.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""message"": """ + Descriptor1.MessageFormat + @""", ""suppressionStates"": [ ""suppressedInSource"" ], ""locations"": [ { ""resultFile"": { ""uri"": """ + filePath + @""", ""region"": { ""startLine"": " + (expectedLineSpan.StartLinePosition.Line + 1) + @", ""startColumn"": " + (expectedLineSpan.StartLinePosition.Character + 1) + @", ""endLine"": " + (expectedLineSpan.EndLinePosition.Line + 1) + @", ""endColumn"": " + (expectedLineSpan.EndLinePosition.Character + 1) + @" } } } ], ""properties"": { ""warningLevel"": 1," + GetExpectedPropertiesMapText() + @" } }, { ""ruleId"": """ + Descriptor2.Id + @""", ""level"": """ + (Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""message"": """ + Descriptor2.MessageFormat + @""", ""properties"": {" + GetExpectedPropertiesMapText() + @" } } ], ""rules"": { """ + Descriptor1.Id + @""": { ""id"": """ + Descriptor1.Id + @""", ""shortDescription"": """ + Descriptor1.Title + @""", ""fullDescription"": """ + Descriptor1.Description + @""", ""defaultLevel"": """ + (Descriptor1.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""helpUri"": """ + Descriptor1.HelpLinkUri + @""", ""properties"": { ""category"": """ + Descriptor1.Category + @""", ""isEnabledByDefault"": " + (Descriptor2.IsEnabledByDefault ? "true" : "false") + @", ""tags"": [ " + String.Join("," + Environment.NewLine + " ", Descriptor1.CustomTags.Select(s => $"\"{s}\"")) + @" ] } }, """ + Descriptor2.Id + @""": { ""id"": """ + Descriptor2.Id + @""", ""shortDescription"": """ + Descriptor2.Title + @""", ""fullDescription"": """ + Descriptor2.Description + @""", ""defaultLevel"": """ + (Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""helpUri"": """ + Descriptor2.HelpLinkUri + @""", ""properties"": { ""category"": """ + Descriptor2.Category + @""", ""isEnabledByDefault"": " + (Descriptor2.IsEnabledByDefault ? "true" : "false") + @", ""tags"": [ " + String.Join("," + Environment.NewLine + " ", Descriptor2.CustomTags.Select(s => $"\"{s}\"")) + @" ] } } } } ] }"; } public static string GetExpectedV2ErrorLogResultsText(Compilation compilation, bool warnAsError = false) { var tree = compilation.SyntaxTrees.First(); var root = tree.GetRoot(); var expectedLineSpan = root.GetLocation().GetLineSpan(); var filePath = GetUriForPath(tree.FilePath); var effectiveSeverity1 = warnAsError || Descriptor1.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning"; var effectiveSeverity2 = warnAsError || Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning"; var warningLevelText = warnAsError ? string.Empty : @" ""warningLevel"": 1,"; return @" ""results"": [ { ""ruleId"": """ + Descriptor1.Id + @""", ""ruleIndex"": 0, ""level"": """ + effectiveSeverity1 + @""", ""message"": { ""text"": """ + Descriptor1.MessageFormat + @""" }, ""locations"": [ { ""physicalLocation"": { ""artifactLocation"": { ""uri"": """ + filePath + @""" }, ""region"": { ""startLine"": " + (expectedLineSpan.StartLinePosition.Line + 1) + @", ""startColumn"": " + (expectedLineSpan.StartLinePosition.Character + 1) + @", ""endLine"": " + (expectedLineSpan.EndLinePosition.Line + 1) + @", ""endColumn"": " + (expectedLineSpan.EndLinePosition.Character + 1) + @" } } } ], ""properties"": {" + warningLevelText + GetExpectedPropertiesMapText() + @" } }, { ""ruleId"": """ + Descriptor2.Id + @""", ""ruleIndex"": 1, ""level"": """ + effectiveSeverity2 + @""", ""message"": { ""text"": """ + Descriptor2.MessageFormat + @""" }, ""properties"": {" + GetExpectedPropertiesMapText() + @" } } ]"; } public static string GetExpectedV2ErrorLogWithSuppressionResultsText(Compilation compilation, string justification, string suppressionType) { var tree = compilation.SyntaxTrees.First(); var root = tree.GetRoot(); var expectedLineSpan = root.GetLocation().GetLineSpan(); var filePath = GetUriForPath(tree.FilePath); var expectedSuppressionPropertyMap = @", ""properties"": { ""suppressionType"": """ + suppressionType + @""" }"; return @" ""results"": [ { ""ruleId"": """ + Descriptor1.Id + @""", ""ruleIndex"": 0, ""level"": """ + (Descriptor1.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""message"": { ""text"": """ + Descriptor1.MessageFormat + @""" }, ""suppressions"": [ { ""kind"": ""inSource""" + (justification == null ? "" : @", ""justification"": """ + (justification) + @"""") + (suppressionType == null ? "" : expectedSuppressionPropertyMap) + @" } ], ""locations"": [ { ""physicalLocation"": { ""artifactLocation"": { ""uri"": """ + filePath + @""" }, ""region"": { ""startLine"": " + (expectedLineSpan.StartLinePosition.Line + 1) + @", ""startColumn"": " + (expectedLineSpan.StartLinePosition.Character + 1) + @", ""endLine"": " + (expectedLineSpan.EndLinePosition.Line + 1) + @", ""endColumn"": " + (expectedLineSpan.EndLinePosition.Character + 1) + @" } } } ], ""properties"": { ""warningLevel"": 1," + GetExpectedPropertiesMapText() + @" } }, { ""ruleId"": """ + Descriptor2.Id + @""", ""ruleIndex"": 1, ""level"": """ + (Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""", ""message"": { ""text"": """ + Descriptor2.MessageFormat + @""" }, ""properties"": {" + GetExpectedPropertiesMapText() + @" } } ]"; } public static string GetExpectedV2SuppressionTextForRulesSection(string[] suppressionKinds) { if (suppressionKinds?.Length > 0) { return @", ""isEverSuppressed"": ""true"", ""suppressionKinds"": [ " + string.Join("," + Environment.NewLine + " ", suppressionKinds.Select(s => $"\"{s}\"")) + @" ]"; } return string.Empty; } public static string GetExpectedV2ErrorLogInvocationsText( params (string DescriptorId, int DescriptorIndex, ImmutableHashSet<ReportDiagnostic> EffectiveSeverities)[] overriddenEffectiveSeveritiesWithIndex) { if (overriddenEffectiveSeveritiesWithIndex.Length == 0) { return string.Empty; } var first = true; var overridesContent = string.Empty; foreach (var (id, index, effectiveSeverities) in overriddenEffectiveSeveritiesWithIndex) { foreach (var effectiveSeverity in effectiveSeverities.OrderBy(Comparer<ReportDiagnostic>.Default)) { if (first) { first = false; } else { overridesContent += ","; } overridesContent += $@" {{ ""descriptor"": {{ ""id"": ""{id}"", ""index"": {index} }}, ""configuration"": {{ {GetConfigurationPropertyString(effectiveSeverity)} }} }}"; } } return $@"""invocations"": [ {{ ""executionSuccessful"": true, ""ruleConfigurationOverrides"": [{overridesContent} ] }} ]"; } private static string GetConfigurationPropertyString(ReportDiagnostic severity) { if (severity == ReportDiagnostic.Suppress) return @"""enabled"": false"; var severityString = severity switch { ReportDiagnostic.Error => "error", ReportDiagnostic.Warn => "warning", ReportDiagnostic.Info or ReportDiagnostic.Hidden => "note", _ => throw ExceptionUtilities.UnexpectedValue(severity) }; return $@"""level"": ""{severityString}"""; } internal static string GetExpectedV2ErrorLogRulesText( ImmutableArray<(DiagnosticDescriptor Descriptor, DiagnosticDescriptorErrorLoggerInfo Info)> descriptorsWithInfo, CultureInfo culture, string[] suppressionKinds1 = null, string[] suppressionKinds2 = null) { var descriptor1Info = descriptorsWithInfo.Single(d => d.Descriptor.Id == Descriptor1.Id).Info; var descriptor1ExecutionTime = ReportAnalyzerUtil.GetFormattedAnalyzerExecutionTime(descriptor1Info.ExecutionTime, culture).Trim(); var descriptor1ExecutionPercentage = ReportAnalyzerUtil.GetFormattedAnalyzerExecutionPercentage(descriptor1Info.ExecutionPercentage, culture).Trim(); var descriptor2Info = descriptorsWithInfo.Single(d => d.Descriptor.Id == Descriptor2.Id).Info; var descriptor2ExecutionTime = ReportAnalyzerUtil.GetFormattedAnalyzerExecutionTime(descriptor2Info.ExecutionTime, culture).Trim(); var descriptor2ExecutionPercentage = ReportAnalyzerUtil.GetFormattedAnalyzerExecutionPercentage(descriptor2Info.ExecutionPercentage, culture).Trim(); return @" ""rules"": [ { ""id"": """ + Descriptor1.Id + @""", ""shortDescription"": { ""text"": """ + Descriptor1.Title + @""" }, ""fullDescription"": { ""text"": """ + Descriptor1.Description + @""" }, ""helpUri"": """ + Descriptor1.HelpLinkUri + @""", ""properties"": { ""category"": """ + Descriptor1.Category + @"""" + GetExpectedV2SuppressionTextForRulesSection(suppressionKinds1) + @", ""executionTimeInSeconds"": """ + descriptor1ExecutionTime + @""", ""executionTimeInPercentage"": """ + descriptor1ExecutionPercentage + @""", ""tags"": [ " + string.Join("," + Environment.NewLine + " ", Descriptor1.CustomTags.Select(s => $"\"{s}\"")) + @" ] } }, { ""id"": """ + Descriptor2.Id + @""", ""shortDescription"": { ""text"": """ + Descriptor2.Title + @""" }, ""fullDescription"": { ""text"": """ + Descriptor2.Description + @""" }, ""defaultConfiguration"": { ""level"": """ + (Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""" }, ""helpUri"": """ + Descriptor2.HelpLinkUri + @""", ""properties"": { ""category"": """ + Descriptor2.Category + @"""" + GetExpectedV2SuppressionTextForRulesSection(suppressionKinds2) + @", ""executionTimeInSeconds"": """ + descriptor2ExecutionTime + @""", ""executionTimeInPercentage"": """ + descriptor2ExecutionPercentage + @""", ""tags"": [ " + String.Join("," + Environment.NewLine + " ", Descriptor2.CustomTags.Select(s => $"\"{s}\"")) + @" ] } } ]"; } public static string GetUriForPath(string path) { var uri = new Uri(path, UriKind.RelativeOrAbsolute); return uri.IsAbsoluteUri ? uri.AbsoluteUri : WebUtility.UrlEncode(uri.ToString()); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class SuppressorForErrorLogTest : DiagnosticSuppressor { public static readonly SuppressionDescriptor Descriptor1 = new("SPR0001", AnalyzerForErrorLogTest.Descriptor1.Id, "SuppressorJustification1"); public static readonly SuppressionDescriptor Descriptor2 = new("SPR0002", AnalyzerForErrorLogTest.Descriptor2.Id, "SuppressorJustification2"); public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Descriptor1, Descriptor2); public override void ReportSuppressions(SuppressionAnalysisContext context) { foreach (var diagnostic in context.ReportedDiagnostics) { var descriptor = diagnostic.Id == Descriptor1.SuppressedDiagnosticId ? Descriptor1 : Descriptor2; context.ReportSuppression(Suppression.Create(descriptor, diagnostic)); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class NotConfigurableDiagnosticAnalyzer : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor EnabledRule = new DiagnosticDescriptor( "ID1", "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, customTags: WellKnownDiagnosticTags.NotConfigurable); public static readonly DiagnosticDescriptor DisabledRule = new DiagnosticDescriptor( "ID2", "Title2", "Message2", "Category2", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: false, customTags: WellKnownDiagnosticTags.NotConfigurable); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(EnabledRule, DisabledRule); } } public override void Initialize(AnalysisContext context) { context.RegisterCompilationAction(compilationContext => { // Report enabled diagnostic. compilationContext.ReportDiagnostic(Diagnostic.Create(EnabledRule, Location.None)); // Try to report disabled diagnostic. compilationContext.ReportDiagnostic(Diagnostic.Create(DisabledRule, Location.None)); }); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class CodeBlockActionAnalyzer : DiagnosticAnalyzer { private readonly bool _onlyStatelessAction; public CodeBlockActionAnalyzer(bool onlyStatelessAction = false) { _onlyStatelessAction = onlyStatelessAction; } public static readonly DiagnosticDescriptor CodeBlockTopLevelRule = new DiagnosticDescriptor( "CodeBlockTopLevelRuleId", "CodeBlockTopLevelRuleTitle", "CodeBlock : {0}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor CodeBlockPerCompilationRule = new DiagnosticDescriptor( "CodeBlockPerCompilationRuleId", "CodeBlockPerCompilationRuleTitle", "CodeBlock : {0}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(CodeBlockTopLevelRule, CodeBlockPerCompilationRule); } } public override void Initialize(AnalysisContext context) { context.RegisterCodeBlockAction(codeBlockContext => { codeBlockContext.ReportDiagnostic(Diagnostic.Create(CodeBlockTopLevelRule, codeBlockContext.OwningSymbol.Locations[0], codeBlockContext.OwningSymbol.Name)); }); if (!_onlyStatelessAction) { context.RegisterCompilationStartAction(compilationStartContext => { compilationStartContext.RegisterCodeBlockAction(codeBlockContext => { codeBlockContext.ReportDiagnostic(Diagnostic.Create(CodeBlockPerCompilationRule, codeBlockContext.OwningSymbol.Locations[0], codeBlockContext.OwningSymbol.Name)); }); }); } } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CSharpCodeBlockObjectCreationAnalyzer : CodeBlockObjectCreationAnalyzer<SyntaxKind> { protected override SyntaxKind ObjectCreationExpressionKind => SyntaxKind.ObjectCreationExpression; } [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class VisualBasicCodeBlockObjectCreationAnalyzer : CodeBlockObjectCreationAnalyzer<VisualBasic.SyntaxKind> { protected override VisualBasic.SyntaxKind ObjectCreationExpressionKind => VisualBasic.SyntaxKind.ObjectCreationExpression; } public abstract class CodeBlockObjectCreationAnalyzer<TLanguageKindEnum> : DiagnosticAnalyzer where TLanguageKindEnum : struct { public static readonly DiagnosticDescriptor DiagnosticDescriptor = new DiagnosticDescriptor( "Id", "Title", "Message", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptor); protected abstract TLanguageKindEnum ObjectCreationExpressionKind { get; } public override void Initialize(AnalysisContext context) { context.RegisterCodeBlockStartAction<TLanguageKindEnum>(codeBlockStartContext => { codeBlockStartContext.RegisterSyntaxNodeAction(syntaxNodeContext => { syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptor, syntaxNodeContext.Node.GetLocation())); }, ObjectCreationExpressionKind); }); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CSharpGenericNameAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = nameof(DiagnosticId); public const string Title = nameof(Title); public const string Message = nameof(Message); public const string Category = nameof(Category); public const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, Severity, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.GenericName); } private void AnalyzeNode(SyntaxNodeAnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation())); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CSharpNamespaceDeclarationAnalyzer : AbstractNamespaceDeclarationAnalyzer<SyntaxKind> { protected override SyntaxKind NamespaceDeclarationSyntaxKind => SyntaxKind.NamespaceDeclaration; } [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class VisualBasicNamespaceDeclarationAnalyzer : AbstractNamespaceDeclarationAnalyzer<VisualBasic.SyntaxKind> { protected override VisualBasic.SyntaxKind NamespaceDeclarationSyntaxKind => VisualBasic.SyntaxKind.NamespaceStatement; } public abstract class AbstractNamespaceDeclarationAnalyzer<TLanguageKindEnum> : DiagnosticAnalyzer where TLanguageKindEnum : struct { public const string DiagnosticId = nameof(DiagnosticId); public const string Title = nameof(Title); public const string Message = nameof(Message); public const string Category = nameof(Category); public const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, Severity, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); protected abstract TLanguageKindEnum NamespaceDeclarationSyntaxKind { get; } public override void Initialize(AnalysisContext context) { context.RegisterSyntaxNodeAction(AnalyzeNode, NamespaceDeclarationSyntaxKind); } private void AnalyzeNode(SyntaxNodeAnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation())); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithNoActions : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor DummyRule = new DiagnosticDescriptor( "ID1", "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DummyRule); public override void Initialize(AnalysisContext context) { } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithDisabledRules : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( "ID1", "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: false); internal readonly ConcurrentSet<ISymbol> CallbackSymbols = new(); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(context => { CallbackSymbols.Add(context.Symbol); context.ReportDiagnostic(Diagnostic.Create(Rule, context.Symbol.Locations[0])); }, SymbolKind.NamedType); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class EnsureNoMergedNamespaceSymbolAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = nameof(DiagnosticId); public const string Title = nameof(Title); public const string Message = nameof(Message); public const string Category = nameof(Category); public const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, Severity, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Namespace); } private void AnalyzeSymbol(SymbolAnalysisContext context) { // Ensure we are not invoked for merged namespace symbol, but instead for constituent namespace scoped to the source assembly. var ns = (INamespaceSymbol)context.Symbol; if (ns.ContainingAssembly != context.Compilation.Assembly || ns.ConstituentNamespaces.Length > 1) { context.ReportDiagnostic(Diagnostic.Create(Rule, ns.Locations[0])); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithNoSupportedDiagnostics : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } public override void Initialize(AnalysisContext context) { } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithInvalidDiagnosticId : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "Invalid ID", "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationAction(compilationContext => compilationContext.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.None))); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithNullDescriptor : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create((DiagnosticDescriptor)null); public override void Initialize(AnalysisContext context) { context.RegisterCompilationAction(_ => { }); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithCSharpCompilerDiagnosticId : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( #pragma warning disable RS1029 // Do not use reserved diagnostic IDs. "CS101", #pragma warning restore RS1029 // Do not use reserved diagnostic IDs. "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationAction(compilationContext => compilationContext.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.None))); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithBasicCompilerDiagnosticId : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( #pragma warning disable RS1029 // Do not use reserved diagnostic IDs. "BC101", #pragma warning restore RS1029 // Do not use reserved diagnostic IDs. "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationAction(compilationContext => compilationContext.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.None))); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithInvalidDiagnosticSpan : DiagnosticAnalyzer { private readonly TextSpan _badSpan; public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "ID", "Title1", "Message", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public AnalyzerWithInvalidDiagnosticSpan(TextSpan badSpan) => _badSpan = badSpan; public Exception ThrownException { get; set; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterSyntaxTreeAction(c => { try { ThrownException = null; c.ReportDiagnostic(Diagnostic.Create(Descriptor, SourceLocation.Create(c.Tree, _badSpan))); } catch (Exception e) { ThrownException = e; throw; } }); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithInvalidDiagnosticLocation : DiagnosticAnalyzer { private readonly Location _invalidLocation; private readonly ActionKind _actionKind; private readonly bool _testInvalidAdditionalLocation; public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "ID", "Title1", "Message {0}", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public enum ActionKind { Symbol, CodeBlock, Operation, OperationBlockEnd, Compilation, CompilationEnd, SyntaxTree } public AnalyzerWithInvalidDiagnosticLocation(SyntaxTree treeInAnotherCompilation, ActionKind actionKind, bool testInvalidAdditionalLocation) { _invalidLocation = treeInAnotherCompilation.GetRoot().GetLocation(); _actionKind = actionKind; _testInvalidAdditionalLocation = testInvalidAdditionalLocation; } private void ReportDiagnostic(Action<Diagnostic> addDiagnostic, ActionKind actionKindBeingRun) { if (_actionKind == actionKindBeingRun) { var diagnostic = _testInvalidAdditionalLocation ? Diagnostic.Create(Descriptor, Location.None, additionalLocations: new[] { _invalidLocation }) : Diagnostic.Create(Descriptor, _invalidLocation); addDiagnostic(diagnostic); } } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(cc => { cc.RegisterSymbolAction(c => ReportDiagnostic(c.ReportDiagnostic, ActionKind.Symbol), SymbolKind.NamedType); cc.RegisterCodeBlockAction(c => ReportDiagnostic(c.ReportDiagnostic, ActionKind.CodeBlock)); cc.RegisterCompilationEndAction(c => ReportDiagnostic(c.ReportDiagnostic, ActionKind.CompilationEnd)); cc.RegisterOperationBlockStartAction(oc => { oc.RegisterOperationAction(c => ReportDiagnostic(c.ReportDiagnostic, ActionKind.Operation), OperationKind.VariableDeclarationGroup); oc.RegisterOperationBlockEndAction(c => ReportDiagnostic(c.ReportDiagnostic, ActionKind.OperationBlockEnd)); }); }); context.RegisterSyntaxTreeAction(c => ReportDiagnostic(c.ReportDiagnostic, ActionKind.SyntaxTree)); context.RegisterCompilationAction(cc => ReportDiagnostic(cc.ReportDiagnostic, ActionKind.Compilation)); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerThatThrowsInSupportedDiagnostics : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => throw new NotImplementedException(); public override void Initialize(AnalysisContext context) { } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerThatThrowsInGetMessage : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( "ID1", "Title1", new MyLocalizableStringThatThrows(), "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(symbolContext => { symbolContext.ReportDiagnostic(Diagnostic.Create(Rule, symbolContext.Symbol.Locations[0])); }, SymbolKind.NamedType); } private sealed class MyLocalizableStringThatThrows : LocalizableString { protected override bool AreEqual(object other) { return ReferenceEquals(this, other); } protected override int GetHash() { return 0; } protected override string GetText(IFormatProvider formatProvider) { throw new NotImplementedException(); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerReportingMisformattedDiagnostic : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( "ID1", "Title1", "Symbol Name: {0}, Extra argument: {1}", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(symbolContext => { // Report diagnostic with incorrect number of message format arguments. symbolContext.ReportDiagnostic(Diagnostic.Create(Rule, symbolContext.Symbol.Locations[0], symbolContext.Symbol.Name)); }, SymbolKind.NamedType); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class CompilationAnalyzerWithSeverity : DiagnosticAnalyzer { public const string DiagnosticId = "ID1000"; public CompilationAnalyzerWithSeverity( DiagnosticSeverity severity, bool configurable) { var customTags = !configurable ? new[] { WellKnownDiagnosticTags.NotConfigurable } : Array.Empty<string>(); Descriptor = new DiagnosticDescriptor( DiagnosticId, "Description1", string.Empty, "Analysis", severity, true, customTags: customTags); } public DiagnosticDescriptor Descriptor { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationAction(this.OnCompilation); } private void OnCompilation(CompilationAnalysisContext context) { // Report the diagnostic on all trees in compilation. foreach (var tree in context.Compilation.SyntaxTrees) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, tree.GetRoot().GetLocation())); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class SemanticModelAnalyzerWithId : DiagnosticAnalyzer { public SemanticModelAnalyzerWithId(string diagnosticId) { Descriptor = new DiagnosticDescriptor( diagnosticId, "Description1", string.Empty, "Analysis", DiagnosticSeverity.Warning, isEnabledByDefault: true); } public DiagnosticDescriptor Descriptor { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) => context.RegisterSemanticModelAction(context => context.ReportDiagnostic( Diagnostic.Create(Descriptor, context.SemanticModel.SyntaxTree.GetRoot().GetLocation()))); } /// <summary> /// This analyzer is intended to be used only when concurrent execution is enabled for analyzers. /// This analyzer will deadlock if the driver runs analyzers on a single thread OR takes a lock around callbacks into this analyzer to prevent concurrent analyzer execution /// Former indicates a bug in the test using this analyzer and the latter indicates a bug in the analyzer driver. /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class ConcurrentAnalyzer : DiagnosticAnalyzer { private readonly ImmutableHashSet<string> _symbolNames; private int _token; public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "ConcurrentAnalyzerId", "Title", "ConcurrentAnalyzerMessage for symbol '{0}'", "Category", DiagnosticSeverity.Warning, true); public ConcurrentAnalyzer(IEnumerable<string> symbolNames) { Assert.True(Environment.ProcessorCount > 1, "This analyzer is intended to be used only in a concurrent environment."); _symbolNames = symbolNames.ToImmutableHashSet(); _token = 0; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(this.OnCompilationStart); // Enable concurrent action callbacks on analyzer. context.EnableConcurrentExecution(); } private void OnCompilationStart(CompilationStartAnalysisContext context) { Assert.True(context.Compilation.Options.ConcurrentBuild, "This analyzer is intended to be used only when concurrent build is enabled."); var pendingSymbols = new ConcurrentSet<INamedTypeSymbol>(); foreach (var type in context.Compilation.GlobalNamespace.GetTypeMembers()) { if (_symbolNames.Contains(type.Name)) { pendingSymbols.Add(type); } } context.RegisterSymbolAction(symbolContext => { if (!pendingSymbols.Remove((INamedTypeSymbol)symbolContext.Symbol)) { return; } var myToken = Interlocked.Increment(ref _token); if (myToken == 1) { // Wait for all symbol callbacks to execute. // This analyzer will deadlock if the driver doesn't attempt concurrent callbacks. while (pendingSymbols.Any()) { Thread.Sleep(10); } } // ok, now report diagnostic on the symbol. var diagnostic = Diagnostic.Create(Descriptor, symbolContext.Symbol.Locations[0], symbolContext.Symbol.Name); symbolContext.ReportDiagnostic(diagnostic); }, SymbolKind.NamedType); } } /// <summary> /// This analyzer will report diagnostics only if it receives any concurrent action callbacks, which would be a /// bug in the analyzer driver as this analyzer doesn't invoke <see cref="AnalysisContext.EnableConcurrentExecution"/>. /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class NonConcurrentAnalyzer : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "NonConcurrentAnalyzerId", "Title", "Analyzer driver made concurrent action callbacks, when analyzer didn't register for concurrent execution", "Category", DiagnosticSeverity.Warning, true); private const int registeredActionCounts = 1000; public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { SemaphoreSlim gate = new SemaphoreSlim(initialCount: registeredActionCounts); for (var i = 0; i < registeredActionCounts; i++) { context.RegisterSymbolAction(symbolContext => { using (gate.DisposableWait(symbolContext.CancellationToken)) { ReportDiagnosticIfActionInvokedConcurrently(gate, symbolContext); } }, SymbolKind.NamedType); } } private void ReportDiagnosticIfActionInvokedConcurrently(SemaphoreSlim gate, SymbolAnalysisContext symbolContext) { if (gate.CurrentCount != registeredActionCounts - 1) { var diagnostic = Diagnostic.Create(Descriptor, symbolContext.Symbol.Locations[0]); symbolContext.ReportDiagnostic(diagnostic); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class OperationAnalyzer : DiagnosticAnalyzer { private readonly ActionKind _actionKind; private readonly bool _verifyGetControlFlowGraph; private readonly ConcurrentDictionary<IOperation, (ControlFlowGraph Graph, ISymbol AssociatedSymbol)> _controlFlowGraphMapOpt; public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "ID", "Title1", "{0} diagnostic", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public enum ActionKind { Operation, OperationInOperationBlockStart, OperationBlock, OperationBlockEnd } public OperationAnalyzer(ActionKind actionKind, bool verifyGetControlFlowGraph = false) { _actionKind = actionKind; _verifyGetControlFlowGraph = verifyGetControlFlowGraph; _controlFlowGraphMapOpt = verifyGetControlFlowGraph ? new ConcurrentDictionary<IOperation, (ControlFlowGraph, ISymbol)>() : null; } public ImmutableArray<(ControlFlowGraph Graph, ISymbol AssociatedSymbol)> GetControlFlowGraphs() { Assert.True(_verifyGetControlFlowGraph); return _controlFlowGraphMapOpt.Values.OrderBy(flowGraphAndSymbol => flowGraphAndSymbol.Graph.OriginalOperation.Syntax.SpanStart).ToImmutableArray(); } private void ReportDiagnostic(Action<Diagnostic> addDiagnostic, Location location) { var diagnostic = Diagnostic.Create(Descriptor, location, _actionKind); addDiagnostic(diagnostic); } private void CacheAndVerifyControlFlowGraph(ImmutableArray<IOperation> operationBlocks, Func<IOperation, (ControlFlowGraph Graph, ISymbol AssociatedSymbol)> getControlFlowGraph) { if (_verifyGetControlFlowGraph) { foreach (var operationBlock in operationBlocks) { var controlFlowGraphAndSymbol = getControlFlowGraph(operationBlock); Assert.NotNull(controlFlowGraphAndSymbol); Assert.Same(operationBlock.GetRootOperation(), controlFlowGraphAndSymbol.Graph.OriginalOperation); _controlFlowGraphMapOpt.Add(controlFlowGraphAndSymbol.Graph.OriginalOperation, controlFlowGraphAndSymbol); // Verify analyzer driver caches the flow graph. Assert.Same(controlFlowGraphAndSymbol.Graph, getControlFlowGraph(operationBlock).Graph); // Verify exceptions for invalid inputs. try { _ = getControlFlowGraph(null); } catch (ArgumentNullException ex) { Assert.Equal(new ArgumentNullException("operationBlock").Message, ex.Message); } try { _ = getControlFlowGraph(operationBlock.Descendants().First()); } catch (ArgumentException ex) { Assert.Equal(new ArgumentException(CodeAnalysisResources.InvalidOperationBlockForAnalysisContext, "operationBlock").Message, ex.Message); } } } } private void VerifyControlFlowGraph(OperationAnalysisContext operationContext, bool inBlockAnalysisContext) { if (_verifyGetControlFlowGraph) { var controlFlowGraph = operationContext.GetControlFlowGraph(); Assert.NotNull(controlFlowGraph); // Verify analyzer driver caches the flow graph. Assert.Same(controlFlowGraph, operationContext.GetControlFlowGraph()); var rootOperation = operationContext.Operation.GetRootOperation(); if (inBlockAnalysisContext) { // Verify same flow graph returned from containing block analysis context. Assert.Same(controlFlowGraph, _controlFlowGraphMapOpt[rootOperation].Graph); } else { _controlFlowGraphMapOpt[rootOperation] = (controlFlowGraph, operationContext.ContainingSymbol); } } } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { switch (_actionKind) { case ActionKind.OperationBlockEnd: context.RegisterOperationBlockStartAction(blockStartContext => { blockStartContext.RegisterOperationBlockEndAction(c => ReportDiagnostic(c.ReportDiagnostic, c.OwningSymbol.Locations[0])); CacheAndVerifyControlFlowGraph(blockStartContext.OperationBlocks, op => (blockStartContext.GetControlFlowGraph(op), blockStartContext.OwningSymbol)); }); break; case ActionKind.OperationBlock: context.RegisterOperationBlockAction(blockContext => { ReportDiagnostic(blockContext.ReportDiagnostic, blockContext.OwningSymbol.Locations[0]); CacheAndVerifyControlFlowGraph(blockContext.OperationBlocks, op => (blockContext.GetControlFlowGraph(op), blockContext.OwningSymbol)); }); break; case ActionKind.Operation: context.RegisterOperationAction(operationContext => { ReportDiagnostic(operationContext.ReportDiagnostic, operationContext.Operation.Syntax.GetLocation()); VerifyControlFlowGraph(operationContext, inBlockAnalysisContext: false); }, OperationKind.Literal); break; case ActionKind.OperationInOperationBlockStart: context.RegisterOperationBlockStartAction(blockContext => { CacheAndVerifyControlFlowGraph(blockContext.OperationBlocks, op => (blockContext.GetControlFlowGraph(op), blockContext.OwningSymbol)); blockContext.RegisterOperationAction(operationContext => { ReportDiagnostic(operationContext.ReportDiagnostic, operationContext.Operation.Syntax.GetLocation()); VerifyControlFlowGraph(operationContext, inBlockAnalysisContext: true); }, OperationKind.Literal); }); break; } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class OperationBlockAnalyzer : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "ID", "Title1", "OperationBlock for {0}: {1}", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterOperationBlockAction(c => { foreach (var operationRoot in c.OperationBlocks) { var diagnostic = Diagnostic.Create(Descriptor, c.OwningSymbol.Locations[0], c.OwningSymbol.Name, operationRoot.Kind); c.ReportDiagnostic(diagnostic); } }); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class FieldReferenceOperationAnalyzer : DiagnosticAnalyzer { private readonly bool _doOperationBlockAnalysis; public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "ID", "Title", "Field {0} = {1}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public FieldReferenceOperationAnalyzer(bool doOperationBlockAnalysis) { _doOperationBlockAnalysis = doOperationBlockAnalysis; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { if (_doOperationBlockAnalysis) { context.RegisterOperationBlockAction(operationBlockAnalysisContext => { foreach (var operationBlock in operationBlockAnalysisContext.OperationBlocks) { foreach (var operation in operationBlock.DescendantsAndSelf().OfType<IFieldReferenceOperation>()) { AnalyzerFieldReferenceOperation(operation, operationBlockAnalysisContext.ReportDiagnostic); } } }); } else { context.RegisterOperationAction(AnalyzerOperation, OperationKind.FieldReference); } } private static void AnalyzerOperation(OperationAnalysisContext operationAnalysisContext) { AnalyzerFieldReferenceOperation((IFieldReferenceOperation)operationAnalysisContext.Operation, operationAnalysisContext.ReportDiagnostic); } private static void AnalyzerFieldReferenceOperation(IFieldReferenceOperation operation, Action<Diagnostic> reportDiagnostic) { var diagnostic = Diagnostic.Create(Descriptor, operation.Syntax.GetLocation(), operation.Field.Name, operation.Field.ConstantValue); reportDiagnostic(diagnostic); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class MethodOrConstructorBodyOperationAnalyzer : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( "ID", "Title", "Method {0}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterOperationAction(operationContext => { var diagnostic = Diagnostic.Create(Descriptor, operationContext.Operation.Syntax.GetLocation(), operationContext.ContainingSymbol.Name); operationContext.ReportDiagnostic(diagnostic); }, OperationKind.MethodBody, OperationKind.ConstructorBody); } } public abstract class AbstractGeneratedCodeAnalyzer<TSyntaxKind> : DiagnosticAnalyzer where TSyntaxKind : struct { private readonly GeneratedCodeAnalysisFlags? _generatedCodeAnalysisFlagsOpt; private readonly bool _testIsGeneratedCodeInCallbacks; public static readonly DiagnosticDescriptor Warning = new DiagnosticDescriptor( "GeneratedCodeAnalyzerWarning", "Title", "GeneratedCodeAnalyzerMessage for '{0}'", "Category", DiagnosticSeverity.Warning, true); public static readonly DiagnosticDescriptor Error = new DiagnosticDescriptor( "GeneratedCodeAnalyzerError", "Title", "GeneratedCodeAnalyzerMessage for '{0}'", "Category", DiagnosticSeverity.Error, true); public static readonly DiagnosticDescriptor Summary = new DiagnosticDescriptor( "GeneratedCodeAnalyzerSummary", "Title2", "GeneratedCodeAnalyzer received callbacks for: '{0}' types and '{1}' files", "Category", DiagnosticSeverity.Warning, true); public static readonly DiagnosticDescriptor Summary2 = new DiagnosticDescriptor( "GeneratedCodeAnalyzerSummary", "Title2", "GeneratedCodeAnalyzer received callbacks for: '{0}' types and '{1}' files and '{2}' additional IsGeneratedCode callbacks", "Category", DiagnosticSeverity.Warning, true); protected AbstractGeneratedCodeAnalyzer(GeneratedCodeAnalysisFlags? generatedCodeAnalysisFlagsOpt, bool testIsGeneratedCodeInCallbacks) { _generatedCodeAnalysisFlagsOpt = generatedCodeAnalysisFlagsOpt; _testIsGeneratedCodeInCallbacks = testIsGeneratedCodeInCallbacks; } protected abstract TSyntaxKind ClassDeclarationSyntaxKind { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Warning, Error, Summary, Summary2); public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(this.OnCompilationStart); if (_generatedCodeAnalysisFlagsOpt.HasValue) { // Configure analysis on generated code. context.ConfigureGeneratedCodeAnalysis(_generatedCodeAnalysisFlagsOpt.Value); } } private void OnCompilationStart(CompilationStartAnalysisContext context) { var sortedCallbackSymbolNames = new SortedSet<string>(); var sortedCallbackTreePaths = new SortedSet<string>(); context.RegisterSymbolAction(symbolContext => { sortedCallbackSymbolNames.Add($"{symbolContext.Symbol.Name}(IsGeneratedCode:{symbolContext.IsGeneratedCode})"); ReportSymbolDiagnostics(symbolContext.Symbol, symbolContext.ReportDiagnostic); }, SymbolKind.NamedType); context.RegisterSyntaxTreeAction(treeContext => { sortedCallbackTreePaths.Add($"{treeContext.Tree.FilePath}(IsGeneratedCode:{treeContext.IsGeneratedCode})"); ReportTreeDiagnostics(treeContext.Tree, treeContext.ReportDiagnostic); }); var sortedCallbackSyntaxNodeNames = new SortedSet<string>(); var sortedCallbackOperationNames = new SortedSet<string>(); var sortedCallbackSemanticModelPaths = new SortedSet<string>(); var sortedCallbackSymbolStartNames = new SortedSet<string>(); var sortedCallbackSymbolEndNames = new SortedSet<string>(); var sortedCallbackOperationBlockStartNames = new SortedSet<string>(); var sortedCallbackOperationBlockEndNames = new SortedSet<string>(); var sortedCallbackOperationBlockNames = new SortedSet<string>(); var sortedCallbackCodeBlockStartNames = new SortedSet<string>(); var sortedCallbackCodeBlockEndNames = new SortedSet<string>(); var sortedCallbackCodeBlockNames = new SortedSet<string>(); if (_testIsGeneratedCodeInCallbacks) { // Test all remaining analysis contexts that expose "IsGeneratedCode" flag context.RegisterSyntaxNodeAction(context => sortedCallbackSyntaxNodeNames.Add($"{context.ContainingSymbol.Name}(IsGeneratedCode:{context.IsGeneratedCode})"), ImmutableArray.Create(ClassDeclarationSyntaxKind)); context.RegisterSemanticModelAction(context => sortedCallbackSemanticModelPaths.Add($"{context.SemanticModel.SyntaxTree.FilePath}(IsGeneratedCode:{context.IsGeneratedCode})")); context.RegisterSymbolStartAction(context => { sortedCallbackSymbolStartNames.Add($"{context.Symbol.Name}(IsGeneratedCode:{context.IsGeneratedCode})"); context.RegisterOperationBlockStartAction(context => { if (context.OwningSymbol.Kind != SymbolKind.Method || context.OperationBlocks.IsEmpty) { return; } sortedCallbackOperationBlockStartNames.Add($"{context.OwningSymbol.ContainingType.Name}(IsGeneratedCode:{context.IsGeneratedCode})"); context.RegisterOperationAction(context => sortedCallbackOperationNames.Add($"{context.ContainingSymbol.ContainingType.Name}(IsGeneratedCode:{context.IsGeneratedCode})"), OperationKind.Invocation); context.RegisterOperationBlockEndAction(context => sortedCallbackOperationBlockEndNames.Add($"{context.OwningSymbol.ContainingType.Name}(IsGeneratedCode:{context.IsGeneratedCode})")); }); context.RegisterOperationBlockAction(context => { if (context.OwningSymbol.Kind != SymbolKind.Method || context.OperationBlocks.IsEmpty) { return; } sortedCallbackOperationBlockNames.Add($"{context.OwningSymbol.ContainingType.Name}(IsGeneratedCode:{context.IsGeneratedCode})"); }); context.RegisterSymbolEndAction(context => sortedCallbackSymbolEndNames.Add($"{context.Symbol.Name}(IsGeneratedCode:{context.IsGeneratedCode})")); }, SymbolKind.NamedType); context.RegisterCodeBlockStartAction<TSyntaxKind>(context => { if (context.OwningSymbol.Kind != SymbolKind.Method) { return; } sortedCallbackCodeBlockStartNames.Add($"{context.OwningSymbol.ContainingType.Name}(IsGeneratedCode:{context.IsGeneratedCode})"); context.RegisterCodeBlockEndAction(context => sortedCallbackCodeBlockEndNames.Add($"{context.OwningSymbol.ContainingType.Name}(IsGeneratedCode:{context.IsGeneratedCode})")); }); context.RegisterCodeBlockAction(context => { if (context.OwningSymbol.Kind != SymbolKind.Method) { return; } sortedCallbackCodeBlockNames.Add($"{context.OwningSymbol.ContainingType.Name}(IsGeneratedCode:{context.IsGeneratedCode})"); }); } context.RegisterCompilationEndAction(endContext => { var arg1 = sortedCallbackSymbolNames.Join(","); var arg2 = sortedCallbackTreePaths.Join(","); var args = new object[] { arg1, arg2 }; var rule = Summary; if (_testIsGeneratedCodeInCallbacks) { var arg3 = sortedCallbackSyntaxNodeNames.Join(",") + ";" + sortedCallbackOperationNames.Join(",") + ";" + sortedCallbackSemanticModelPaths.Join(",") + ";" + sortedCallbackSymbolStartNames.Join(",") + ";" + sortedCallbackSymbolEndNames.Join(",") + ";" + sortedCallbackOperationBlockStartNames.Join(",") + ";" + sortedCallbackOperationBlockEndNames.Join(",") + ";" + sortedCallbackOperationBlockNames.Join(",") + ";" + sortedCallbackCodeBlockStartNames.Join(",") + ";" + sortedCallbackCodeBlockEndNames.Join(",") + ";" + sortedCallbackCodeBlockNames.Join(","); args = new object[] { arg1, arg2, arg3 }; rule = Summary2; } // Summary diagnostics about received callbacks. var diagnostic = Diagnostic.Create(rule, Location.None, args); endContext.ReportDiagnostic(diagnostic); }); } private void ReportSymbolDiagnostics(ISymbol symbol, Action<Diagnostic> addDiagnostic) { ReportDiagnosticsCore(addDiagnostic, symbol.Locations[0], symbol.Name); } private void ReportTreeDiagnostics(SyntaxTree tree, Action<Diagnostic> addDiagnostic) { ReportDiagnosticsCore(addDiagnostic, tree.GetRoot().GetLastToken().GetLocation(), tree.FilePath); } private void ReportDiagnosticsCore(Action<Diagnostic> addDiagnostic, Location location, params object[] messageArguments) { // warning diagnostic var diagnostic = Diagnostic.Create(Warning, location, messageArguments); addDiagnostic(diagnostic); // error diagnostic diagnostic = Diagnostic.Create(Error, location, messageArguments); addDiagnostic(diagnostic); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class GeneratedCodeAnalyzer2 : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( "GeneratedCodeAnalyzer2Warning", "Title", "GeneratedCodeAnalyzer2Message for '{0}'; Total types analyzed: '{1}'", "Category", DiagnosticSeverity.Warning, true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { // Analyze but don't report diagnostics on generated code. context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); context.RegisterCompilationStartAction(compilationStartContext => { var namedTypes = new HashSet<ISymbol>(); compilationStartContext.RegisterSymbolAction(symbolContext => namedTypes.Add(symbolContext.Symbol), SymbolKind.NamedType); compilationStartContext.RegisterCompilationEndAction(compilationEndContext => { foreach (var namedType in namedTypes) { var diagnostic = Diagnostic.Create(Rule, namedType.Locations[0], namedType.Name, namedTypes.Count); compilationEndContext.ReportDiagnostic(diagnostic); } }); }); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class SharedStateAnalyzer : DiagnosticAnalyzer { private readonly SyntaxTreeValueProvider<bool> _treeValueProvider; private readonly HashSet<SyntaxTree> _treeCallbackSet; private readonly SourceTextValueProvider<int> _textValueProvider; private readonly HashSet<SourceText> _textCallbackSet; public static readonly DiagnosticDescriptor GeneratedCodeDescriptor = new DiagnosticDescriptor( "GeneratedCodeDiagnostic", "Title1", "GeneratedCodeDiagnostic {0}", "Category", DiagnosticSeverity.Warning, true); public static readonly DiagnosticDescriptor NonGeneratedCodeDescriptor = new DiagnosticDescriptor( "UserCodeDiagnostic", "Title2", "UserCodeDiagnostic {0}", "Category", DiagnosticSeverity.Warning, true); public static readonly DiagnosticDescriptor UniqueTextFileDescriptor = new DiagnosticDescriptor( "UniqueTextFileDiagnostic", "Title3", "UniqueTextFileDiagnostic {0}", "Category", DiagnosticSeverity.Warning, true); public static readonly DiagnosticDescriptor NumberOfUniqueTextFileDescriptor = new DiagnosticDescriptor( "NumberOfUniqueTextFileDescriptor", "Title4", "NumberOfUniqueTextFileDescriptor {0}", "Category", DiagnosticSeverity.Warning, true); public SharedStateAnalyzer() { _treeValueProvider = new SyntaxTreeValueProvider<bool>(IsGeneratedCode); _treeCallbackSet = new HashSet<SyntaxTree>(SyntaxTreeComparer.Instance); _textValueProvider = new SourceTextValueProvider<int>(GetCharacterCount); _textCallbackSet = new HashSet<SourceText>(SourceTextComparer.Instance); } private bool IsGeneratedCode(SyntaxTree tree) { lock (_treeCallbackSet) { if (!_treeCallbackSet.Add(tree)) { throw new Exception("Expected driver to make a single callback per tree"); } } var fileNameWithoutExtension = PathUtilities.GetFileName(tree.FilePath, includeExtension: false); return fileNameWithoutExtension.EndsWith(".designer", StringComparison.OrdinalIgnoreCase) || fileNameWithoutExtension.EndsWith(".generated", StringComparison.OrdinalIgnoreCase); } private int GetCharacterCount(SourceText text) { lock (_textCallbackSet) { if (!_textCallbackSet.Add(text)) { throw new Exception("Expected driver to make a single callback per text"); } } return text.Length; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(GeneratedCodeDescriptor, NonGeneratedCodeDescriptor, UniqueTextFileDescriptor, NumberOfUniqueTextFileDescriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(this.OnCompilationStart); } private void OnCompilationStart(CompilationStartAnalysisContext context) { context.RegisterSymbolAction(symbolContext => { var descriptor = GeneratedCodeDescriptor; foreach (var location in symbolContext.Symbol.Locations) { context.TryGetValue(location.SourceTree, _treeValueProvider, out var isGeneratedCode); if (!isGeneratedCode) { descriptor = NonGeneratedCodeDescriptor; break; } } var diagnostic = Diagnostic.Create(descriptor, symbolContext.Symbol.Locations[0], symbolContext.Symbol.Name); symbolContext.ReportDiagnostic(diagnostic); }, SymbolKind.NamedType); context.RegisterSyntaxTreeAction(treeContext => { context.TryGetValue(treeContext.Tree, _treeValueProvider, out var isGeneratedCode); var descriptor = isGeneratedCode ? GeneratedCodeDescriptor : NonGeneratedCodeDescriptor; var diagnostic = Diagnostic.Create(descriptor, Location.None, treeContext.Tree.FilePath); treeContext.ReportDiagnostic(diagnostic); context.TryGetValue(treeContext.Tree.GetText(), _textValueProvider, out var length); diagnostic = Diagnostic.Create(UniqueTextFileDescriptor, Location.None, treeContext.Tree.FilePath); treeContext.ReportDiagnostic(diagnostic); }); context.RegisterCompilationEndAction(endContext => { if (_treeCallbackSet.Count != endContext.Compilation.SyntaxTrees.Count()) { throw new Exception("Expected driver to make a callback for every tree"); } var diagnostic = Diagnostic.Create(NumberOfUniqueTextFileDescriptor, Location.None, _textCallbackSet.Count); endContext.ReportDiagnostic(diagnostic); }); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class AnalyzerForParameters : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor ParameterDescriptor = new DiagnosticDescriptor( "Parameter_ID", "Parameter_Title", "Parameter_Message", "Parameter_Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(ParameterDescriptor); public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(SymbolAction, SymbolKind.Parameter); } private void SymbolAction(SymbolAnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create(ParameterDescriptor, context.Symbol.Locations[0])); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class SymbolStartAnalyzer : DiagnosticAnalyzer { private readonly SymbolKind _symbolKind; private readonly bool _topLevelAction; private readonly OperationKind? _operationKind; private readonly string _analyzerId; public SymbolStartAnalyzer(bool topLevelAction, SymbolKind symbolKind, OperationKind? operationKindOpt = null, int? analyzerId = null) { _topLevelAction = topLevelAction; _symbolKind = symbolKind; _operationKind = operationKindOpt; _analyzerId = $"Analyzer{(analyzerId.HasValue ? analyzerId.Value : 1)}"; SymbolsStarted = new ConcurrentSet<ISymbol>(); } internal ConcurrentSet<ISymbol> SymbolsStarted { get; } public static readonly DiagnosticDescriptor SymbolStartTopLevelRule = new DiagnosticDescriptor( "SymbolStartTopLevelRuleId", "SymbolStartTopLevelRuleTitle", "Symbol : {0}, Analyzer: {1}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor SymbolStartCompilationLevelRule = new DiagnosticDescriptor( "SymbolStartRuleId", "SymbolStartRuleTitle", "Symbol : {0}, Analyzer: {1}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor SymbolStartedEndedDifferRule = new DiagnosticDescriptor( "SymbolStartedEndedDifferRuleId", "SymbolStartedEndedDifferRuleTitle", "Symbols Started: '{0}', Symbols Ended: '{1}', Analyzer: {2}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor SymbolStartedOrderingRule = new DiagnosticDescriptor( "SymbolStartedOrderingRuleId", "SymbolStartedOrderingRuleTitle", "Member '{0}' started before container '{1}', Analyzer: {2}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor SymbolEndedOrderingRule = new DiagnosticDescriptor( "SymbolEndedOrderingRuleId", "SymbolEndedOrderingRuleTitle", "Container '{0}' ended before member '{1}', Analyzer: {2}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor OperationOrderingRule = new DiagnosticDescriptor( "OperationOrderingRuleId", "OperationOrderingRuleTitle", "Container '{0}' started after operation '{1}', Analyzer: {2}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor DuplicateStartActionRule = new DiagnosticDescriptor( "DuplicateStartActionRuleId", "DuplicateStartActionRuleTitle", "Symbol : {0}, Analyzer: {1}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor DuplicateEndActionRule = new DiagnosticDescriptor( "DuplicateEndActionRuleId", "DuplicateEndActionRuleTitle", "Symbol : {0}, Analyzer: {1}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static readonly DiagnosticDescriptor OperationRule = new DiagnosticDescriptor( "OperationRuleId", "OperationRuleTitle", "Symbol Started: '{0}', Owning Symbol: '{1}' Operation : {2}, Analyzer: {3}", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create( SymbolStartTopLevelRule, SymbolStartCompilationLevelRule, SymbolStartedEndedDifferRule, SymbolStartedOrderingRule, SymbolEndedOrderingRule, DuplicateStartActionRule, DuplicateEndActionRule, OperationRule, OperationOrderingRule); } } public override void Initialize(AnalysisContext context) { var diagnostics = new ConcurrentBag<Diagnostic>(); var symbolsEnded = new ConcurrentSet<ISymbol>(); var seenOperationContainers = new ConcurrentDictionary<OperationAnalysisContext, ISet<ISymbol>>(); if (_topLevelAction) { context.RegisterSymbolStartAction(onSymbolStart, _symbolKind); context.RegisterCompilationStartAction(compilationStartContext => { compilationStartContext.RegisterCompilationEndAction(compilationEndContext => { reportDiagnosticsAtCompilationEnd(compilationEndContext); }); }); } else { context.RegisterCompilationStartAction(compilationStartContext => { compilationStartContext.RegisterSymbolStartAction(onSymbolStart, _symbolKind); compilationStartContext.RegisterCompilationEndAction(compilationEndContext => { reportDiagnosticsAtCompilationEnd(compilationEndContext); }); }); } return; void onSymbolStart(SymbolStartAnalysisContext symbolStartContext) { performSymbolStartActionVerification(symbolStartContext); if (_operationKind.HasValue) { symbolStartContext.RegisterOperationAction(operationContext => { performOperationActionVerification(operationContext, symbolStartContext); }, _operationKind.Value); } symbolStartContext.RegisterSymbolEndAction(symbolEndContext => { performSymbolEndActionVerification(symbolEndContext, symbolStartContext); }); } void reportDiagnosticsAtCompilationEnd(CompilationAnalysisContext compilationEndContext) { if (!SymbolsStarted.SetEquals(symbolsEnded)) { // Symbols Started: '{0}', Symbols Ended: '{1}', Analyzer: {2} var symbolsStartedStr = string.Join(", ", SymbolsStarted.Select(s => s.ToDisplayString().Order())); var symbolsEndedStr = string.Join(", ", symbolsEnded.Select(s => s.ToDisplayString().Order())); compilationEndContext.ReportDiagnostic(Diagnostic.Create(SymbolStartedEndedDifferRule, Location.None, symbolsStartedStr, symbolsEndedStr, _analyzerId)); } foreach (var diagnostic in diagnostics) { compilationEndContext.ReportDiagnostic(diagnostic); } } void performSymbolStartActionVerification(SymbolStartAnalysisContext symbolStartContext) { verifySymbolStartOrdering(symbolStartContext); verifySymbolStartAndOperationOrdering(symbolStartContext); if (!SymbolsStarted.Add(symbolStartContext.Symbol)) { diagnostics.Add(Diagnostic.Create(DuplicateStartActionRule, Location.None, symbolStartContext.Symbol.Name, _analyzerId)); } } void performSymbolEndActionVerification(SymbolAnalysisContext symbolEndContext, SymbolStartAnalysisContext symbolStartContext) { Assert.Equal(symbolStartContext.Symbol, symbolEndContext.Symbol); verifySymbolEndOrdering(symbolEndContext); if (!symbolsEnded.Add(symbolEndContext.Symbol)) { diagnostics.Add(Diagnostic.Create(DuplicateEndActionRule, Location.None, symbolEndContext.Symbol.Name, _analyzerId)); } Assert.False(symbolEndContext.Symbol.IsImplicitlyDeclared); var rule = _topLevelAction ? SymbolStartTopLevelRule : SymbolStartCompilationLevelRule; symbolEndContext.ReportDiagnostic(Diagnostic.Create(rule, Location.None, symbolStartContext.Symbol.Name, _analyzerId)); } void performOperationActionVerification(OperationAnalysisContext operationContext, SymbolStartAnalysisContext symbolStartContext) { var containingSymbols = GetContainingSymbolsAndThis(operationContext.ContainingSymbol).ToSet(); seenOperationContainers.Add(operationContext, containingSymbols); Assert.Contains(symbolStartContext.Symbol, containingSymbols); Assert.All(containingSymbols, s => Assert.DoesNotContain(s, symbolsEnded)); // Symbol Started: '{0}', Owning Symbol: '{1}' Operation : {2}, Analyzer: {3} operationContext.ReportDiagnostic(Diagnostic.Create(OperationRule, Location.None, symbolStartContext.Symbol.Name, operationContext.ContainingSymbol.Name, operationContext.Operation.Syntax.ToString(), _analyzerId)); } IEnumerable<ISymbol> GetContainingSymbolsAndThis(ISymbol symbol) { do { yield return symbol; symbol = symbol.ContainingSymbol; } while (symbol != null && !symbol.IsImplicitlyDeclared); } void verifySymbolStartOrdering(SymbolStartAnalysisContext symbolStartContext) { ISymbol symbolStarted = symbolStartContext.Symbol; IEnumerable<ISymbol> members; switch (symbolStarted) { case INamedTypeSymbol namedType: members = namedType.GetMembers(); break; case INamespaceSymbol namespaceSym: members = namespaceSym.GetMembers(); break; default: return; } foreach (var member in members.Where(m => !m.IsImplicitlyDeclared)) { if (SymbolsStarted.Contains(member)) { // Member '{0}' started before container '{1}', Analyzer {2} diagnostics.Add(Diagnostic.Create(SymbolStartedOrderingRule, Location.None, member, symbolStarted, _analyzerId)); } } } void verifySymbolEndOrdering(SymbolAnalysisContext symbolEndContext) { ISymbol symbolEnded = symbolEndContext.Symbol; IList<ISymbol> containersToVerify = new List<ISymbol>(); if (symbolEnded.ContainingType != null) { containersToVerify.Add(symbolEnded.ContainingType); } if (symbolEnded.ContainingNamespace != null) { containersToVerify.Add(symbolEnded.ContainingNamespace); } foreach (var container in containersToVerify) { if (symbolsEnded.Contains(container)) { // Container '{0}' ended before member '{1}', Analyzer {2} diagnostics.Add(Diagnostic.Create(SymbolEndedOrderingRule, Location.None, container, symbolEnded, _analyzerId)); } } } void verifySymbolStartAndOperationOrdering(SymbolStartAnalysisContext symbolStartContext) { foreach (var kvp in seenOperationContainers) { OperationAnalysisContext operationContext = kvp.Key; ISet<ISymbol> containers = kvp.Value; if (containers.Contains(symbolStartContext.Symbol)) { // Container '{0}' started after operation '{1}', Analyzer {2} diagnostics.Add(Diagnostic.Create(OperationOrderingRule, Location.None, symbolStartContext.Symbol, operationContext.Operation.Syntax.ToString(), _analyzerId)); } } } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressorForId : DiagnosticSuppressor { public SuppressionDescriptor SuppressionDescriptor { get; } public DiagnosticSuppressorForId(string suppressedDiagnosticId, string suppressionId = null) { SuppressionDescriptor = new SuppressionDescriptor( id: suppressionId ?? "SPR0001", suppressedDiagnosticId: suppressedDiagnosticId, justification: $"Suppress {suppressedDiagnosticId}"); } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(SuppressionDescriptor); public override void ReportSuppressions(SuppressionAnalysisContext context) { foreach (var diagnostic in context.ReportedDiagnostics) { Assert.Equal(SuppressionDescriptor.SuppressedDiagnosticId, diagnostic.Id); context.ReportSuppression(Suppression.Create(SuppressionDescriptor, diagnostic)); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressorForMultipleIds : DiagnosticSuppressor { public DiagnosticSuppressorForMultipleIds(params string[] suppressedDiagnosticIds) { var builder = ImmutableArray.CreateBuilder<SuppressionDescriptor>(); int i = 1; foreach (var suppressedDiagnosticId in suppressedDiagnosticIds) { var descriptor = new SuppressionDescriptor( id: $"SPR000{i++}", suppressedDiagnosticId: suppressedDiagnosticId, justification: $"Suppress {suppressedDiagnosticId}"); builder.Add(descriptor); } SupportedSuppressions = builder.ToImmutable(); } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions { get; } public override void ReportSuppressions(SuppressionAnalysisContext context) { foreach (var diagnostic in context.ReportedDiagnostics) { foreach (var suppressionDescriptor in SupportedSuppressions) { if (suppressionDescriptor.SuppressedDiagnosticId == diagnostic.Id) { context.ReportSuppression(Suppression.Create(suppressionDescriptor, diagnostic)); } } } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressorForId_ThrowsOperationCancelledException : DiagnosticSuppressor { public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); public SuppressionDescriptor SuppressionDescriptor { get; } public DiagnosticSuppressorForId_ThrowsOperationCancelledException(string suppressedDiagnosticId) { SuppressionDescriptor = new SuppressionDescriptor( id: "SPR0001", suppressedDiagnosticId: suppressedDiagnosticId, justification: $"Suppress {suppressedDiagnosticId}"); } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(SuppressionDescriptor); public override void ReportSuppressions(SuppressionAnalysisContext context) { CancellationTokenSource.Cancel(); context.CancellationToken.ThrowIfCancellationRequested(); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressorThrowsExceptionFromSupportedSuppressions : DiagnosticSuppressor { private readonly NotImplementedException _exception; public DiagnosticSuppressorThrowsExceptionFromSupportedSuppressions(NotImplementedException exception) { _exception = exception; } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => throw _exception; public override void ReportSuppressions(SuppressionAnalysisContext context) { } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressorThrowsExceptionFromReportedSuppressions : DiagnosticSuppressor { private readonly SuppressionDescriptor _descriptor; private readonly NotImplementedException _exception; public DiagnosticSuppressorThrowsExceptionFromReportedSuppressions(string suppressedDiagnosticId, NotImplementedException exception) { _descriptor = new SuppressionDescriptor( "SPR0001", suppressedDiagnosticId, $"Suppress {suppressedDiagnosticId}"); _exception = exception; } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(_descriptor); public override void ReportSuppressions(SuppressionAnalysisContext context) { throw _exception; } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressor_UnsupportedSuppressionReported : DiagnosticSuppressor { private readonly SuppressionDescriptor _supportedDescriptor; private readonly SuppressionDescriptor _unsupportedDescriptor; public DiagnosticSuppressor_UnsupportedSuppressionReported(string suppressedDiagnosticId, string supportedSuppressionId, string unsupportedSuppressionId) { _supportedDescriptor = new SuppressionDescriptor( supportedSuppressionId, suppressedDiagnosticId, $"Suppress {suppressedDiagnosticId}"); _unsupportedDescriptor = new SuppressionDescriptor( unsupportedSuppressionId, suppressedDiagnosticId, $"Suppress {suppressedDiagnosticId}"); } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(_supportedDescriptor); public override void ReportSuppressions(SuppressionAnalysisContext context) { foreach (var diagnostic in context.ReportedDiagnostics) { Assert.Equal(_unsupportedDescriptor.SuppressedDiagnosticId, diagnostic.Id); context.ReportSuppression(Suppression.Create(_unsupportedDescriptor, diagnostic)); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressor_InvalidDiagnosticSuppressionReported : DiagnosticSuppressor { private readonly SuppressionDescriptor _supportedDescriptor; private readonly SuppressionDescriptor _unsupportedDescriptor; public DiagnosticSuppressor_InvalidDiagnosticSuppressionReported(string suppressedDiagnosticId, string unsupportedSuppressedDiagnosticId) { _supportedDescriptor = new SuppressionDescriptor( "SPR0001", suppressedDiagnosticId, $"Suppress {suppressedDiagnosticId}"); _unsupportedDescriptor = new SuppressionDescriptor( "SPR0002", unsupportedSuppressedDiagnosticId, $"Suppress {unsupportedSuppressedDiagnosticId}"); } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(_supportedDescriptor); public override void ReportSuppressions(SuppressionAnalysisContext context) { foreach (var diagnostic in context.ReportedDiagnostics) { Assert.Equal(_supportedDescriptor.SuppressedDiagnosticId, diagnostic.Id); context.ReportSuppression(Suppression.Create(_unsupportedDescriptor, diagnostic)); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DiagnosticSuppressor_NonReportedDiagnosticCannotBeSuppressed : DiagnosticSuppressor { private readonly SuppressionDescriptor _descriptor1, _descriptor2; private readonly string _nonReportedDiagnosticId; public DiagnosticSuppressor_NonReportedDiagnosticCannotBeSuppressed(string reportedDiagnosticId, string nonReportedDiagnosticId) { _descriptor1 = new SuppressionDescriptor( "SPR0001", reportedDiagnosticId, $"Suppress {reportedDiagnosticId}"); _descriptor2 = new SuppressionDescriptor( "SPR0002", nonReportedDiagnosticId, $"Suppress {nonReportedDiagnosticId}"); _nonReportedDiagnosticId = nonReportedDiagnosticId; } public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(_descriptor1, _descriptor2); public override void ReportSuppressions(SuppressionAnalysisContext context) { var nonReportedDiagnostic = Diagnostic.Create( id: _nonReportedDiagnosticId, category: "Category", message: "Message", severity: DiagnosticSeverity.Warning, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, warningLevel: 1); context.ReportSuppression(Suppression.Create(_descriptor2, nonReportedDiagnostic)); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class NamedTypeAnalyzer : DiagnosticAnalyzer { public enum AnalysisKind { Symbol, SymbolStartEnd, CompilationStartEnd } public const string RuleId = "ID1"; public const string RuleCategory = "Category1"; private readonly DiagnosticDescriptor _rule; private readonly AnalysisKind _analysisKind; private readonly GeneratedCodeAnalysisFlags _analysisFlags; private readonly ConcurrentSet<ISymbol> _symbolCallbacks; public NamedTypeAnalyzer(AnalysisKind analysisKind, GeneratedCodeAnalysisFlags analysisFlags = GeneratedCodeAnalysisFlags.None, bool configurable = true) { _analysisKind = analysisKind; _analysisFlags = analysisFlags; _symbolCallbacks = new ConcurrentSet<ISymbol>(); var customTags = configurable ? Array.Empty<string>() : new[] { WellKnownDiagnosticTags.NotConfigurable }; _rule = new DiagnosticDescriptor( RuleId, "Title1", "Symbol: {0}", RuleCategory, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, customTags: customTags); } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule); public string GetSortedSymbolCallbacksString() => string.Join(", ", _symbolCallbacks.Select(s => s.Name).Order()); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(_analysisFlags); switch (_analysisKind) { case AnalysisKind.Symbol: context.RegisterSymbolAction(c => { _symbolCallbacks.Add(c.Symbol); ReportDiagnostic(c.Symbol, c.ReportDiagnostic); }, SymbolKind.NamedType); break; case AnalysisKind.SymbolStartEnd: context.RegisterSymbolStartAction(symbolStartContext => { symbolStartContext.RegisterSymbolEndAction(symbolEndContext => { _symbolCallbacks.Add(symbolEndContext.Symbol); ReportDiagnostic(symbolEndContext.Symbol, symbolEndContext.ReportDiagnostic); }); }, SymbolKind.NamedType); break; case AnalysisKind.CompilationStartEnd: context.RegisterCompilationStartAction(compilationStartContext => { compilationStartContext.RegisterSymbolAction(c => { _symbolCallbacks.Add(c.Symbol); }, SymbolKind.NamedType); compilationStartContext.RegisterCompilationEndAction( compilationEndContext => compilationEndContext.ReportDiagnostic( Diagnostic.Create(_rule, Location.None, GetSortedSymbolCallbacksString()))); }); break; } } private void ReportDiagnostic(ISymbol symbol, Action<Diagnostic> reportDiagnostic) => reportDiagnostic(Diagnostic.Create(_rule, symbol.Locations[0], symbol.Name)); } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithNoLocationDiagnostics : DiagnosticAnalyzer { public AnalyzerWithNoLocationDiagnostics(bool isEnabledByDefault) { Descriptor = new DiagnosticDescriptor( "ID0001", "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault); } public DiagnosticDescriptor Descriptor { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationAction(compilationContext => compilationContext.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.None))); } } /// <summary> /// An analyzer that reports a diagnostic on every single named type. /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class NamedTypeAnalyzerWithConfigurableEnabledByDefault : DiagnosticAnalyzer { private readonly bool _throwOnAllNamedTypes; private readonly DiagnosticSeverity _reportedSeverity; public NamedTypeAnalyzerWithConfigurableEnabledByDefault(bool isEnabledByDefault, DiagnosticSeverity defaultSeverity, bool throwOnAllNamedTypes = false) : this(isEnabledByDefault, defaultSeverity, customConfigurable: false, throwOnAllNamedTypes) { } public NamedTypeAnalyzerWithConfigurableEnabledByDefault(bool isEnabledByDefault, DiagnosticSeverity defaultSeverity, bool customConfigurable, bool throwOnAllNamedTypes) : this(isEnabledByDefault, defaultSeverity, defaultSeverity, customConfigurable, throwOnAllNamedTypes) { } public NamedTypeAnalyzerWithConfigurableEnabledByDefault( bool isEnabledByDefault, DiagnosticSeverity defaultSeverity, DiagnosticSeverity reportedSeverity, bool customConfigurable, bool throwOnAllNamedTypes) { var customTags = Array.Empty<string>(); if (customConfigurable) customTags = [WellKnownDiagnosticTags.CustomSeverityConfigurable]; Descriptor = new DiagnosticDescriptor( "ID0001", "Title1", "Message1", "Category1", defaultSeverity, isEnabledByDefault, customTags: customTags); _throwOnAllNamedTypes = throwOnAllNamedTypes; _reportedSeverity = reportedSeverity; } public DiagnosticDescriptor Descriptor { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(context => { if (_throwOnAllNamedTypes) { throw new NotImplementedException(); } var diagnostic = Diagnostic.Create(Descriptor, context.Symbol.Locations[0], _reportedSeverity, additionalLocations: null, properties: null, messageArgs: null); context.ReportDiagnostic(diagnostic); }, SymbolKind.NamedType); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class RegisterOperationBlockAndOperationActionAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor s_descriptor = new DiagnosticDescriptor( "ID0001", "Title", "Message", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor); public override void Initialize(AnalysisContext analysisContext) { analysisContext.RegisterOperationAction(_ => { }, OperationKind.Invocation); analysisContext.RegisterOperationBlockStartAction(OnOperationBlockStart); } private void OnOperationBlockStart(OperationBlockStartAnalysisContext context) { context.RegisterOperationBlockEndAction( endContext => endContext.ReportDiagnostic(Diagnostic.Create(s_descriptor, context.OwningSymbol.Locations[0]))); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FieldAnalyzer : DiagnosticAnalyzer { private readonly bool _syntaxTreeAction; public FieldAnalyzer(string diagnosticId, bool syntaxTreeAction) { _syntaxTreeAction = syntaxTreeAction; Descriptor = new DiagnosticDescriptor( diagnosticId, "Title", "Message", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); } public DiagnosticDescriptor Descriptor { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { if (_syntaxTreeAction) { context.RegisterSyntaxTreeAction(context => { var fields = context.Tree.GetRoot().DescendantNodes().OfType<CSharp.Syntax.FieldDeclarationSyntax>(); foreach (var variable in fields.SelectMany(f => f.Declaration.Variables)) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, variable.Identifier.GetLocation())); } }); } else { context.RegisterSymbolAction( context => context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Symbol.Locations[0])), SymbolKind.Field); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class AdditionalFileAnalyzer : DiagnosticAnalyzer { private readonly bool _registerFromInitialize; private readonly TextSpan _diagnosticSpan; public AdditionalFileAnalyzer(bool registerFromInitialize, TextSpan diagnosticSpan, string id = "ID0001") { _registerFromInitialize = registerFromInitialize; _diagnosticSpan = diagnosticSpan; Descriptor = new DiagnosticDescriptor( id, "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); } public DiagnosticDescriptor Descriptor { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { if (_registerFromInitialize) { context.RegisterAdditionalFileAction(AnalyzeAdditionalFile); } else { context.RegisterCompilationStartAction(context => context.RegisterAdditionalFileAction(AnalyzeAdditionalFile)); } } private void AnalyzeAdditionalFile(AdditionalFileAnalysisContext context) { if (context.AdditionalFile.Path == null) { return; } var text = context.AdditionalFile.GetText(); var location = Location.Create(context.AdditionalFile.Path, _diagnosticSpan, text.Lines.GetLinePositionSpan(_diagnosticSpan)); context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class RegisterSyntaxTreeCancellationAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "ID0001"; private static readonly DiagnosticDescriptor s_descriptor = new DiagnosticDescriptor( DiagnosticId, "Title", "Message", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); public CancellationToken CancellationToken => _cancellationTokenSource.Token; public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor); public override void Initialize(AnalysisContext context) { context.RegisterSyntaxTreeAction(context => { // Mimic cancellation by throwing an OperationCanceledException in first callback. if (!_cancellationTokenSource.IsCancellationRequested) { _cancellationTokenSource.Cancel(); while (true) { context.CancellationToken.ThrowIfCancellationRequested(); } throw ExceptionUtilities.Unreachable(); } context.ReportDiagnostic(Diagnostic.Create(s_descriptor, context.Tree.GetRoot().GetLocation())); }); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LocalNonLocalDiagnosticsAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor s_descriptor = new("ID0001", "Title", "{0}", "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true); private readonly ActionKind _actionKind; public enum ActionKind { SyntaxTreeAction, SemanticModelAction, SymbolAction, SymbolStartEndAction, OperationAction, OperationBlockAction, OperationBlockStartEndAction, SyntaxNodeAction, CodeBlockAction, CodeBlockStartEndAction } public LocalNonLocalDiagnosticsAnalyzer(ActionKind actionKind) { _actionKind = actionKind; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(AnalyzeCompilation); } private void AnalyzeCompilation(CompilationStartAnalysisContext context) { switch (_actionKind) { case ActionKind.SyntaxTreeAction: context.RegisterSyntaxTreeAction(treeContext => ReportDiagnosticsInAllTrees("RegisterSyntaxTreeAction", treeContext.Tree, treeContext.Compilation, treeContext.ReportDiagnostic)); break; case ActionKind.SemanticModelAction: context.RegisterSemanticModelAction(semanticModelContext => ReportDiagnosticsInAllTrees("RegisterSemanticModelAction", semanticModelContext.SemanticModel.SyntaxTree, semanticModelContext.SemanticModel.Compilation, semanticModelContext.ReportDiagnostic)); break; case ActionKind.SymbolAction: context.RegisterSymbolAction(symbolContext => ReportSymbolDiagnostics("RegisterSymbolAction", symbolContext.Symbol, symbolContext.Compilation, symbolContext.ReportDiagnostic), SymbolKind.NamedType, SymbolKind.Method); break; case ActionKind.SymbolStartEndAction: context.RegisterSymbolStartAction(symbolStartContext => { symbolStartContext.RegisterOperationAction(operationContext => ReportDiagnostics($"RegisterOperationAction({operationContext.Operation.Syntax}) in RegisterSymbolStartAction", operationContext.Operation.Syntax.SyntaxTree, operationContext.ContainingSymbol, operationContext.ReportDiagnostic), OperationKind.VariableDeclarationGroup); symbolStartContext.RegisterSyntaxNodeAction(syntaxNodeContext => ReportDiagnostics($"RegisterSyntaxNodeAction({syntaxNodeContext.Node}) in RegisterSymbolStartAction", syntaxNodeContext.Node.SyntaxTree, syntaxNodeContext.ContainingSymbol, syntaxNodeContext.ReportDiagnostic), SyntaxKind.LocalDeclarationStatement); symbolStartContext.RegisterSymbolEndAction(symbolEndContext => ReportSymbolDiagnostics("RegisterSymbolEndAction", symbolEndContext.Symbol, symbolEndContext.Compilation, symbolEndContext.ReportDiagnostic)); }, SymbolKind.NamedType); break; case ActionKind.OperationAction: context.RegisterOperationAction(operationContext => ReportDiagnostics($"RegisterOperationAction({operationContext.Operation.Syntax})", operationContext.Operation.Syntax.SyntaxTree, operationContext.ContainingSymbol, operationContext.ReportDiagnostic), OperationKind.VariableDeclarationGroup); break; case ActionKind.OperationBlockAction: context.RegisterOperationBlockAction(operationBlockContext => ReportDiagnostics("RegisterOperationBlockAction", operationBlockContext.OwningSymbol.DeclaringSyntaxReferences[0].SyntaxTree, operationBlockContext.OwningSymbol, operationBlockContext.ReportDiagnostic)); break; case ActionKind.OperationBlockStartEndAction: context.RegisterOperationBlockStartAction(operationBlockStartContext => { operationBlockStartContext.RegisterOperationAction(operationContext => ReportDiagnostics($"RegisterOperationAction({operationContext.Operation.Syntax}) in RegisterOperationBlockStartAction", operationContext.Operation.Syntax.SyntaxTree, operationContext.ContainingSymbol, operationContext.ReportDiagnostic), OperationKind.VariableDeclarationGroup); operationBlockStartContext.RegisterOperationBlockEndAction(operationBlockEndContext => ReportDiagnostics("RegisterOperationBlockEndAction", operationBlockEndContext.OwningSymbol.DeclaringSyntaxReferences[0].SyntaxTree, operationBlockEndContext.OwningSymbol, operationBlockEndContext.ReportDiagnostic)); }); break; case ActionKind.SyntaxNodeAction: context.RegisterSyntaxNodeAction(syntaxNodeContext => ReportDiagnostics($"RegisterSyntaxNodeAction({syntaxNodeContext.Node})", syntaxNodeContext.Node.SyntaxTree, syntaxNodeContext.ContainingSymbol, syntaxNodeContext.ReportDiagnostic), SyntaxKind.LocalDeclarationStatement); break; case ActionKind.CodeBlockAction: context.RegisterCodeBlockAction(codeBlockContext => ReportDiagnostics("RegisterCodeBlockAction", codeBlockContext.CodeBlock.SyntaxTree, codeBlockContext.OwningSymbol, codeBlockContext.ReportDiagnostic)); break; case ActionKind.CodeBlockStartEndAction: context.RegisterCodeBlockStartAction<SyntaxKind>(codeBlockStartContext => { codeBlockStartContext.RegisterSyntaxNodeAction(syntaxNodeContext => ReportDiagnostics($"RegisterSyntaxNodeAction({syntaxNodeContext.Node}) in RegisterCodeBlockStartAction", syntaxNodeContext.Node.SyntaxTree, syntaxNodeContext.ContainingSymbol, syntaxNodeContext.ReportDiagnostic), SyntaxKind.LocalDeclarationStatement); codeBlockStartContext.RegisterCodeBlockEndAction(codeBlockEndContext => ReportDiagnostics("RegisterCodeBlockEndAction", codeBlockEndContext.CodeBlock.SyntaxTree, codeBlockEndContext.OwningSymbol, codeBlockEndContext.ReportDiagnostic)); }); break; default: throw ExceptionUtilities.UnexpectedValue(_actionKind); } } private static void ReportSymbolDiagnostics(string actionName, ISymbol symbol, Compilation compilation, Action<Diagnostic> reportDiagnostic) { var arg = $"{actionName}({symbol.Name})"; var trees = symbol.DeclaringSyntaxReferences.Select(syntaxRef => syntaxRef.SyntaxTree).Distinct(); foreach (var tree in trees) { var index = compilation.SyntaxTrees.IndexOf(tree) + 1; ReportDiagnosticsInTreeCore($"{arg}(File{index})", tree, reportDiagnostic); } } private static void ReportDiagnosticsInAllTrees(string actionName, SyntaxTree analyzedTree, Compilation compilation, Action<Diagnostic> reportDiagnostic) { var index = compilation.SyntaxTrees.IndexOf(analyzedTree) + 1; foreach (var tree in compilation.SyntaxTrees) { ReportDiagnosticsInTreeCore($"{actionName}(File{index})", tree, reportDiagnostic); } } private static void ReportDiagnostics(string actionName, SyntaxTree tree, ISymbol containingSymbol, Action<Diagnostic> reportDiagnostic) { var arg = $"{actionName}({containingSymbol.Name})"; ReportDiagnosticsInTreeCore(arg, tree, reportDiagnostic); } private static void ReportDiagnosticsInTreeCore(string arg, SyntaxTree tree, Action<Diagnostic> reportDiagnostic) { var root = tree.GetRoot(); foreach (var localDecl in root.DescendantNodes().OfType<CSharp.Syntax.LocalDeclarationStatementSyntax>()) { reportDiagnostic(Diagnostic.Create(s_descriptor, localDecl.GetLocation(), arg)); } } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class VariableDeclarationAnalyzer : DiagnosticAnalyzer { private readonly bool _testSyntaxNodeAction; public VariableDeclarationAnalyzer(string diagnosticId, bool testSyntaxNodeAction) { _testSyntaxNodeAction = testSyntaxNodeAction; Descriptor = new DiagnosticDescriptor( diagnosticId, "Title", "Message", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); } public DiagnosticDescriptor Descriptor { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor); public override void Initialize(AnalysisContext context) { if (_testSyntaxNodeAction) { context.RegisterSyntaxNodeAction<SyntaxKind>( context => context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation())), SyntaxKind.VariableDeclaration); } else { context.RegisterOperationAction( context => context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Operation.Syntax.GetLocation())), OperationKind.VariableDeclaration); } } } internal enum AnalyzerRegisterActionKind { SyntaxTree, SyntaxNode, Symbol, Operation, SemanticModel, } [DiagnosticAnalyzer(LanguageNames.CSharp)] internal sealed class CancellationTestAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "DiagnosticId"; private readonly DiagnosticDescriptor s_descriptor = new DiagnosticDescriptor(DiagnosticId, "test", "test", "test", DiagnosticSeverity.Warning, isEnabledByDefault: true); private readonly AnalyzerRegisterActionKind _actionKind; private readonly CancellationTokenSource _cancellationTokenSource = new(); public CancellationTestAnalyzer(AnalyzerRegisterActionKind actionKind) { _actionKind = actionKind; CanceledCompilations = new ConcurrentSet<Compilation>(); } public CancellationToken CancellationToken => _cancellationTokenSource.Token; public ConcurrentSet<Compilation> CanceledCompilations { get; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(OnCompilationStart); } private void OnCompilationStart(CompilationStartAnalysisContext context) { switch (_actionKind) { case AnalyzerRegisterActionKind.SyntaxTree: context.RegisterSyntaxTreeAction(syntaxContext => HandleCallback(syntaxContext.Tree.GetRoot().GetLocation(), context.Compilation, syntaxContext.ReportDiagnostic, syntaxContext.CancellationToken)); break; case AnalyzerRegisterActionKind.SyntaxNode: context.RegisterSyntaxNodeAction(context => HandleCallback(context.Node.GetLocation(), context.Compilation, context.ReportDiagnostic, context.CancellationToken), CodeAnalysis.CSharp.SyntaxKind.ClassDeclaration); break; case AnalyzerRegisterActionKind.Symbol: context.RegisterSymbolAction(context => HandleCallback(context.Symbol.Locations[0], context.Compilation, context.ReportDiagnostic, context.CancellationToken), SymbolKind.NamedType); break; case AnalyzerRegisterActionKind.Operation: context.RegisterOperationAction(context => HandleCallback(context.Operation.Syntax.GetLocation(), context.Compilation, context.ReportDiagnostic, context.CancellationToken), OperationKind.VariableDeclaration); break; case AnalyzerRegisterActionKind.SemanticModel: context.RegisterSemanticModelAction(context => HandleCallback(context.SemanticModel.SyntaxTree.GetRoot().GetLocation(), context.SemanticModel.Compilation, context.ReportDiagnostic, context.CancellationToken)); break; } } private void HandleCallback(Location analysisLocation, Compilation compilation, Action<Diagnostic> reportDiagnostic, CancellationToken cancellationToken) { // Mimic cancellation by throwing an OperationCanceledException in first callback. if (!_cancellationTokenSource.IsCancellationRequested) { _cancellationTokenSource.Cancel(); CanceledCompilations.Add(compilation); while (true) { cancellationToken.ThrowIfCancellationRequested(); } throw ExceptionUtilities.Unreachable(); } // Report diagnostic in the second callback. reportDiagnostic(Diagnostic.Create(s_descriptor, analysisLocation)); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AllActionsAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor s_descriptor = new("ID0001", "Title", "Message", "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true); private readonly bool _testSyntaxTreeAction; private readonly bool _testSemanticModelAction; private readonly bool _testSymbolStartAction; private readonly bool _testBlockActions; public readonly List<SyntaxTree> AnalyzedTrees = new(); public readonly List<SemanticModel> AnalyzedSemanticModels = new(); public readonly List<ISymbol> AnalyzedSymbols = new(); public readonly List<ISymbol> AnalyzedSymbolStartSymbols = new(); public readonly List<ISymbol> AnalyzedSymbolEndSymbols = new(); public readonly List<IOperation> AnalyzedOperations = new(); public readonly List<ISymbol> AnalyzedOperationBlockSymbols = new(); public readonly List<IOperation> AnalyzedOperationsInsideOperationBlock = new(); public readonly List<ISymbol> AnalyzedOperationBlockStartSymbols = new(); public readonly List<ISymbol> AnalyzedOperationBlockEndSymbols = new(); public readonly List<SyntaxNode> AnalyzedSyntaxNodes = new(); public readonly List<ISymbol> AnalyzedCodeBlockSymbols = new(); public readonly List<SyntaxNode> AnalyzedSyntaxNodesInsideCodeBlock = new(); public readonly List<ISymbol> AnalyzedCodeBlockStartSymbols = new(); public readonly List<ISymbol> AnalyzedCodeBlockEndSymbols = new(); public AllActionsAnalyzer(bool testSyntaxTreeAction, bool testSemanticModelAction, bool testSymbolStartAction, bool testBlockActions) { _testSyntaxTreeAction = testSyntaxTreeAction; _testSemanticModelAction = testSemanticModelAction; _testSymbolStartAction = testSymbolStartAction; _testBlockActions = testBlockActions; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor); public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(AnalyzeCompilation); } private void AnalyzeCompilation(CompilationStartAnalysisContext context) { // Unconditionally test symbol/operation/node actions. context.RegisterSymbolAction(symbolContext => AnalyzedSymbols.Add(symbolContext.Symbol), SymbolKind.NamedType, SymbolKind.Method); context.RegisterOperationAction(operationContext => AnalyzedOperations.Add(operationContext.Operation), OperationKind.VariableDeclaration); context.RegisterSyntaxNodeAction(syntaxNodeContext => AnalyzedSyntaxNodes.Add(syntaxNodeContext.Node), SyntaxKind.LocalDeclarationStatement); if (_testSyntaxTreeAction) { context.RegisterSyntaxTreeAction(treeContext => AnalyzedTrees.Add(treeContext.Tree)); } if (_testSemanticModelAction) { context.RegisterSemanticModelAction(semanticModelContext => AnalyzedSemanticModels.Add(semanticModelContext.SemanticModel)); } if (_testSymbolStartAction) { context.RegisterSymbolStartAction(symbolStartContext => { AnalyzedSymbolStartSymbols.Add(symbolStartContext.Symbol); symbolStartContext.RegisterSymbolEndAction(symbolEndContext => AnalyzedSymbolEndSymbols.Add(symbolEndContext.Symbol)); }, SymbolKind.NamedType); } if (_testBlockActions) { context.RegisterOperationBlockAction(operationBlockContext => AnalyzedOperationBlockSymbols.Add(operationBlockContext.OwningSymbol)); context.RegisterOperationBlockStartAction(operationBlockStartContext => { AnalyzedOperationBlockStartSymbols.Add(operationBlockStartContext.OwningSymbol); operationBlockStartContext.RegisterOperationAction(operationContext => AnalyzedOperationsInsideOperationBlock.Add(operationContext.Operation), OperationKind.VariableDeclaration); operationBlockStartContext.RegisterOperationBlockEndAction(operationBlockEndContext => AnalyzedOperationBlockEndSymbols.Add(operationBlockEndContext.OwningSymbol)); }); context.RegisterCodeBlockAction(codeBlockContext => AnalyzedCodeBlockSymbols.Add(codeBlockContext.OwningSymbol)); context.RegisterCodeBlockStartAction<SyntaxKind>(codeBlockStartContext => { AnalyzedCodeBlockStartSymbols.Add(codeBlockStartContext.OwningSymbol); codeBlockStartContext.RegisterSyntaxNodeAction(syntaxNodeContext => AnalyzedSyntaxNodesInsideCodeBlock.Add(syntaxNodeContext.Node), SyntaxKind.LocalDeclarationStatement); codeBlockStartContext.RegisterCodeBlockEndAction(codeBlockEndContext => AnalyzedCodeBlockEndSymbols.Add(codeBlockEndContext.OwningSymbol)); }); } } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FilterSpanTestAnalyzer : DiagnosticAnalyzer { public enum AnalysisKind { SyntaxTree, AdditionalFile, SemanticModel, Symbol, SymbolStart, Operation, OperationBlockStart, OperationBlock, SyntaxNode, CodeBlockStart, CodeBlock, } private static readonly DiagnosticDescriptor s_descriptor = new DiagnosticDescriptor( "ID0001", "Title", "Message", "Category", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); private readonly AnalysisKind _analysisKind; public FilterSpanTestAnalyzer(AnalysisKind analysisKind) { _analysisKind = analysisKind; } public TextSpan? CallbackFilterSpan { get; private set; } public SyntaxTree CallbackFilterTree { get; private set; } public AdditionalText CallbackFilterFile { get; private set; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor); public override void Initialize(AnalysisContext context) { switch (_analysisKind) { case AnalysisKind.SyntaxTree: context.RegisterSyntaxTreeAction(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterTree = context.Tree; }); break; case AnalysisKind.AdditionalFile: context.RegisterAdditionalFileAction(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterFile = context.AdditionalFile; }); break; case AnalysisKind.SemanticModel: context.RegisterSemanticModelAction(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterTree = context.FilterTree; }); break; case AnalysisKind.Symbol: context.RegisterSymbolAction(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterTree = context.FilterTree; }, SymbolKind.NamedType); break; case AnalysisKind.SymbolStart: context.RegisterSymbolStartAction(startContext => { CallbackFilterSpan = startContext.FilterSpan; CallbackFilterTree = startContext.FilterTree; startContext.RegisterSymbolEndAction(endContext => { if (startContext.FilterTree != endContext.FilterTree) throw new InvalidOperationException("Mismatched FilterTree"); if (startContext.FilterSpan != endContext.FilterSpan) throw new InvalidOperationException("Mismatched FilterSpan"); }); }, SymbolKind.NamedType); break; case AnalysisKind.Operation: context.RegisterOperationAction(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterTree = context.FilterTree; }, OperationKind.VariableDeclarationGroup); break; case AnalysisKind.OperationBlockStart: context.RegisterOperationBlockStartAction(startContext => { CallbackFilterSpan = startContext.FilterSpan; CallbackFilterTree = startContext.FilterTree; startContext.RegisterOperationBlockEndAction(endContext => { if (startContext.FilterTree != endContext.FilterTree) throw new InvalidOperationException("Mismatched FilterTree"); if (startContext.FilterSpan != endContext.FilterSpan) throw new InvalidOperationException("Mismatched FilterSpan"); }); }); break; case AnalysisKind.OperationBlock: context.RegisterOperationBlockAction(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterTree = context.FilterTree; }); break; case AnalysisKind.SyntaxNode: context.RegisterSyntaxNodeAction<SyntaxKind>(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterTree = context.FilterTree; }, SyntaxKind.LocalDeclarationStatement); break; case AnalysisKind.CodeBlockStart: context.RegisterCodeBlockStartAction<SyntaxKind>(startContext => { CallbackFilterSpan = startContext.FilterSpan; CallbackFilterTree = startContext.FilterTree; startContext.RegisterCodeBlockEndAction(endContext => { if (startContext.FilterTree != endContext.FilterTree) throw new InvalidOperationException("Mismatched FilterTree"); if (startContext.FilterSpan != endContext.FilterSpan) throw new InvalidOperationException("Mismatched FilterSpan"); }); }); break; case AnalysisKind.CodeBlock: context.RegisterCodeBlockAction(context => { CallbackFilterSpan = context.FilterSpan; CallbackFilterTree = context.FilterTree; }); break; default: throw ExceptionUtilities.UnexpectedValue(_analysisKind); } } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class MinimumReportedSeverityAnalyzer : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor _descriptor = new DiagnosticDescriptor( "ID1", "Title1", "Message1", "Category1", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public DiagnosticSeverity MinimumReportedSeverity { get; private set; } public bool AnalyzerInvoked { get; private set; } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_descriptor); public override void Initialize(AnalysisContext context) { MinimumReportedSeverity = context.MinimumReportedSeverity; AnalyzerInvoked = true; } } } }