diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index eb5b9297e70a3..3deaa69809ac4 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -1356,6 +1356,25 @@ partial class PartialType VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); } + [Fact, WorkItem(11217, "https://github.com/dotnet/roslyn/issues/11217")] + public void TestGeneratedCodeAnalyzerNoReportDiagnostics() + { + string source1 = @" +class TypeInUserFile { } +"; + string source2 = @" +class TypeInGeneratedFile { } +"; + var tree1 = CSharpSyntaxTree.ParseText(source1, path: "SourceFileRegular.cs"); + var tree2 = CSharpSyntaxTree.ParseText(source2, path: "SourceFileRegular.Designer.cs"); + var compilation = CreateCompilationWithMscorlib45(new[] { tree1, tree2 }, new MetadataReference[] { SystemRef }); + compilation.VerifyDiagnostics(); + + var analyzers = new DiagnosticAnalyzer[] { new GeneratedCodeAnalyzer2() }; + compilation.VerifyAnalyzerDiagnostics(analyzers, + expected: Diagnostic("GeneratedCodeAnalyzer2Warning", "TypeInUserFile").WithArguments("TypeInUserFile", "2").WithLocation(2, 7)); + } + internal class OwningSymbolTestAnalyzer : DiagnosticAnalyzer { public static readonly DiagnosticDescriptor ExpressionDescriptor = new DiagnosticDescriptor( diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index ec909ed74d31a..867c018777980 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -74,6 +74,11 @@ internal abstract partial class AnalyzerDriver : IDisposable /// private Dictionary _lazyGeneratedCodeFilesMap; + /// + /// Lazily populated dictionary from tree to declared symbols with GeneratedCodeAttribute. + /// + private Dictionary> _lazyGeneratedCodeSymbolsMap; + /// /// Symbol for . /// @@ -149,6 +154,7 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn _doNotAnalyzeGeneratedCode = ShouldSkipAnalysisOnGeneratedCode(unsuppressedAnalyzers); _treatAllCodeAsNonGeneratedCode = ShouldTreatAllCodeAsNonGeneratedCode(unsuppressedAnalyzers, _generatedCodeAnalysisFlagsMap); _lazyGeneratedCodeFilesMap = _treatAllCodeAsNonGeneratedCode ? null : new Dictionary(); + _lazyGeneratedCodeSymbolsMap = _treatAllCodeAsNonGeneratedCode ? null : new Dictionary>(); _generatedCodeAttribute = analyzerExecutor.Compilation?.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute"); _symbolActionsByKind = MakeSymbolActionsByKind(); @@ -596,26 +602,32 @@ private bool IsInGeneratedCode(Location location, Compilation compilation, Cance return false; } + // Check if this is a generated code file by its extension. if (IsGeneratedCode(location.SourceTree)) { return true; } - if (_generatedCodeAttribute != null) + // Check if the file has generated code definitions (i.e. symbols with GeneratedCodeAttribute). + if (_generatedCodeAttribute != null && _lazyGeneratedCodeSymbolsMap != null) { - var model = compilation.GetSemanticModel(location.SourceTree); - for (var node = location.SourceTree.GetRoot(cancellationToken).FindNode(location.SourceSpan, getInnermostNodeForTie: true); - node != null; - node = node.Parent) + var generatedCodeSymbolsInTree = GetOrComputeGeneratedCodeSymbolsInTree(location.SourceTree, compilation, cancellationToken); + if (generatedCodeSymbolsInTree.Count > 0) { - var declaredSymbols = model.GetDeclaredSymbolsForNode(node, cancellationToken); - Debug.Assert(declaredSymbols != null); - - foreach (var symbol in declaredSymbols) + var model = compilation.GetSemanticModel(location.SourceTree); + for (var node = location.SourceTree.GetRoot(cancellationToken).FindNode(location.SourceSpan, getInnermostNodeForTie: true); + node != null; + node = node.Parent) { - if (GeneratedCodeUtilities.IsGeneratedSymbolWithGeneratedCodeAttribute(symbol, _generatedCodeAttribute)) + var declaredSymbols = model.GetDeclaredSymbolsForNode(node, cancellationToken); + Debug.Assert(declaredSymbols != null); + + foreach (var symbol in declaredSymbols) { - return true; + if (generatedCodeSymbolsInTree.Contains(symbol)) + { + return true; + } } } } @@ -624,6 +636,67 @@ private bool IsInGeneratedCode(Location location, Compilation compilation, Cance return false; } + private ImmutableHashSet GetOrComputeGeneratedCodeSymbolsInTree(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken) + { + Debug.Assert(_lazyGeneratedCodeSymbolsMap != null); + + ImmutableHashSet generatedCodeSymbols; + lock (_lazyGeneratedCodeSymbolsMap) + { + if (_lazyGeneratedCodeSymbolsMap.TryGetValue(tree, out generatedCodeSymbols)) + { + return generatedCodeSymbols; + } + } + + generatedCodeSymbols = ComputeGeneratedCodeSymbolsInTree(tree, compilation, cancellationToken); + + lock (_lazyGeneratedCodeSymbolsMap) + { + ImmutableHashSet existingGeneratedCodeSymbols; + if (!_lazyGeneratedCodeSymbolsMap.TryGetValue(tree, out existingGeneratedCodeSymbols)) + { + _lazyGeneratedCodeSymbolsMap.Add(tree, generatedCodeSymbols); + } + else + { + Debug.Assert(existingGeneratedCodeSymbols.SetEquals(generatedCodeSymbols)); + } + } + + return generatedCodeSymbols; + } + + private ImmutableHashSet ComputeGeneratedCodeSymbolsInTree(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken) + { + // PERF: Bail out early if file doesn't have "GeneratedCode" text. + var text = tree.GetText(cancellationToken).ToString(); + if (!text.Contains("GeneratedCode")) + { + return ImmutableHashSet.Empty; + } + + var model = compilation.GetSemanticModel(tree); + var root = tree.GetRoot(cancellationToken); + var span = root.FullSpan; + var builder = new List(); + model.ComputeDeclarationsInSpan(span, getSymbol: true, builder: builder, cancellationToken: cancellationToken); + + ImmutableHashSet.Builder generatedSymbolsBuilderOpt = null; + foreach (var declarationInfo in builder) + { + var symbol = declarationInfo.DeclaredSymbol; + if (symbol != null && + GeneratedCodeUtilities.IsGeneratedSymbolWithGeneratedCodeAttribute(symbol, _generatedCodeAttribute)) + { + generatedSymbolsBuilderOpt = generatedSymbolsBuilderOpt ?? ImmutableHashSet.CreateBuilder(); + generatedSymbolsBuilderOpt.Add(symbol); + } + } + + return generatedSymbolsBuilderOpt != null ? generatedSymbolsBuilderOpt.ToImmutable() : ImmutableHashSet.Empty; + } + /// /// Return a task that completes when the driver is initialized. /// diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index cc302d909f53b..e4c9f3da54715 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Collections; -using Microsoft.CodeAnalysis.Semantics; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -327,7 +326,7 @@ private void ExecuteCompilationActionsCore(ImmutableArray, CodeBlockAnalyzerAction, SyntaxNodeAnalyzerAction, SyntaxNodeAnalyzerStateData, SyntaxNode, TLanguageKindEnum>( codeBlockStartActions, codeBlockActions, codeBlockEndActions, analyzer, declaredNode, declaredSymbol, executableCodeBlocks, (codeBlocks) => codeBlocks.SelectMany(cb => cb.DescendantNodesAndSelf()), - semanticModel, getKind, analyzerStateOpt?.CodeBlockAnalysisState, isGeneratedCode); + semanticModel, getKind, analyzerStateOpt?.CodeBlockAnalysisState); } } finally @@ -595,7 +594,7 @@ private void ExecuteCompilationActionsCore(ImmutableArray( operationBlockStartActions, operationBlockActions, operationBlockEndActions, analyzer, declaredNode, declaredSymbol, operationBlocks, (blocks) => operations, semanticModel, - null, analyzerStateOpt?.OperationBlockAnalysisState, isGeneratedCode); + null, analyzerStateOpt?.OperationBlockAnalysisState); } } finally @@ -615,8 +614,7 @@ private void ExecuteCompilationActionsCore(ImmutableArray, IEnumerable> getNodesToAnalyze, SemanticModel semanticModel, Func getKind, - AnalysisState.BlockAnalyzerStateData analyzerStateOpt, - bool isGeneratedCode) + AnalysisState.BlockAnalyzerStateData analyzerStateOpt) where TLanguageKindEnum : struct where TBlockStartAction : AnalyzerAction where TBlockAction : AnalyzerAction @@ -654,7 +652,7 @@ private void ExecuteCompilationActionsCore(ImmutableArray getKind, TextSpan filterSpan, - SyntaxNodeAnalyzerStateData analyzerStateOpt, - bool isGeneratedCode) + SyntaxNodeAnalyzerStateData analyzerStateOpt) where TLanguageKindEnum : struct { - var addDiagnostic = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false, isGeneratedCode: isGeneratedCode); + var addDiagnostic = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false); ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, containingSymbol, model, getKind, addDiagnostic, analyzerStateOpt); } @@ -957,7 +954,7 @@ private void ExecuteCompilationActionsCore(ImmutableArray GetAddDiagnostic(ISymbol contextSymbol, ImmutableArray cachedDeclaringReferences, DiagnosticAnalyzer analyzer, Func getTopMostNodeForAnalysis, bool isGeneratedCodeSymbol) + private Action GetAddDiagnostic(ISymbol contextSymbol, ImmutableArray cachedDeclaringReferences, DiagnosticAnalyzer analyzer, Func getTopMostNodeForAnalysis) { - return GetAddDiagnostic(contextSymbol, cachedDeclaringReferences, _compilation, analyzer, isGeneratedCodeSymbol, _addNonCategorizedDiagnosticOpt, + return GetAddDiagnostic(contextSymbol, cachedDeclaringReferences, _compilation, analyzer, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, getTopMostNodeForAnalysis, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } @@ -1228,7 +1224,6 @@ private Action GetAddDiagnostic(ISymbol contextSymbol, ImmutableArra ImmutableArray cachedDeclaringReferences, Compilation compilation, DiagnosticAnalyzer analyzer, - bool isGeneratedCodeSymbol, Action addNonCategorizedDiagnosticOpt, Action addCategorizedLocalDiagnosticOpt, Action addCategorizedNonLocalDiagnosticOpt, @@ -1238,7 +1233,7 @@ private Action GetAddDiagnostic(ISymbol contextSymbol, ImmutableArra { return diagnostic => { - if (isGeneratedCodeSymbol && shouldSuppressGeneratedCodeDiagnostic(diagnostic, analyzer, compilation, cancellationToken)) + if (shouldSuppressGeneratedCodeDiagnostic(diagnostic, analyzer, compilation, cancellationToken)) { return; } @@ -1275,27 +1270,34 @@ private Action GetAddDiagnostic(ISymbol contextSymbol, ImmutableArra private Action GetAddCompilationDiagnostic(DiagnosticAnalyzer analyzer) { - if (_addCategorizedNonLocalDiagnosticOpt == null) - { - return _addNonCategorizedDiagnosticOpt; - } - return diagnostic => { + if (_shouldSuppressGeneratedCodeDiagnostic(diagnostic, analyzer, _compilation, _cancellationToken)) + { + return; + } + + if (_addCategorizedNonLocalDiagnosticOpt == null) + { + Debug.Assert(_addNonCategorizedDiagnosticOpt != null); + _addNonCategorizedDiagnosticOpt(diagnostic); + return; + } + _addCategorizedNonLocalDiagnosticOpt(diagnostic, analyzer); }; } - private Action GetAddDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic, bool isGeneratedCode) + private Action GetAddDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) { - return GetAddDiagnostic(tree, null, _compilation, analyzer, isSyntaxDiagnostic, isGeneratedCode, + return GetAddDiagnostic(tree, null, _compilation, analyzer, isSyntaxDiagnostic, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } - private Action GetAddDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic, bool isGeneratedCode) + private Action GetAddDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) { - return GetAddDiagnostic(tree, span, _compilation, analyzer, false, isGeneratedCode, + return GetAddDiagnostic(tree, span, _compilation, analyzer, false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } @@ -1306,7 +1308,6 @@ private Action GetAddDiagnostic(SyntaxTree tree, TextSpan? span, Dia Compilation compilation, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic, - bool isGeneratedCode, Action addNonCategorizedDiagnosticOpt, Action addCategorizedLocalDiagnosticOpt, Action addCategorizedNonLocalDiagnosticOpt, @@ -1315,7 +1316,7 @@ private Action GetAddDiagnostic(SyntaxTree tree, TextSpan? span, Dia { return diagnostic => { - if (isGeneratedCode && shouldSuppressGeneratedCodeDiagnostic(diagnostic, analyzer, compilation, cancellationToken)) + if (shouldSuppressGeneratedCodeDiagnostic(diagnostic, analyzer, compilation, cancellationToken)) { return; } diff --git a/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs b/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs index 67ee8fd7075b7..08a9bd4c2c37f 100644 --- a/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs +++ b/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs @@ -843,6 +843,40 @@ private void ReportDiagnosticsCore(Action addDiagnostic, Location lo } } + [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 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(); + 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 {