From 450cf6a8563736bfb0e6ec115c9789dc81a5ca3c Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Wed, 10 Jun 2020 16:56:37 -0700 Subject: [PATCH 01/11] Add analyzer action for reporting diagnostics in additional files Addresses #44131 The added analyzer APIs are similar to the existing APIs for syntax tree callback. This commit adds the compiler APIs + tests Future enhancement: Add analyzer action for reporting diagnostics in analyzer config files --- .../Test/CommandLine/CommandLineTests.cs | 24 +++ .../DiagnosticAnalyzerTests.AllInOne.cs | 4 +- .../Diagnostics/DiagnosticAnalyzerTests.cs | 130 +++++++++++++++ .../Diagnostics/AnalysisContextInfoTests.cs | 30 ++-- .../Core/Portable/CodeAnalysisResources.resx | 3 + .../Diagnostic/ExternalFileLocation.cs | 2 + .../DiagnosticAnalyzer/AnalysisContextInfo.cs | 48 ++++-- .../DiagnosticAnalyzer/AnalysisResult.cs | 61 +++---- .../AnalysisResultBuilder.cs | 146 +++++++++++++---- .../DiagnosticAnalyzer/AnalysisScope.cs | 74 +++++++-- .../AnalysisState.PerAnalyzerState.cs | 61 +++---- .../DiagnosticAnalyzer/AnalysisState.cs | 58 +++---- .../AnalyzerActionCounts.cs | 8 + .../DiagnosticAnalyzer/AnalyzerDriver.cs | 65 +++++++- ...yzerExecutor.AnalyzerDiagnosticReporter.cs | 32 +++- .../DiagnosticAnalyzer/AnalyzerExecutor.cs | 154 +++++++++++------- .../DiagnosticAnalyzer/AnalyzerTelemetry.cs | 6 + .../CompilationStartedEvent.cs | 19 ++- .../CompilationWithAnalyzers.cs | 92 ++++++++--- .../DiagnosticAnalysisContext.cs | 80 +++++++++ .../DiagnosticAnalyzerAction.cs | 11 ++ .../DiagnosticAnalyzer/DiagnosticQueue.cs | 2 +- .../DiagnosticStartAnalysisScope.cs | 35 ++++ .../SourceOrNonSourceFile.cs | 72 ++++++++ .../Core/Portable/PublicAPI.Unshipped.txt | 13 ++ .../Portable/xlf/CodeAnalysisResources.cs.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.de.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.es.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.fr.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.it.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.ja.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.ko.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.pl.xlf | 5 + .../xlf/CodeAnalysisResources.pt-BR.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.ru.xlf | 5 + .../Portable/xlf/CodeAnalysisResources.tr.xlf | 5 + .../xlf/CodeAnalysisResources.zh-Hans.xlf | 5 + .../xlf/CodeAnalysisResources.zh-Hant.xlf | 5 + .../Test/CommandLine/CommandLineTests.vb | 25 +++ .../DiagnosticAnalyzerTests.AllInOne.vb | 3 +- .../Diagnostics/DiagnosticAnalyzerTests.vb | 37 +++++ .../Diagnostics/CommonDiagnosticAnalyzers.cs | 49 ++++++ .../Diagnostics/TestDiagnosticAnalyzer.cs | 15 +- .../Portable/Mocks/TestAdditionalText.cs | 6 + 44 files changed, 1151 insertions(+), 279 deletions(-) create mode 100644 src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 170f219d2963b..6cd84bf4900a9 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -12588,6 +12588,30 @@ class C Assert.True(options.TryGetValue("key3", out val)); Assert.Equal("value3", val); } + + [Theory, CombinatorialData] + public void TestAdditionalFileAnalyzer(bool registerFromInitialize) + { + var srcDirectory = Temp.CreateDirectory(); + + var source = "class C { }"; + var srcFile = srcDirectory.CreateFile("a.cs"); + srcFile.WriteAllText(source); + + var additionalText = "Additional Text"; + var additionalFile = srcDirectory.CreateFile("b.txt"); + additionalFile.WriteAllText(additionalText); + + var diagnosticSpan = new TextSpan(2, 2); + var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan); + + var output = VerifyOutput(srcDirectory, srcFile, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false, + additionalFlags: new[] { "/additionalfile:" + additionalFile.Path }, + analyzers: analyzer); + Assert.Contains("b.txt(1,3): warning ID0001", output, StringComparison.Ordinal); + + CleanupAllGeneratedFiles(srcDirectory.Path); + } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs index 3d33e6d626ea2..3ccc91692fde1 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs @@ -94,8 +94,10 @@ public class C public void AnalyzerDriverIsSafeAgainstAnalyzerExceptions() { var compilation = CreateCompilationWithMscorlib45(TestResource.AllInOneCSharpCode); + var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray()); + ThrowingDiagnosticAnalyzer.VerifyAnalyzerEngineIsSafeAgainstExceptions(analyzer => - compilation.GetAnalyzerDiagnostics(new[] { analyzer }, null)); + compilation.GetAnalyzerDiagnostics(new[] { analyzer }, options)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index 4ed91e853af98..73f888abc2186 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Cci; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Diagnostics; @@ -17,6 +18,7 @@ using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -3518,5 +3520,133 @@ public B(int a) { } Diagnostic("ID0001", "B").WithLocation(7, 12) }); } + + [Theory, CombinatorialData] + public async Task TestAdditionalFileAnalyzer(bool registerFromInitialize) + { + var tree = CSharpSyntaxTree.ParseText(string.Empty); + var compilation = CreateCompilationWithMscorlib45(new[] { tree }); + compilation.VerifyDiagnostics(); + + AdditionalText additionalFile = new TestAdditionalText("Additional File Text"); + var options = new AnalyzerOptions(ImmutableArray.Create(additionalFile)); + var diagnosticSpan = new TextSpan(2, 2); + var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan); + var analyzers = ImmutableArray.Create(analyzer); + + var diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None); + verifyDiagnostics(diagnostics); + + diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile, CancellationToken.None); + verifyDiagnostics(diagnostics); + + var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None); + verifyDiagnostics(analysisResult.GetAllDiagnostics()); + verifyDiagnostics(analysisResult.NonSourceFileDiagnostics[additionalFile][analyzer]); + + void verifyDiagnostics(ImmutableArray diagnostics) + { + var diagnostic = Assert.Single(diagnostics); + Assert.Equal(analyzer.Descriptor.Id, diagnostic.Id); + Assert.Equal(LocationKind.ExternalFile, diagnostic.Location.Kind); + var location = (ExternalFileLocation)diagnostic.Location; + Assert.Equal(additionalFile.Path, location.FilePath); + Assert.Equal(diagnosticSpan, location.SourceSpan); + } + } + + [Theory, CombinatorialData] + public async Task TestMultipleAdditionalFileAnalyzers(bool registerFromInitialize, bool additionalFilesHaveSamePaths, bool firstAdditionalFileHasNullPath) + { + var tree = CSharpSyntaxTree.ParseText(string.Empty); + var compilation = CreateCompilationWithMscorlib45(new[] { tree }); + compilation.VerifyDiagnostics(); + + var path1 = firstAdditionalFileHasNullPath ? null : @"c:\file.txt"; + var path2 = additionalFilesHaveSamePaths ? path1 : @"file2.txt"; + + AdditionalText additionalFile1 = new TestAdditionalText("Additional File1 Text", path: path1); + AdditionalText additionalFile2 = new TestAdditionalText("Additional File2 Text", path: path2); + var additionalFiles = ImmutableArray.Create(additionalFile1, additionalFile2); + var options = new AnalyzerOptions(additionalFiles); + + var diagnosticSpan = new TextSpan(2, 2); + var analyzer1 = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0001"); + var analyzer2 = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0002"); + var analyzers = ImmutableArray.Create(analyzer1, analyzer2); + + var diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None); + verifyDiagnostics(diagnostics, analyzers, additionalFiles); + + diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile1, CancellationToken.None); + verifyDiagnostics(diagnostics, analyzers, ImmutableArray.Create(additionalFile1)); + diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile2, CancellationToken.None); + verifyDiagnostics(diagnostics, analyzers, ImmutableArray.Create(additionalFile2)); + + var singleAnalyzerArray = ImmutableArray.Create(analyzer1); + diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile1, singleAnalyzerArray, CancellationToken.None); + verifyDiagnostics(diagnostics, singleAnalyzerArray, ImmutableArray.Create(additionalFile1)); + diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile2, singleAnalyzerArray, CancellationToken.None); + verifyDiagnostics(diagnostics, singleAnalyzerArray, ImmutableArray.Create(additionalFile2)); + + var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None); + verifyDiagnostics(analysisResult.GetAllDiagnostics(), analyzers, additionalFiles); + + if (!additionalFilesHaveSamePaths) + { + verifyDiagnostics(getReportedDiagnostics(analysisResult, analyzer1, additionalFile1), singleAnalyzerArray, ImmutableArray.Create(additionalFile1)); + verifyDiagnostics(getReportedDiagnostics(analysisResult, analyzer1, additionalFile2), singleAnalyzerArray, ImmutableArray.Create(additionalFile2)); + singleAnalyzerArray = ImmutableArray.Create(analyzer2); + verifyDiagnostics(getReportedDiagnostics(analysisResult, analyzer2, additionalFile1), singleAnalyzerArray, ImmutableArray.Create(additionalFile1)); + verifyDiagnostics(getReportedDiagnostics(analysisResult, analyzer2, additionalFile2), singleAnalyzerArray, ImmutableArray.Create(additionalFile2)); + } + + static ImmutableArray getReportedDiagnostics(AnalysisResult analysisResult, DiagnosticAnalyzer analyzer, AdditionalText additionalFile) + { + if (analysisResult.NonSourceFileDiagnostics.TryGetValue(additionalFile, out var diagnosticsMap) && + diagnosticsMap.TryGetValue(analyzer, out var diagnostics)) + { + return diagnostics; + } + + return ImmutableArray.Empty; + } + + void verifyDiagnostics(ImmutableArray diagnostics, ImmutableArray analyzers, ImmutableArray additionalFiles) + { + foreach (AdditionalFileAnalyzer analyzer in analyzers) + { + var fileIndex = 0; + foreach (var additionalFile in additionalFiles) + { + var applicableDiagnostics = diagnostics.WhereAsArray( + d => d.Id == analyzer.Descriptor.Id && PathUtilities.Comparer.Equals(d.Location.GetLineSpan().Path, additionalFile.Path)); + if (additionalFile.Path == null) + { + Assert.Empty(applicableDiagnostics); + continue; + } + + var expectedCount = additionalFilesHaveSamePaths ? additionalFiles.Length : 1; + Assert.Equal(expectedCount, applicableDiagnostics.Length); + + foreach (var diagnostic in applicableDiagnostics) + { + Assert.Equal(LocationKind.ExternalFile, diagnostic.Location.Kind); + var location = (ExternalFileLocation)diagnostic.Location; + Assert.Equal(diagnosticSpan, location.SourceSpan); + } + + fileIndex++; + if (!additionalFilesHaveSamePaths || fileIndex == additionalFiles.Length) + { + diagnostics = diagnostics.RemoveRange(applicableDiagnostics); + } + } + } + + Assert.Empty(diagnostics); + } + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs index ccd581021f27a..39ac477be413d 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -23,23 +24,25 @@ public void InitializeTest() var parseOptions = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.None) .WithFeatures(new[] { new KeyValuePair("IOperation", "true") }); var compilation = CreateCompilation(code, parseOptions: parseOptions); - - Verify(compilation, nameof(AnalysisContext.RegisterCodeBlockAction)); - Verify(compilation, nameof(AnalysisContext.RegisterCodeBlockStartAction)); - Verify(compilation, nameof(AnalysisContext.RegisterCompilationAction)); - Verify(compilation, nameof(AnalysisContext.RegisterCompilationStartAction)); - Verify(compilation, nameof(AnalysisContext.RegisterOperationAction)); - Verify(compilation, nameof(AnalysisContext.RegisterOperationBlockAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSemanticModelAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSymbolAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSyntaxNodeAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSyntaxTreeAction)); + var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray()); + + Verify(compilation, options, nameof(AnalysisContext.RegisterCodeBlockAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterCodeBlockStartAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterCompilationAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterCompilationStartAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterOperationAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterOperationBlockAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSemanticModelAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSymbolAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSyntaxNodeAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSyntaxTreeAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterAdditionalFileAction)); } - private static void Verify(Compilation compilation, string context) + private static void Verify(Compilation compilation, AnalyzerOptions options, string context) { var analyzer = new Analyzer(s => context == s); - var diagnostics = compilation.GetAnalyzerDiagnostics(new DiagnosticAnalyzer[] { analyzer }); + var diagnostics = compilation.GetAnalyzerDiagnostics(new DiagnosticAnalyzer[] { analyzer }, options); Assert.Equal(1, diagnostics.Length); Assert.True(diagnostics[0].Descriptor.Description.ToString().IndexOf(analyzer.Info.GetContext()) >= 0); @@ -74,6 +77,7 @@ public override void Initialize(AnalysisContext c) c.RegisterSymbolAction(b => ThrowIfMatch(nameof(c.RegisterSymbolAction), new AnalysisContextInfo(b.Compilation, b.Symbol)), SymbolKind.NamedType); c.RegisterSyntaxNodeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxNodeAction), new AnalysisContextInfo(b.SemanticModel.Compilation, b.Node)), SyntaxKind.ReturnStatement); c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, b.Tree))); + c.RegisterAdditionalFileAction(b => ThrowIfMatch(nameof(c.RegisterAdditionalFileAction), new AnalysisContextInfo(b.Compilation, b.AdditionalFile))); } private void ThrowIfMatch(string context, AnalysisContextInfo info) diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index 518e8d23ca413..d115074cfea40 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -556,6 +556,9 @@ Syntax tree doesn't belong to the underlying 'Compilation'. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Resource stream ended at {0} bytes, expected {1} bytes. diff --git a/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs b/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs index 2e96c659b577b..21401827908ae 100644 --- a/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs +++ b/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs @@ -32,6 +32,8 @@ public override TextSpan SourceSpan } } + public string FilePath => _lineSpan.Path; + public override FileLinePositionSpan GetLineSpan() { return _lineSpan; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs index 51ade1a134f6f..fdd919339c368 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable + using System.Text; -using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -12,14 +13,15 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal struct AnalysisContextInfo { - private readonly Compilation _compilation; - private readonly IOperation _operation; - private readonly ISymbol _symbol; - private readonly SyntaxTree _tree; - private readonly SyntaxNode _node; + private readonly Compilation? _compilation; + private readonly IOperation? _operation; + private readonly ISymbol? _symbol; + private readonly SyntaxTree? _tree; + private readonly AdditionalText? _nonSourceFile; + private readonly SyntaxNode? _node; public AnalysisContextInfo(Compilation compilation) : - this(compilation: compilation, operation: null, symbol: null, tree: null, node: null) + this(compilation: compilation, operation: null, symbol: null, tree: null, node: null, nonSourceFile: null) { } @@ -29,41 +31,48 @@ public AnalysisContextInfo(SemanticModel model) : } public AnalysisContextInfo(Compilation compilation, ISymbol symbol) : - this(compilation: compilation, operation: null, symbol: symbol, tree: null, node: null) + this(compilation: compilation, operation: null, symbol: symbol, tree: null, node: null, nonSourceFile: null) { } public AnalysisContextInfo(Compilation compilation, SyntaxTree tree) : - this(compilation: compilation, operation: null, symbol: null, tree: tree, node: null) + this(compilation: compilation, operation: null, symbol: null, tree: tree, node: null, nonSourceFile: null) + { + } + + public AnalysisContextInfo(Compilation compilation, AdditionalText nonSourceFile) : + this(compilation: compilation, operation: null, symbol: null, tree: null, node: null, nonSourceFile) { } public AnalysisContextInfo(Compilation compilation, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: null, tree: node.SyntaxTree, node: node) + this(compilation: compilation, operation: null, symbol: null, tree: node.SyntaxTree, node, nonSourceFile: null) { } public AnalysisContextInfo(Compilation compilation, IOperation operation) : - this(compilation: compilation, operation: operation, symbol: null, tree: operation.Syntax.SyntaxTree, node: operation.Syntax) + this(compilation: compilation, operation: operation, symbol: null, tree: operation.Syntax.SyntaxTree, node: operation.Syntax, nonSourceFile: null) { } public AnalysisContextInfo(Compilation compilation, ISymbol symbol, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: symbol, tree: node.SyntaxTree, node: node) + this(compilation: compilation, operation: null, symbol: symbol, tree: node.SyntaxTree, node, nonSourceFile: null) { } public AnalysisContextInfo( - Compilation compilation, - IOperation operation, - ISymbol symbol, - SyntaxTree tree, - SyntaxNode node) + Compilation? compilation, + IOperation? operation, + ISymbol? symbol, + SyntaxTree? tree, + SyntaxNode? node, + AdditionalText? nonSourceFile) { _compilation = compilation; _operation = operation; _symbol = symbol; _tree = tree; + _nonSourceFile = nonSourceFile; _node = node; } @@ -91,6 +100,11 @@ public string GetContext() sb.AppendLine($"{nameof(SyntaxTree)}: {_tree.FilePath}"); } + if (_nonSourceFile?.Path != null) + { + sb.AppendLine($"{nameof(AdditionalText)}: {_nonSourceFile.Path}"); + } + if (_node != null) { var text = _tree?.GetText(); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs index 8e7f6a12b8510..15026fbaf1a19 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs @@ -24,12 +24,14 @@ internal AnalysisResult( ImmutableArray analyzers, ImmutableDictionary>> localSyntaxDiagnostics, ImmutableDictionary>> localSemanticDiagnostics, + ImmutableDictionary>> localNonSourceFileDiagnostics, ImmutableDictionary> nonLocalDiagnostics, ImmutableDictionary analyzerTelemetryInfo) { Analyzers = analyzers; SyntaxDiagnostics = localSyntaxDiagnostics; SemanticDiagnostics = localSemanticDiagnostics; + NonSourceFileDiagnostics = localNonSourceFileDiagnostics; CompilationDiagnostics = nonLocalDiagnostics; AnalyzerTelemetryInfo = analyzerTelemetryInfo; } @@ -37,27 +39,32 @@ internal AnalysisResult( /// /// Analyzers corresponding to this analysis result. /// - public ImmutableArray Analyzers { get; private set; } + public ImmutableArray Analyzers { get; } /// /// Syntax diagnostics reported by the . /// - public ImmutableDictionary>> SyntaxDiagnostics { get; private set; } + public ImmutableDictionary>> SyntaxDiagnostics { get; } /// /// Semantic diagnostics reported by the . /// - public ImmutableDictionary>> SemanticDiagnostics { get; private set; } + public ImmutableDictionary>> SemanticDiagnostics { get; } + + /// + /// Diagnostics in non-source files reported by the . + /// + public ImmutableDictionary>> NonSourceFileDiagnostics { get; } /// /// Compilation diagnostics reported by the . /// - public ImmutableDictionary> CompilationDiagnostics { get; private set; } + public ImmutableDictionary> CompilationDiagnostics { get; } /// /// Analyzer telemetry info (register action counts and execution times). /// - public ImmutableDictionary AnalyzerTelemetryInfo { get; private set; } + public ImmutableDictionary AnalyzerTelemetryInfo { get; } /// /// Gets all the diagnostics reported by the given . @@ -69,7 +76,7 @@ public ImmutableArray GetAllDiagnostics(DiagnosticAnalyzer analyzer) throw new ArgumentException(CodeAnalysisResources.UnsupportedAnalyzerInstance, nameof(analyzer)); } - return GetDiagnostics(SpecializedCollections.SingletonEnumerable(analyzer), getLocalSyntaxDiagnostics: true, getLocalSemanticDiagnostics: true, getNonLocalDiagnostics: true); + return GetDiagnostics(SpecializedCollections.SingletonEnumerable(analyzer)); } /// @@ -77,45 +84,25 @@ public ImmutableArray GetAllDiagnostics(DiagnosticAnalyzer analyzer) /// public ImmutableArray GetAllDiagnostics() { - return GetDiagnostics(Analyzers, getLocalSyntaxDiagnostics: true, getLocalSemanticDiagnostics: true, getNonLocalDiagnostics: true); - } - - internal ImmutableArray GetSyntaxDiagnostics(ImmutableArray analyzers) - { - return GetDiagnostics(analyzers, getLocalSyntaxDiagnostics: true, getLocalSemanticDiagnostics: false, getNonLocalDiagnostics: false); - } - - internal ImmutableArray GetSemanticDiagnostics(ImmutableArray analyzers) - { - return GetDiagnostics(analyzers, getLocalSyntaxDiagnostics: false, getLocalSemanticDiagnostics: true, getNonLocalDiagnostics: false); + return GetDiagnostics(Analyzers); } - internal ImmutableArray GetDiagnostics(IEnumerable analyzers, bool getLocalSyntaxDiagnostics, bool getLocalSemanticDiagnostics, bool getNonLocalDiagnostics) + private ImmutableArray GetDiagnostics(IEnumerable analyzers) { var excludedAnalyzers = Analyzers.Except(analyzers); var excludedAnalyzersSet = excludedAnalyzers.Any() ? excludedAnalyzers.ToImmutableHashSet() : ImmutableHashSet.Empty; - return GetDiagnostics(excludedAnalyzersSet, getLocalSyntaxDiagnostics, getLocalSemanticDiagnostics, getNonLocalDiagnostics); + return GetDiagnostics(excludedAnalyzersSet); } - private ImmutableArray GetDiagnostics(ImmutableHashSet excludedAnalyzers, bool getLocalSyntaxDiagnostics, bool getLocalSemanticDiagnostics, bool getNonLocalDiagnostics) + private ImmutableArray GetDiagnostics(ImmutableHashSet excludedAnalyzers) { - if (SyntaxDiagnostics.Count > 0 || SemanticDiagnostics.Count > 0 || CompilationDiagnostics.Count > 0) + if (SyntaxDiagnostics.Count > 0 || SemanticDiagnostics.Count > 0 || NonSourceFileDiagnostics.Count > 0 || CompilationDiagnostics.Count > 0) { var builder = ImmutableArray.CreateBuilder(); - if (getLocalSyntaxDiagnostics) - { - AddLocalDiagnostics(SyntaxDiagnostics, excludedAnalyzers, builder); - } - - if (getLocalSemanticDiagnostics) - { - AddLocalDiagnostics(SemanticDiagnostics, excludedAnalyzers, builder); - } - - if (getNonLocalDiagnostics) - { - AddNonLocalDiagnostics(CompilationDiagnostics, excludedAnalyzers, builder); - } + AddLocalDiagnostics(SyntaxDiagnostics, excludedAnalyzers, builder); + AddLocalDiagnostics(SemanticDiagnostics, excludedAnalyzers, builder); + AddLocalDiagnostics(NonSourceFileDiagnostics, excludedAnalyzers, builder); + AddNonLocalDiagnostics(CompilationDiagnostics, excludedAnalyzers, builder); return builder.ToImmutable(); } @@ -123,8 +110,8 @@ private ImmutableArray GetDiagnostics(ImmutableHashSet.Empty; } - private static void AddLocalDiagnostics( - ImmutableDictionary>> localDiagnostics, + private static void AddLocalDiagnostics( + ImmutableDictionary>> localDiagnostics, ImmutableHashSet excludedAnalyzers, ImmutableArray.Builder builder) { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs index 1576cd24a21bc..506ae0ec2db86 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -26,16 +27,19 @@ internal sealed class AnalysisResultBuilder private readonly Dictionary? _analyzerExecutionTimeOpt; private readonly HashSet _completedAnalyzers; private readonly Dictionary _analyzerActionCounts; + private readonly ImmutableDictionary> _pathToAdditionalTextMap; private Dictionary.Builder>>? _localSemanticDiagnosticsOpt = null; private Dictionary.Builder>>? _localSyntaxDiagnosticsOpt = null; + private Dictionary.Builder>>? _localNonSourceFileDiagnosticsOpt = null; private Dictionary.Builder>? _nonLocalDiagnosticsOpt = null; - internal AnalysisResultBuilder(bool logAnalyzerExecutionTime, ImmutableArray analyzers) + internal AnalysisResultBuilder(bool logAnalyzerExecutionTime, ImmutableArray analyzers, ImmutableArray nonSourceFiles) { _analyzerExecutionTimeOpt = logAnalyzerExecutionTime ? CreateAnalyzerExecutionTimeMap(analyzers) : null; _completedAnalyzers = new HashSet(); _analyzerActionCounts = new Dictionary(analyzers.Length); + _pathToAdditionalTextMap = CreatePathToAdditionalTextMap(nonSourceFiles); } private static Dictionary CreateAnalyzerExecutionTimeMap(ImmutableArray analyzers) @@ -49,6 +53,37 @@ private static Dictionary CreateAnalyzerExecutionT return map; } + private static ImmutableDictionary> CreatePathToAdditionalTextMap(ImmutableArray nonSourceFiles) + { + if (nonSourceFiles.IsEmpty) + { + return ImmutableDictionary>.Empty; + } + + var builder = ImmutableDictionary.CreateBuilder>(PathUtilities.Comparer); + foreach (var file in nonSourceFiles) + { + // Null file path for additional files is not possible from IDE or command line compiler host. + // However, it is possible from custom third party analysis hosts. + // Ensure we handle it gracefully + var path = file.Path ?? string.Empty; + + // Handle multiple additional files with same path. + if (builder.TryGetValue(path, out var value)) + { + value = value.Add(file); + } + else + { + value = new OneOrMany(file); + } + + builder[path] = value; + } + + return builder.ToImmutable(); + } + public TimeSpan GetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer) { Debug.Assert(_analyzerExecutionTimeOpt != null); @@ -79,7 +114,7 @@ internal ImmutableArray GetPendingAnalyzers(ImmutableArray getAnalyzerActionCounts, bool fullAnalysisResultForAnalyzersInScope) { - Debug.Assert(!fullAnalysisResultForAnalyzersInScope || analysisScope.FilterTreeOpt == null, "Full analysis result cannot come from partial (tree) analysis."); + Debug.Assert(!fullAnalysisResultForAnalyzersInScope || analysisScope.FilterFileOpt == null, "Full analysis result cannot come from partial (tree) analysis."); foreach (var analyzer in analysisScope.Analyzers) { @@ -98,8 +133,9 @@ internal void ApplySuppressionsAndStoreAnalysisResult(AnalysisScope analysisScop if (syntaxDiagnostics.Length > 0 || semanticDiagnostics.Length > 0 || compilationDiagnostics.Length > 0 || fullAnalysisResultForAnalyzersInScope) { - UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, ref _localSyntaxDiagnosticsOpt); - UpdateLocalDiagnostics_NoLock(analyzer, semanticDiagnostics, fullAnalysisResultForAnalyzersInScope, ref _localSemanticDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, getSourceTree, ref _localSyntaxDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, getAdditionalTextKey, ref _localNonSourceFileDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, semanticDiagnostics, fullAnalysisResultForAnalyzersInScope, getSourceTree, ref _localSemanticDiagnosticsOpt); UpdateNonLocalDiagnostics_NoLock(analyzer, compilationDiagnostics, fullAnalysisResultForAnalyzersInScope); } @@ -122,31 +158,65 @@ internal void ApplySuppressionsAndStoreAnalysisResult(AnalysisScope analysisScop } } } + + static SyntaxTree? getSourceTree(Diagnostic diagnostic) + => diagnostic.Location.SourceTree; + + AdditionalText? getAdditionalTextKey(Diagnostic diagnostic) + { + // Fetch the first additional file that matches diagnostic location. + if (diagnostic.Location is ExternalFileLocation externalFileLocation) + { + var path = externalFileLocation.FilePath; + if (_pathToAdditionalTextMap.TryGetValue(externalFileLocation.FilePath, out var additionalTexts)) + { + if (additionalTexts.Count == 1) + { + return additionalTexts[0]; + } + + foreach (var additionalText in additionalTexts) + { + if (analysisScope.NonSourceFiles.Contains(additionalText)) + { + return additionalText; + } + } + } + } + + return null; + } } - private void UpdateLocalDiagnostics_NoLock( + private void UpdateLocalDiagnostics_NoLock( DiagnosticAnalyzer analyzer, ImmutableArray diagnostics, bool overwrite, - ref Dictionary.Builder>>? lazyLocalDiagnostics) + Func getKey, + ref Dictionary.Builder>>? lazyLocalDiagnostics) + where TKey : class { if (diagnostics.IsEmpty) { return; } - lazyLocalDiagnostics = lazyLocalDiagnostics ?? new Dictionary.Builder>>(); + lazyLocalDiagnostics = lazyLocalDiagnostics ?? new Dictionary.Builder>>(); - foreach (var diagsByTree in diagnostics.GroupBy(d => d.Location.SourceTree)) + foreach (var diagsByKey in diagnostics.GroupBy(getKey)) { - var tree = diagsByTree.Key; - Debug.Assert(tree is object); + var key = diagsByKey.Key; + if (key is null) + { + continue; + } Dictionary.Builder>? allDiagnostics; - if (!lazyLocalDiagnostics.TryGetValue(tree, out allDiagnostics)) + if (!lazyLocalDiagnostics.TryGetValue(key, out allDiagnostics)) { allDiagnostics = new Dictionary.Builder>(); - lazyLocalDiagnostics[tree] = allDiagnostics; + lazyLocalDiagnostics[key] = allDiagnostics; } ImmutableArray.Builder? analyzerDiagnostics; @@ -161,7 +231,7 @@ private void UpdateLocalDiagnostics_NoLock( analyzerDiagnostics.Clear(); } - analyzerDiagnostics.AddRange(diagsByTree); + analyzerDiagnostics.AddRange(diagsByKey); } } @@ -208,10 +278,12 @@ private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisS { AddAllLocalDiagnostics_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); AddAllLocalDiagnostics_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder); + AddAllLocalDiagnostics_NoLock(_localNonSourceFileDiagnosticsOpt, analysisScope, builder); } else if (analysisScope.IsSyntaxOnlyTreeAnalysis) { AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); + AddLocalDiagnosticsForPartialAnalysis_NoLock(_localNonSourceFileDiagnosticsOpt, analysisScope, builder); } else { @@ -221,22 +293,23 @@ private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisS if (getNonLocalDiagnostics && _nonLocalDiagnosticsOpt != null) { - AddDiagnostics_NoLock(_nonLocalDiagnosticsOpt, analysisScope, builder); + AddDiagnostics_NoLock(_nonLocalDiagnosticsOpt, analysisScope.Analyzers, builder); } return builder.ToImmutableArray(); } - private static void AddAllLocalDiagnostics_NoLock( - Dictionary.Builder>>? localDiagnostics, + private static void AddAllLocalDiagnostics_NoLock( + Dictionary.Builder>>? localDiagnostics, AnalysisScope analysisScope, ImmutableArray.Builder builder) + where TKey : class { if (localDiagnostics != null) { foreach (var localDiagsByTree in localDiagnostics.Values) { - AddDiagnostics_NoLock(localDiagsByTree, analysisScope, builder); + AddDiagnostics_NoLock(localDiagsByTree, analysisScope.Analyzers, builder); } } } @@ -245,22 +318,36 @@ private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( Dictionary.Builder>>? localDiagnostics, AnalysisScope analysisScope, ImmutableArray.Builder builder) + => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.SourceTree, analysisScope.Analyzers, builder); + + private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( + Dictionary.Builder>>? localDiagnostics, + AnalysisScope analysisScope, + ImmutableArray.Builder builder) + => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.NonSourceFile, analysisScope.Analyzers, builder); + + private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( + Dictionary.Builder>>? localDiagnostics, + TKey? key, + ImmutableArray analyzers, + ImmutableArray.Builder builder) + where TKey : class { Dictionary.Builder>? diagnosticsForTree; - if (localDiagnostics != null && localDiagnostics.TryGetValue(analysisScope.FilterTreeOpt, out diagnosticsForTree)) + if (key != null && localDiagnostics != null && localDiagnostics.TryGetValue(key, out diagnosticsForTree)) { - AddDiagnostics_NoLock(diagnosticsForTree, analysisScope, builder); + AddDiagnostics_NoLock(diagnosticsForTree, analyzers, builder); } } private static void AddDiagnostics_NoLock( Dictionary.Builder> diagnostics, - AnalysisScope analysisScope, + ImmutableArray analyzers, ImmutableArray.Builder builder) { Debug.Assert(diagnostics != null); - foreach (var analyzer in analysisScope.Analyzers) + foreach (var analyzer in analyzers) { ImmutableArray.Builder? diagnosticsByAnalyzer; if (diagnostics.TryGetValue(analyzer, out diagnosticsByAnalyzer)) @@ -276,6 +363,7 @@ internal AnalysisResult ToAnalysisResult(ImmutableArray anal ImmutableDictionary>> localSyntaxDiagnostics; ImmutableDictionary>> localSemanticDiagnostics; + ImmutableDictionary>> localNonSourceFileDiagnostics; ImmutableDictionary> nonLocalDiagnostics; var analyzersSet = analyzers.ToImmutableHashSet(); @@ -283,29 +371,31 @@ internal AnalysisResult ToAnalysisResult(ImmutableArray anal { localSyntaxDiagnostics = GetImmutable(analyzersSet, _localSyntaxDiagnosticsOpt); localSemanticDiagnostics = GetImmutable(analyzersSet, _localSemanticDiagnosticsOpt); + localNonSourceFileDiagnostics = GetImmutable(analyzersSet, _localNonSourceFileDiagnosticsOpt); nonLocalDiagnostics = GetImmutable(analyzersSet, _nonLocalDiagnosticsOpt); } cancellationToken.ThrowIfCancellationRequested(); var analyzerTelemetryInfo = GetTelemetryInfo(analyzers); - return new AnalysisResult(analyzers, localSyntaxDiagnostics, localSemanticDiagnostics, nonLocalDiagnostics, analyzerTelemetryInfo); + return new AnalysisResult(analyzers, localSyntaxDiagnostics, localSemanticDiagnostics, localNonSourceFileDiagnostics, nonLocalDiagnostics, analyzerTelemetryInfo); } - private static ImmutableDictionary>> GetImmutable( + private static ImmutableDictionary>> GetImmutable( ImmutableHashSet analyzers, - Dictionary.Builder>>? localDiagnosticsOpt) + Dictionary.Builder>>? localDiagnosticsOpt) + where TKey : class { if (localDiagnosticsOpt == null) { - return ImmutableDictionary>>.Empty; + return ImmutableDictionary>>.Empty; } - var builder = ImmutableDictionary.CreateBuilder>>(); + var builder = ImmutableDictionary.CreateBuilder>>(); var perTreeBuilder = ImmutableDictionary.CreateBuilder>(); foreach (var diagnosticsByTree in localDiagnosticsOpt) { - var tree = diagnosticsByTree.Key; + var key = diagnosticsByTree.Key; foreach (var diagnosticsByAnalyzer in diagnosticsByTree.Value) { if (analyzers.Contains(diagnosticsByAnalyzer.Key)) @@ -314,7 +404,7 @@ private static ImmutableDictionary internal class AnalysisScope { - public SyntaxTree FilterTreeOpt { get; } + public SourceOrNonSourceFile? FilterFileOpt { get; } public TextSpan? FilterSpanOpt { get; } public ImmutableArray Analyzers { get; } @@ -28,6 +30,11 @@ internal class AnalysisScope /// public IEnumerable SyntaxTrees { get; } + /// + /// Non-source files on which we need to perform analysis. + /// + public IEnumerable NonSourceFiles { get; } + public bool ConcurrentAnalysis { get; } /// @@ -36,31 +43,35 @@ internal class AnalysisScope public bool CategorizeDiagnostics { get; } /// - /// True if we need to perform only syntax analysis for a single tree. + /// True if we need to perform only syntax analysis for a single tree or non-source file. /// public bool IsSyntaxOnlyTreeAnalysis { get; } /// - /// True if we need to perform analysis for a single tree. + /// True if we need to perform analysis for a single tree or non-source file. /// - public bool IsTreeAnalysis => FilterTreeOpt != null; + public bool IsTreeAnalysis => FilterFileOpt != null; - public AnalysisScope(Compilation compilation, ImmutableArray analyzers, bool concurrentAnalysis, bool categorizeDiagnostics) - : this(compilation.SyntaxTrees, analyzers, filterTreeOpt: null, filterSpanOpt: null, isSyntaxOnlyTreeAnalysis: false, concurrentAnalysis: concurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics) + public AnalysisScope(Compilation compilation, AnalyzerOptions? analyzerOptions, ImmutableArray analyzers, bool concurrentAnalysis, bool categorizeDiagnostics) + : this(compilation.SyntaxTrees, analyzerOptions?.AdditionalFiles ?? ImmutableArray.Empty, + analyzers, filterTreeOpt: null, filterSpanOpt: null, isSyntaxOnlyTreeAnalysis: false, concurrentAnalysis, categorizeDiagnostics) { } - public AnalysisScope(ImmutableArray analyzers, SyntaxTree filterTree, TextSpan? filterSpan, bool syntaxAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) - : this(SpecializedCollections.SingletonEnumerable(filterTree), analyzers, filterTree, filterSpan, syntaxAnalysis, concurrentAnalysis, categorizeDiagnostics) + public AnalysisScope(ImmutableArray analyzers, SourceOrNonSourceFile filterTree, TextSpan? filterSpan, bool syntaxAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + : this(filterTree?.SourceTree != null ? SpecializedCollections.SingletonEnumerable(filterTree.SourceTree) : SpecializedCollections.EmptyEnumerable(), + filterTree?.NonSourceFile != null ? SpecializedCollections.SingletonEnumerable(filterTree.NonSourceFile) : SpecializedCollections.EmptyEnumerable(), + analyzers, filterTree, filterSpan, syntaxAnalysis, concurrentAnalysis, categorizeDiagnostics) { Debug.Assert(filterTree != null); } - private AnalysisScope(IEnumerable trees, ImmutableArray analyzers, SyntaxTree filterTreeOpt, TextSpan? filterSpanOpt, bool isSyntaxOnlyTreeAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + private AnalysisScope(IEnumerable trees, IEnumerable nonSourceFiles, ImmutableArray analyzers, SourceOrNonSourceFile? filterTreeOpt, TextSpan? filterSpanOpt, bool isSyntaxOnlyTreeAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) { SyntaxTrees = trees; + NonSourceFiles = nonSourceFiles; Analyzers = analyzers; - FilterTreeOpt = filterTreeOpt; + FilterFileOpt = filterTreeOpt; FilterSpanOpt = filterSpanOpt; IsSyntaxOnlyTreeAnalysis = isSyntaxOnlyTreeAnalysis; ConcurrentAnalysis = concurrentAnalysis; @@ -69,7 +80,7 @@ private AnalysisScope(IEnumerable trees, ImmutableArray analyzers) { - return new AnalysisScope(SyntaxTrees, analyzers, FilterTreeOpt, FilterSpanOpt, IsSyntaxOnlyTreeAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); + return new AnalysisScope(SyntaxTrees, NonSourceFiles, analyzers, FilterFileOpt, FilterSpanOpt, IsSyntaxOnlyTreeAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); } public static bool ShouldSkipSymbolAnalysis(SymbolDeclaredCompilationEvent symbolEvent) @@ -87,19 +98,29 @@ public static bool ShouldSkipDeclarationAnalysis(ISymbol symbol) public bool ShouldAnalyze(SyntaxTree tree) { - return FilterTreeOpt == null || FilterTreeOpt == tree; + return FilterFileOpt == null || FilterFileOpt.SourceTree == tree; + } + + public bool ShouldAnalyze(AdditionalText file) + { + return FilterFileOpt == null || FilterFileOpt.NonSourceFile == file; } public bool ShouldAnalyze(ISymbol symbol) { - if (FilterTreeOpt == null) + if (FilterFileOpt == null) { return true; } + if (FilterFileOpt.SourceTree == null) + { + return false; + } + foreach (var location in symbol.Locations) { - if (location.SourceTree != null && FilterTreeOpt == location.SourceTree && ShouldInclude(location.SourceSpan)) + if (location.SourceTree != null && FilterFileOpt.SourceTree == location.SourceTree && ShouldInclude(location.SourceSpan)) { return true; } @@ -110,11 +131,16 @@ public bool ShouldAnalyze(ISymbol symbol) public bool ShouldAnalyze(SyntaxNode node) { - if (FilterTreeOpt == null) + if (FilterFileOpt == null) { return true; } + if (FilterFileOpt.SourceTree == null) + { + return false; + } + return ShouldInclude(node.FullSpan); } @@ -130,14 +156,26 @@ public bool ContainsSpan(TextSpan filterSpan) public bool ShouldInclude(Diagnostic diagnostic) { - if (FilterTreeOpt == null) + if (FilterFileOpt == null) { return true; } - if (!diagnostic.Location.IsInSource || diagnostic.Location.SourceTree != FilterTreeOpt) + if (diagnostic.Location.IsInSource) { - return false; + if (FilterFileOpt?.SourceTree == null || + diagnostic.Location.SourceTree != FilterFileOpt.SourceTree) + { + return false; + } + } + else if (diagnostic.Location is ExternalFileLocation externalFileLocation) + { + if (FilterFileOpt?.NonSourceFile == null || + !PathUtilities.Comparer.Equals(externalFileLocation.FilePath, FilterFileOpt.NonSourceFile.Path)) + { + return false; + } } return ShouldInclude(diagnostic.Location.SourceSpan); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs index 9d7319088fb54..3b761a7485d7b 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs @@ -23,8 +23,8 @@ private class PerAnalyzerState private readonly Dictionary _pendingSymbols = new Dictionary(); private readonly Dictionary> _pendingDeclarations = new Dictionary>(); - private Dictionary _lazySyntaxTreesWithAnalysisData = null; - private int _pendingSyntaxAnalysisTreesCount = 0; + private Dictionary _lazyFilesWithAnalysisData = null; + private int _pendingSyntaxAnalysisFilesCount = 0; private Dictionary _lazyPendingSymbolEndAnalyses = null; private readonly ObjectPool _analyzerStateDataPool; @@ -52,31 +52,31 @@ public void AddPendingEvents(HashSet uniqueEvents) } } - public bool HasPendingSyntaxAnalysis(SyntaxTree treeOpt) + public bool HasPendingSyntaxAnalysis(SourceOrNonSourceFile fileOpt) { lock (_gate) { - if (_pendingSyntaxAnalysisTreesCount == 0) + if (_pendingSyntaxAnalysisFilesCount == 0) { return false; } - Debug.Assert(_lazySyntaxTreesWithAnalysisData != null); + Debug.Assert(_lazyFilesWithAnalysisData != null); - if (treeOpt == null) + if (fileOpt == null) { - // We have syntax analysis pending for at least one tree. + // We have syntax analysis pending for at least one file. return true; } AnalyzerStateData state; - if (!_lazySyntaxTreesWithAnalysisData.TryGetValue(treeOpt, out state)) + if (!_lazyFilesWithAnalysisData.TryGetValue(fileOpt, out state)) { - // We haven't even started analysis for this tree. + // We haven't even started analysis for this file. return true; } - // See if we have completed analysis for this tree. + // See if we have completed analysis for this file. return state.StateKind == StateKind.FullyProcessed; } } @@ -143,15 +143,15 @@ private static bool MarkEntityProcessed_NoLock EnsureDeclarationDataMap_NoLock(ISymbol symbol, Dictionary declarationDataMap) @@ -408,20 +408,20 @@ public void MarkDeclarationsComplete(ISymbol symbol) } } - public bool TryStartSyntaxAnalysis(SyntaxTree tree, out AnalyzerStateData state) + public bool TryStartSyntaxAnalysis(SourceOrNonSourceFile tree, out AnalyzerStateData state) { lock (_gate) { - Debug.Assert(_lazySyntaxTreesWithAnalysisData != null); + Debug.Assert(_lazyFilesWithAnalysisData != null); return TryStartSyntaxAnalysis_NoLock(tree, out state); } } - public void MarkSyntaxAnalysisComplete(SyntaxTree tree) + public void MarkSyntaxAnalysisComplete(SourceOrNonSourceFile file) { lock (_gate) { - MarkSyntaxTreeProcessed_NoLock(tree); + MarkSyntaxAnalysisComplete_NoLock(file); } } @@ -461,12 +461,17 @@ public void OnCompilationEventGenerated(CompilationEvent compilationEvent, Analy return; } } - else if (compilationEvent is CompilationStartedEvent) + else if (compilationEvent is CompilationStartedEvent compilationStartedEvent) { - if (actionCounts.SyntaxTreeActionsCount > 0) + if (actionCounts.SyntaxTreeActionsCount > 0 || actionCounts.AdditionalFileActionsCount > 0) { - _lazySyntaxTreesWithAnalysisData = new Dictionary(); - _pendingSyntaxAnalysisTreesCount = compilationEvent.Compilation.SyntaxTrees.Count(); + var fileCount = actionCounts.SyntaxTreeActionsCount > 0 ? compilationEvent.Compilation.SyntaxTrees.Count() : 0; + fileCount += actionCounts.AdditionalFileActionsCount > 0 ? compilationStartedEvent.AdditionalFiles.Length : 0; + if (fileCount > 0) + { + _lazyFilesWithAnalysisData = new Dictionary(); + _pendingSyntaxAnalysisFilesCount = fileCount; + } } if (actionCounts.CompilationActionsCount == 0) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs index 921004cbd80d9..b18905a4159b5 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs @@ -107,7 +107,7 @@ public async Task OnCompilationEventsGeneratedAsync(ImmutableArray compilationEvents, SyntaxTree filterTreeOpt) + private void OnCompilationEventsGenerated_NoLock(ImmutableArray compilationEvents) { Debug.Assert(_lazyAnalyzerActionCountsMap != null); // Add the events to our global pending events map. - AddToEventsMap_NoLock(compilationEvents, filterTreeOpt); + AddToEventsMap_NoLock(compilationEvents); // Mark the events for analysis for each analyzer. ArrayBuilder newPartialSymbols = null; @@ -174,14 +174,8 @@ private void OnCompilationEventsGenerated_NoLock(ImmutableArray compilationEvents, SyntaxTree filterTreeOpt) + private void AddToEventsMap_NoLock(ImmutableArray compilationEvents) { - if (filterTreeOpt != null) - { - AddPendingSourceEvents_NoLock(compilationEvents, filterTreeOpt); - return; - } - foreach (var compilationEvent in compilationEvents) { UpdateEventsMap_NoLock(compilationEvent, add: true); @@ -245,20 +239,6 @@ private void UpdateEventsMap_NoLock(CompilationEvent compilationEvent, bool add) } } - private void AddPendingSourceEvents_NoLock(ImmutableArray compilationEvents, SyntaxTree tree) - { - HashSet currentEvents; - if (!_pendingSourceEvents.TryGetValue(tree, out currentEvents)) - { - currentEvents = new HashSet(compilationEvents); - _pendingSourceEvents[tree] = currentEvents; - _compilationData.RemoveCachedSemanticModel(tree); - return; - } - - currentEvents.AddAll(compilationEvents); - } - private void AddPendingSourceEvent_NoLock(SyntaxTree tree, CompilationEvent compilationEvent) { HashSet currentEvents; @@ -317,7 +297,8 @@ private static bool HasActionsForEvent(CompilationEvent compilationEvent, Analyz if (compilationEvent is CompilationStartedEvent) { return actionCounts.CompilationActionsCount > 0 || - actionCounts.SyntaxTreeActionsCount > 0; + actionCounts.SyntaxTreeActionsCount > 0 || + actionCounts.AdditionalFileActionsCount > 0; } else if (compilationEvent is CompilationCompletedEvent) { @@ -514,7 +495,7 @@ public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) foreach (var analyzer in analysisScope.Analyzers) { var analyzerState = GetAnalyzerState(analyzer); - if (analyzerState.HasPendingSyntaxAnalysis(analysisScope.FilterTreeOpt)) + if (analyzerState.HasPendingSyntaxAnalysis(analysisScope.FilterFileOpt)) { return true; } @@ -528,9 +509,10 @@ public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) /// public bool HasPendingSymbolAnalysis(AnalysisScope analysisScope, CancellationToken cancellationToken) { - Debug.Assert(analysisScope.FilterTreeOpt != null); + RoslynDebug.Assert(analysisScope.FilterFileOpt != null); + RoslynDebug.Assert(analysisScope.FilterFileOpt.SourceTree != null); - var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterTreeOpt, cancellationToken); + var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterFileOpt.SourceTree, cancellationToken); foreach (var symbolDeclaredEvent in symbolDeclaredEvents) { if (analysisScope.ShouldAnalyze(symbolDeclaredEvent.Symbol)) @@ -744,33 +726,33 @@ public void MarkDeclarationsComplete(ISymbol symbol, IEnumerable - /// Attempts to start processing a syntax tree for the given analyzer's syntax tree actions. + /// Attempts to start processing a syntax tree or additional file for the given analyzer's syntax tree or additional file actions respectively. /// /// - /// Returns false if the tree has already been processed for the analyzer OR is currently being processed by another task. + /// Returns false if the file has already been processed for the analyzer OR is currently being processed by another task. /// If true, then it returns a non-null representing partial syntax analysis state for the given tree for the given analyzer. /// - public bool TryStartSyntaxAnalysis(SyntaxTree tree, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) + public bool TryStartSyntaxAnalysis(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) { - return GetAnalyzerState(analyzer).TryStartSyntaxAnalysis(tree, out state); + return GetAnalyzerState(analyzer).TryStartSyntaxAnalysis(file, out state); } /// - /// Marks the given tree as fully syntactically analyzed for the given analyzer. + /// Marks the given file as fully syntactically analyzed for the given analyzer. /// - public void MarkSyntaxAnalysisComplete(SyntaxTree tree, DiagnosticAnalyzer analyzer) + public void MarkSyntaxAnalysisComplete(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer) { - GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree); + GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(file); } /// - /// Marks the given tree as fully syntactically analyzed for the given analyzers. + /// Marks the given file as fully syntactically analyzed for the given analyzers. /// - public void MarkSyntaxAnalysisComplete(SyntaxTree tree, IEnumerable analyzers) + public void MarkSyntaxAnalysisComplete(SourceOrNonSourceFile file, IEnumerable analyzers) { foreach (var analyzer in analyzers) { - GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree); + GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(file); } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs index 718855c120515..86821565806d5 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs @@ -17,6 +17,7 @@ internal AnalyzerActionCounts(in AnalyzerActions analyzerActions) : analyzerActions.CompilationEndActionsCount, analyzerActions.CompilationActionsCount, analyzerActions.SyntaxTreeActionsCount, + analyzerActions.AdditionalFileActionsCount, analyzerActions.SemanticModelActionsCount, analyzerActions.SymbolActionsCount, analyzerActions.SymbolStartActionsCount, @@ -38,6 +39,7 @@ internal AnalyzerActionCounts( int compilationEndActionsCount, int compilationActionsCount, int syntaxTreeActionsCount, + int additionalFileActionsCount, int semanticModelActionsCount, int symbolActionsCount, int symbolStartActionsCount, @@ -56,6 +58,7 @@ internal AnalyzerActionCounts( CompilationEndActionsCount = compilationEndActionsCount; CompilationActionsCount = compilationActionsCount; SyntaxTreeActionsCount = syntaxTreeActionsCount; + AdditionalFileActionsCount = additionalFileActionsCount; SemanticModelActionsCount = semanticModelActionsCount; SymbolActionsCount = symbolActionsCount; SymbolStartActionsCount = symbolStartActionsCount; @@ -99,6 +102,11 @@ internal AnalyzerActionCounts( /// public int SyntaxTreeActionsCount { get; } + /// + /// Count of registered additional file actions. + /// + public int AdditionalFileActionsCount { get; } + /// /// Count of registered semantic model actions. /// diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 0428eced223b0..323b1184d24b8 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -74,6 +74,7 @@ internal abstract partial class AnalyzerDriver : IDisposable private ImmutableDictionary>> _symbolActionsByKind; private ImmutableDictionary> _semanticModelActionsMap; private ImmutableDictionary> _syntaxTreeActionsMap; + private ImmutableDictionary> _additionalFileActionsMap; // Compilation actions and compilation end actions have separate maps so that it is easy to // execute the compilation actions before the compilation end actions. private ImmutableDictionary> _compilationActionsMap; @@ -246,6 +247,7 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn _symbolActionsByKind = MakeSymbolActionsByKind(in AnalyzerActions); _semanticModelActionsMap = MakeSemanticModelActionsByAnalyzer(in AnalyzerActions); _syntaxTreeActionsMap = MakeSyntaxTreeActionsByAnalyzer(in AnalyzerActions); + _additionalFileActionsMap = MakeAdditionalFileActionsByAnalyzer(in AnalyzerActions); _compilationActionsMap = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationActions); _compilationEndActionsMap = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationEndActions); @@ -556,6 +558,11 @@ private static void OnDriverException(Task faultedTask, AnalyzerExecutor analyze private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { + if (_syntaxTreeActionsMap.IsEmpty) + { + return; + } + if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) { // For partial analysis, only execute syntax tree actions if performing syntax analysis. @@ -565,9 +572,10 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState foreach (var tree in analysisScope.SyntaxTrees) { var isGeneratedCode = IsGeneratedCode(tree); + var file = SourceOrNonSourceFile.Create(tree); if (isGeneratedCode && DoNotAnalyzeGeneratedCode) { - analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analysisScope.Analyzers); + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analysisScope.Analyzers); continue; } @@ -579,11 +587,40 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState if (_syntaxTreeActionsMap.TryGetValue(analyzer, out syntaxTreeActions)) { // Execute actions for a given analyzer sequentially. - AnalyzerExecutor.TryExecuteSyntaxTreeActions(syntaxTreeActions, analyzer, tree, analysisScope, analysisStateOpt, isGeneratedCode); + AnalyzerExecutor.TryExecuteSyntaxTreeActions(syntaxTreeActions, analyzer, file, analysisScope, analysisStateOpt, isGeneratedCode); } else { - analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analyzer); + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analyzer); + } + } + } + } + + private void ExecuteAdditionalFileActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) + { + if (_additionalFileActionsMap.IsEmpty) + { + return; + } + + foreach (var additionalFile in analysisScope.NonSourceFiles) + { + var file = SourceOrNonSourceFile.Create(additionalFile); + + foreach (var analyzer in analysisScope.Analyzers) + { + cancellationToken.ThrowIfCancellationRequested(); + + ImmutableArray actions; + if (_additionalFileActionsMap.TryGetValue(analyzer, out actions)) + { + // Execute actions for a given analyzer sequentially. + AnalyzerExecutor.TryExecuteAdditionalFileActions(actions, analyzer, file, analysisScope, analysisStateOpt); + } + else + { + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analyzer); } } } @@ -644,7 +681,7 @@ internal static AnalyzerDriver CreateAndAttachToCompilation( var analysisOptions = new CompilationWithAnalyzersOptions(options, onAnalyzerException, analyzerExceptionFilter: analyzerExceptionFilter, concurrentAnalysis: true, logAnalyzerExecutionTime: reportAnalyzer, reportSuppressedDiagnostics: false); analyzerDriver.Initialize(newCompilation, analysisOptions, new CompilationData(newCompilation), categorizeDiagnostics, cancellationToken); - var analysisScope = new AnalysisScope(newCompilation, analyzers, concurrentAnalysis: newCompilation.Options.ConcurrentBuild, categorizeDiagnostics: categorizeDiagnostics); + var analysisScope = new AnalysisScope(newCompilation, options, analyzers, concurrentAnalysis: newCompilation.Options.ConcurrentBuild, categorizeDiagnostics: categorizeDiagnostics); analyzerDriver.AttachQueueAndStartProcessingEvents(newCompilation.EventQueue, analysisScope, cancellationToken: cancellationToken); return analyzerDriver; } @@ -1114,6 +1151,18 @@ private static ImmutableDictionary> MakeAdditionalFileActionsByAnalyzer(in AnalyzerActions analyzerActions) + { + var builder = ImmutableDictionary.CreateBuilder>(); + var actionsByAnalyzers = analyzerActions.AdditionalFileActions.GroupBy(action => action.Analyzer); + foreach (var analyzerAndActions in actionsByAnalyzers) + { + builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArray()); + } + + return builder.ToImmutable(); + } + private static ImmutableDictionary> MakeSemanticModelActionsByAnalyzer(in AnalyzerActions analyzerActions) { var builder = ImmutableDictionary.CreateBuilder>(); @@ -1163,8 +1212,11 @@ private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, An // Kick off tasks to execute syntax tree actions. var syntaxTreeActionsTask = Task.Run(() => ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt, cancellationToken), cancellationToken); + // Kick off tasks to execute additional file actions. + var additionalFileActionsTask = Task.Run(() => ExecuteAdditionalFileActions(analysisScope, analysisStateOpt, cancellationToken), cancellationToken); + // Wait for all worker threads to complete processing events. - await Task.WhenAll(workerTasks.Concat(syntaxTreeActionsTask)).ConfigureAwait(false); + await Task.WhenAll(workerTasks.Concat(syntaxTreeActionsTask).Concat(additionalFileActionsTask)).ConfigureAwait(false); for (int i = 0; i < workerCount; i++) { @@ -1180,6 +1232,7 @@ private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, An completedEvent = await ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken).ConfigureAwait(false); ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt, cancellationToken); + ExecuteAdditionalFileActions(analysisScope, analysisStateOpt, cancellationToken); } // Finally process the compilation completed event, if any. @@ -2167,7 +2220,7 @@ protected override bool TryExecuteDeclaringReferenceActions( cancellationToken.ThrowIfCancellationRequested(); var decl = declaringReferences[i]; - if (analysisScope.FilterTreeOpt != null && analysisScope.FilterTreeOpt != decl.SyntaxTree) + if (analysisScope.FilterFileOpt != null && analysisScope.FilterFileOpt?.SourceTree != decl.SyntaxTree) { continue; } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs index 65fc204402701..e00a8f91edbb4 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs @@ -5,9 +5,7 @@ #nullable enable using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; using System.Threading; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; @@ -29,7 +27,7 @@ private sealed class AnalyzerDiagnosticReporter new ObjectPool(() => new AnalyzerDiagnosticReporter(), 10); public static AnalyzerDiagnosticReporter GetInstance( - SyntaxTree contextTree, + SourceOrNonSourceFile contextFile, TextSpan? span, Compilation compilation, DiagnosticAnalyzer analyzer, @@ -41,7 +39,7 @@ public static AnalyzerDiagnosticReporter GetInstance( CancellationToken cancellationToken) { var item = s_objectPool.Allocate(); - item._contextTree = contextTree; + item._contextFile = contextFile; item._span = span; item._compilation = compilation; item._analyzer = analyzer; @@ -56,7 +54,7 @@ public static AnalyzerDiagnosticReporter GetInstance( public void Free() { - _contextTree = null!; + _contextFile = null!; _span = null; _compilation = null!; _analyzer = null!; @@ -69,7 +67,7 @@ public void Free() s_objectPool.Free(this); } - private SyntaxTree _contextTree; + private SourceOrNonSourceFile _contextFile; private TextSpan? _span; private Compilation _compilation; private DiagnosticAnalyzer _analyzer; @@ -105,8 +103,7 @@ private void AddDiagnostic(Diagnostic diagnostic) Debug.Assert(_addNonCategorizedDiagnosticOpt == null); RoslynDebug.Assert(_addCategorizedNonLocalDiagnosticOpt != null); - if (diagnostic.Location.IsInSource && - _contextTree == diagnostic.Location.SourceTree && + if (isLocalDiagnostic(diagnostic) && (!_span.HasValue || _span.Value.IntersectsWith(diagnostic.Location.SourceSpan))) { _addCategorizedLocalDiagnosticOpt(diagnostic, _analyzer, _isSyntaxDiagnostic); @@ -115,6 +112,25 @@ private void AddDiagnostic(Diagnostic diagnostic) { _addCategorizedNonLocalDiagnosticOpt(diagnostic, _analyzer); } + + return; + + bool isLocalDiagnostic(Diagnostic diagnostic) + { + if (diagnostic.Location.IsInSource) + { + return _contextFile?.SourceTree != null && + _contextFile.SourceTree == diagnostic.Location.SourceTree; + } + + if (diagnostic.Location is ExternalFileLocation externalFileLocation) + { + return _contextFile?.NonSourceFile != null && + PathUtilities.Comparer.Equals(_contextFile.NonSourceFile.Path, externalFileLocation.FilePath); + } + + return false; + } } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index 596f275c29ba1..148de79c2d1ec 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -674,7 +674,7 @@ private void ExecuteSemanticModelActionsCore( return; } - var diagReporter = GetAddDiagnostic(semanticModel.SyntaxTree, analyzer, isSyntaxDiagnostic: false); + var diagReporter = GetAddSemanticDiagnostic(semanticModel.SyntaxTree, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); @@ -706,7 +706,7 @@ private void ExecuteSemanticModelActionsCore( /// /// Syntax tree actions to be executed. /// Analyzer whose actions are to be executed. - /// Syntax tree to analyze. + /// Syntax tree to analyze. /// Scope for analyzer execution. /// An optional object to track analysis state. /// Flag indicating if the syntax tree being analyzed is generated code. @@ -717,19 +717,20 @@ private void ExecuteSemanticModelActionsCore( public bool TryExecuteSyntaxTreeActions( ImmutableArray syntaxTreeActions, DiagnosticAnalyzer analyzer, - SyntaxTree tree, + SourceOrNonSourceFile file, AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool isGeneratedCode) { + RoslynDebug.Assert(file.SourceTree != null); AnalyzerStateData analyzerStateOpt = null; try { - if (TryStartSyntaxAnalysis(tree, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + if (TryStartSyntaxAnalysis(file, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) { - ExecuteSyntaxTreeActionsCore(syntaxTreeActions, analyzer, tree, analyzerStateOpt, isGeneratedCode); - analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analyzer); + ExecuteSyntaxTreeActionsCore(syntaxTreeActions, analyzer, file, analyzerStateOpt, isGeneratedCode); + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analyzer); return true; } @@ -744,17 +745,20 @@ public bool TryExecuteSyntaxTreeActions( private void ExecuteSyntaxTreeActionsCore( ImmutableArray syntaxTreeActions, DiagnosticAnalyzer analyzer, - SyntaxTree tree, + SourceOrNonSourceFile file, AnalyzerStateData analyzerStateOpt, bool isGeneratedCode) { + RoslynDebug.Assert(file.SourceTree != null); + + var tree = file.SourceTree; if (isGeneratedCode && _shouldSkipAnalysisOnGeneratedCode(analyzer) || _isAnalyzerSuppressedForTree(analyzer, tree)) { return; } - var diagReporter = GetAddDiagnostic(tree, analyzer, isSyntaxDiagnostic: true); + var diagReporter = GetAddSyntaxDiagnostic(file, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); @@ -780,6 +784,79 @@ private void ExecuteSyntaxTreeActionsCore( diagReporter.Free(); } + /// + /// Tries to execute the additional file actions. + /// + /// Actions to be executed. + /// Analyzer whose actions are to be executed. + /// Additional file to analyze. + /// Scope for analyzer execution. + /// An optional object to track analysis state. + /// + /// True, if successfully executed the actions for the given analysis scope OR all the actions have already been executed for the given analysis scope. + /// False, if there are some pending actions that are currently being executed on another thread. + /// + public bool TryExecuteAdditionalFileActions( + ImmutableArray additionalFileActions, + DiagnosticAnalyzer analyzer, + SourceOrNonSourceFile file, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + { + RoslynDebug.Assert(file.NonSourceFile != null); + AnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartSyntaxAnalysis(file, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteAdditionalFileActionsCore(additionalFileActions, analyzer, file, analyzerStateOpt); + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analyzer); + return true; + } + + return analysisStateOpt == null || !analysisStateOpt.HasPendingSyntaxAnalysis(analysisScope); + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteAdditionalFileActionsCore( + ImmutableArray additionalFileActions, + DiagnosticAnalyzer analyzer, + SourceOrNonSourceFile file, + AnalyzerStateData analyzerStateOpt) + { + RoslynDebug.Assert(file.NonSourceFile != null); + var additionalFile = file.NonSourceFile; + + var diagReporter = GetAddSyntaxDiagnostic(file, analyzer); + + using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); + foreach (var additionalFileAction in additionalFileActions) + { + if (ShouldExecuteAction(analyzerStateOpt, additionalFileAction)) + { + _cancellationToken.ThrowIfCancellationRequested(); + + var context = new AdditionalFileAnalysisContext(additionalFile, _analyzerOptions, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, _compilation, _cancellationToken); + + // Catch Exception from action. + ExecuteAndCatchIfThrows( + additionalFileAction.Analyzer, + data => data.action(data.context), + (action: additionalFileAction.Action, context), + new AnalysisContextInfo(_compilation, additionalFile)); + + analyzerStateOpt?.ProcessedActions.Add(additionalFileAction); + } + } + + diagReporter.Free(); + } + private void ExecuteSyntaxNodeAction( SyntaxNodeAnalyzerAction syntaxNodeAction, SyntaxNode node, @@ -979,7 +1056,7 @@ private void ExecuteBlockActionsCore( return; } - var diagReporter = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false); + var diagReporter = GetAddSemanticDiagnostic(model.SyntaxTree, filterSpan, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, analyzer, containingSymbol, model, getKind, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt); @@ -1352,7 +1429,7 @@ private void ExecuteOperationActionsCore( return; } - var diagReporter = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false); + var diagReporter = GetAddSemanticDiagnostic(model.SyntaxTree, filterSpan, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, containingSymbol, model, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt); @@ -1717,60 +1794,25 @@ private Action GetAddCompilationDiagnostic(DiagnosticAnalyzer analyz }; } - private AnalyzerDiagnosticReporter GetAddDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(tree, null, _compilation, analyzer, isSyntaxDiagnostic, + return AnalyzerDiagnosticReporter.GetInstance(SourceOrNonSourceFile.Create(tree), null, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } - private AnalyzerDiagnosticReporter GetAddDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(tree, span, _compilation, analyzer, false, + return AnalyzerDiagnosticReporter.GetInstance(SourceOrNonSourceFile.Create(tree), span, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } - private static Action GetAddDiagnostic( - SyntaxTree contextTree, - TextSpan? span, - Compilation compilation, - DiagnosticAnalyzer analyzer, - bool isSyntaxDiagnostic, - Action addNonCategorizedDiagnosticOpt, - Action addCategorizedLocalDiagnosticOpt, - Action addCategorizedNonLocalDiagnosticOpt, - Func shouldSuppressGeneratedCodeDiagnostic, - CancellationToken cancellationToken) + private AnalyzerDiagnosticReporter GetAddSyntaxDiagnostic(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer) { - return diagnostic => - { - if (shouldSuppressGeneratedCodeDiagnostic(diagnostic, analyzer, compilation, cancellationToken)) - { - return; - } - - if (addCategorizedLocalDiagnosticOpt == null) - { - Debug.Assert(addNonCategorizedDiagnosticOpt != null); - addNonCategorizedDiagnosticOpt(diagnostic); - return; - } - - Debug.Assert(addNonCategorizedDiagnosticOpt == null); - Debug.Assert(addCategorizedNonLocalDiagnosticOpt != null); - - if (diagnostic.Location.IsInSource && - contextTree == diagnostic.Location.SourceTree && - (!span.HasValue || span.Value.IntersectsWith(diagnostic.Location.SourceSpan))) - { - addCategorizedLocalDiagnosticOpt(diagnostic, analyzer, isSyntaxDiagnostic); - } - else - { - addCategorizedNonLocalDiagnosticOpt(diagnostic, analyzer); - } - }; + return AnalyzerDiagnosticReporter.GetInstance(file, null, _compilation, analyzer, isSyntaxDiagnostic: true, + _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, + _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } private static bool ShouldExecuteAction(AnalyzerStateData analyzerStateOpt, AnalyzerAction action) @@ -1841,12 +1883,12 @@ private static bool TryStartProcessingEvent(CompilationEvent nonSymbolCompilatio return analysisStateOpt == null || analysisStateOpt.TryStartProcessingEvent(nonSymbolCompilationEvent, analyzer, out analyzerStateOpt); } - private static bool TryStartSyntaxAnalysis(SyntaxTree tree, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) + private static bool TryStartSyntaxAnalysis(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) { Debug.Assert(analysisScope.Analyzers.Contains(analyzer)); analyzerStateOpt = null; - return analysisStateOpt == null || analysisStateOpt.TryStartSyntaxAnalysis(tree, analyzer, out analyzerStateOpt); + return analysisStateOpt == null || analysisStateOpt.TryStartSyntaxAnalysis(file, analyzer, out analyzerStateOpt); } private static bool TryStartAnalyzingSymbol(ISymbol symbol, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs index 515d1bbd37d9c..1e4968e80cc1d 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs @@ -33,6 +33,11 @@ public sealed class AnalyzerTelemetryInfo /// public int SyntaxTreeActionsCount { get; set; } = 0; + /// + /// Count of registered additional file actions. + /// + public int AdditionalFileActionsCount { get; set; } = 0; + /// /// Count of registered semantic model actions. /// @@ -117,6 +122,7 @@ internal AnalyzerTelemetryInfo(AnalyzerActionCounts actionCounts, int suppressio CompilationActionsCount = actionCounts.CompilationActionsCount; SyntaxTreeActionsCount = actionCounts.SyntaxTreeActionsCount; + AdditionalFileActionsCount = actionCounts.AdditionalFileActionsCount; SemanticModelActionsCount = actionCounts.SemanticModelActionsCount; SymbolActionsCount = actionCounts.SymbolActionsCount; SymbolStartActionsCount = actionCounts.SymbolStartActionsCount; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs index 9f65a30c47ba0..035343206e14a 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; + namespace Microsoft.CodeAnalysis.Diagnostics { /// @@ -9,10 +11,25 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal sealed class CompilationStartedEvent : CompilationEvent { - public CompilationStartedEvent(Compilation compilation) : base(compilation) { } + public ImmutableArray AdditionalFiles { get; } + + private CompilationStartedEvent(Compilation compilation, ImmutableArray additionalFiles) + : base(compilation) + { + AdditionalFiles = additionalFiles; + } + + public CompilationStartedEvent(Compilation compilation) + : this(compilation, ImmutableArray.Empty) + { + } + public override string ToString() { return "CompilationStartedEvent"; } + + public CompilationStartedEvent WithAdditionalFiles(ImmutableArray additionalFiles) + => new CompilationStartedEvent(Compilation, additionalFiles); } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index 7415cf17ace1c..02140b1bc5b1a 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -56,7 +56,7 @@ public class CompilationWithAnalyzers /// Lock to track the set of active tasks computing tree diagnostics and task computing compilation diagnostics. /// private readonly object _executingTasksLock = new object(); - private readonly Dictionary>? _executingConcurrentTreeTasksOpt; + private readonly Dictionary>? _executingConcurrentTreeTasksOpt; private Tuple? _executingCompilationOrNonConcurrentTreeTask; /// @@ -136,10 +136,10 @@ private CompilationWithAnalyzers(Compilation compilation, ImmutableArray.Empty); _analyzerManager = new AnalyzerManager(analyzers); _driverPool = new ObjectPool(() => _compilation.CreateAnalyzerDriver(analyzers, _analyzerManager, severityFilter: SeverityFilter.None)); - _executingConcurrentTreeTasksOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary>() : null; + _executingConcurrentTreeTasksOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary>() : null; _concurrentTreeTaskTokensOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary() : null; _executingCompilationOrNonConcurrentTreeTask = null; } @@ -245,6 +245,19 @@ private void VerifyTree(SyntaxTree tree) } } + private void VerifyAdditionalFile(AdditionalText file) + { + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + + if (_analysisOptions.Options == null || !_analysisOptions.Options.AdditionalFiles.Contains(file)) + { + throw new ArgumentException(CodeAnalysisResources.InvalidNonSourceFile, nameof(file)); + } + } + #endregion /// @@ -340,7 +353,7 @@ private async Task> GetAnalyzerCompilationDiagnostics await WaitForActiveAnalysisTasksAsync(waitForTreeTasks: true, waitForCompilationOrNonConcurrentTask: true, cancellationToken: cancellationToken).ConfigureAwait(false); var diagnostics = ImmutableArray.Empty; - var analysisScope = new AnalysisScope(_compilation, analyzers, _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(_compilation, _analysisOptions.Options, analyzers, _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); Func> getPendingEvents = () => _analysisState.GetPendingEvents(analyzers, includeSourceEvents: true, includeNonSourceEvents: true, cancellationToken); @@ -365,7 +378,7 @@ private async Task> GetAnalyzerDiagnosticsWithoutStat await ComputeAnalyzerDiagnosticsWithoutStateTrackingAsync(cancellationToken).ConfigureAwait(false); // Get analyzer diagnostics for the given analysis scope. - var analysisScope = new AnalysisScope(_compilation, analyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(_compilation, _analysisOptions.Options, analyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: true); } @@ -391,7 +404,7 @@ private async Task ComputeAnalyzerDiagnosticsWithoutStateTrackingAsync(Cancellat var categorizeDiagnostics = true; driver = compilation.CreateAnalyzerDriver(analyzers, _analyzerManager, severityFilter: SeverityFilter.None); driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, cancellationToken); - var analysisScope = new AnalysisScope(compilation, analyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); + var analysisScope = new AnalysisScope(compilation, _analysisOptions.Options, analyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); driver.AttachQueueAndStartProcessingEvents(compilation.EventQueue, analysisScope, cancellationToken); // Force compilation diagnostics and wait for analyzer execution to complete. @@ -431,7 +444,7 @@ private async Task> GetAllDiagnosticsWithoutStateTrac var categorizeDiagnostics = false; driver = compilation.CreateAnalyzerDriver(analyzers, _analyzerManager, severityFilter: SeverityFilter.None); driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, cancellationToken); - var analysisScope = new AnalysisScope(compilation, analyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); + var analysisScope = new AnalysisScope(compilation, _analysisOptions.Options, analyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); driver.AttachQueueAndStartProcessingEvents(compilation.EventQueue, analysisScope, cancellationToken); // Force compilation diagnostics and wait for analyzer execution to complete. @@ -459,7 +472,7 @@ public async Task> GetAnalyzerSyntaxDiagnosticsAsync( { VerifyTree(tree); - return await GetAnalyzerSyntaxDiagnosticsCoreAsync(tree, Analyzers, cancellationToken).ConfigureAwait(false); + return await GetAnalyzerSyntaxDiagnosticsCoreAsync(SourceOrNonSourceFile.Create(tree), Analyzers, cancellationToken).ConfigureAwait(false); } /// @@ -478,7 +491,7 @@ public async Task> GetAnalyzerSyntaxDiagnosticsAsync( VerifyTree(tree); VerifyExistingAnalyzersArgument(analyzers); - return await GetAnalyzerSyntaxDiagnosticsCoreAsync(tree, analyzers, cancellationToken).ConfigureAwait(false); + return await GetAnalyzerSyntaxDiagnosticsCoreAsync(SourceOrNonSourceFile.Create(tree), analyzers, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { @@ -486,13 +499,47 @@ public async Task> GetAnalyzerSyntaxDiagnosticsAsync( } } - private async Task> GetAnalyzerSyntaxDiagnosticsCoreAsync(SyntaxTree tree, ImmutableArray analyzers, CancellationToken cancellationToken) + /// + /// Returns diagnostics produced by all from analyzing the given additional . + /// The given must be part of for the for this CompilationWithAnalyzers instance. + /// Depending on analyzers' behavior, returned diagnostics can have locations outside the file, + /// and some diagnostics that would be reported for the file by an analysis of the complete compilation + /// can be absent. + /// + /// Additional file to analyze. + /// Cancellation token. + public async Task> GetAnalyzerAdditionalFileDiagnosticsAsync(AdditionalText file, CancellationToken cancellationToken) + { + VerifyAdditionalFile(file); + + return await GetAnalyzerSyntaxDiagnosticsCoreAsync(SourceOrNonSourceFile.Create(file), Analyzers, cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns diagnostics produced by given from analyzing the given additional . + /// The given must be part of for the for this CompilationWithAnalyzers instance. + /// Depending on analyzers' behavior, returned diagnostics can have locations outside the file, + /// and some diagnostics that would be reported for the file by an analysis of the complete compilation + /// can be absent. + /// + /// Additional file to analyze. + /// Analyzers whose diagnostics are required. All the given analyzers must be from the analyzers passed into the constructor of . + /// Cancellation token. + public async Task> GetAnalyzerAdditionalFileDiagnosticsAsync(AdditionalText file, ImmutableArray analyzers, CancellationToken cancellationToken) + { + VerifyAdditionalFile(file); + VerifyExistingAnalyzersArgument(analyzers); + + return await GetAnalyzerSyntaxDiagnosticsCoreAsync(SourceOrNonSourceFile.Create(file), analyzers, cancellationToken).ConfigureAwait(false); + } + + private async Task> GetAnalyzerSyntaxDiagnosticsCoreAsync(SourceOrNonSourceFile file, ImmutableArray analyzers, CancellationToken cancellationToken) { try { var taskToken = Interlocked.Increment(ref _currentToken); - var analysisScope = new AnalysisScope(analyzers, tree, filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, file, filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); var pendingAnalyzers = _analysisResultBuilder.GetPendingAnalyzers(analyzers); if (pendingAnalyzers.Length > 0) @@ -559,7 +606,8 @@ private async Task> GetAnalyzerSemanticDiagnosticsCor { var taskToken = Interlocked.Increment(ref _currentToken); - var analysisScope = new AnalysisScope(analyzers, model.SyntaxTree, filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var file = SourceOrNonSourceFile.Create(model.SyntaxTree); + var analysisScope = new AnalysisScope(analyzers, file, filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); var pendingAnalyzers = _analysisResultBuilder.GetPendingAnalyzers(analyzers); if (pendingAnalyzers.Length > 0) @@ -728,7 +776,7 @@ await Task.WhenAll(partialTrees.Select(tree => cancellationSource); // Wait for higher priority tree document tasks to complete. - computeTask = await SetActiveAnalysisTaskAsync(getComputeTask, analysisScope.FilterTreeOpt, newTaskToken, cancellationToken).ConfigureAwait(false); + computeTask = await SetActiveAnalysisTaskAsync(getComputeTask, analysisScope.FilterFileOpt, newTaskToken, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -746,7 +794,7 @@ await Task.WhenAll(partialTrees.Select(tree => } finally { - ClearExecutingTask(computeTask, analysisScope.FilterTreeOpt); + ClearExecutingTask(computeTask, analysisScope.FilterFileOpt); computeTask = null; } } @@ -769,13 +817,13 @@ private void GenerateCompilationEvents(AnalysisScope analysisScope, Cancellation { // Invoke GetDiagnostics to populate CompilationEvent queue for the given analysis scope. // Discard the returned diagnostics. - if (analysisScope.FilterTreeOpt == null) + if (analysisScope.FilterFileOpt == null) { _ = _compilation.GetDiagnostics(cancellationToken); } else if (!analysisScope.IsSyntaxOnlyTreeAnalysis) { - var mappedModel = _compilationData.GetOrCreateCachedSemanticModel(analysisScope.FilterTreeOpt, _compilation, cancellationToken); + var mappedModel = _compilationData.GetOrCreateCachedSemanticModel(analysisScope.FilterFileOpt!.SourceTree!, _compilation, cancellationToken); _ = mappedModel.GetDiagnostics(cancellationToken: cancellationToken); } } @@ -805,6 +853,12 @@ ImmutableArray dequeueGeneratedCompilationEvents() while (_compilation.EventQueue.TryDequeue(out CompilationEvent compilationEvent)) { + if (compilationEvent is CompilationStartedEvent compilationStartedEvent && + _analysisOptions.Options?.AdditionalFiles.Length > 0) + { + compilationEvent = compilationStartedEvent.WithAdditionalFiles(_analysisOptions.Options.AdditionalFiles); + } + builder.Add(compilationEvent); } @@ -897,7 +951,7 @@ private async Task ComputeAnalyzerDiagnosticsCoreAsync(AnalyzerDriver driver, As } } - private Task SetActiveAnalysisTaskAsync(Func> getNewAnalysisTask, SyntaxTree? treeOpt, int newTaskToken, CancellationToken cancellationToken) + private Task SetActiveAnalysisTaskAsync(Func> getNewAnalysisTask, SourceOrNonSourceFile? treeOpt, int newTaskToken, CancellationToken cancellationToken) { if (treeOpt != null) { @@ -967,7 +1021,7 @@ private async Task WaitForActiveAnalysisTasksAsync(bool waitForTreeTasks, bool w } } - private async Task SetActiveTreeAnalysisTaskAsync(Func> getNewTreeAnalysisTask, SyntaxTree tree, int newTaskToken, CancellationToken cancellationToken) + private async Task SetActiveTreeAnalysisTaskAsync(Func> getNewTreeAnalysisTask, SourceOrNonSourceFile tree, int newTaskToken, CancellationToken cancellationToken) { try { @@ -1034,7 +1088,7 @@ private void SuspendAnalysis_NoLock(Task computeTask, CancellationTokenSource ct } } - private void ClearExecutingTask(Task? computeTask, SyntaxTree? treeOpt) + private void ClearExecutingTask(Task? computeTask, SourceOrNonSourceFile? treeOpt) { if (computeTask != null) { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs index 31b58d26a3612..b8dd222e38637 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs @@ -120,6 +120,16 @@ public virtual void RegisterSymbolStartAction(Action /// Action to be executed at completion of parsing of a document. public abstract void RegisterSyntaxTreeAction(Action action); + /// + /// Register an action to be executed for each non-code document. + /// An additional file action reports s about the of a document. + /// + /// Action to be executed for each non-code document. + public virtual void RegisterAdditionalFileAction(Action action) + { + throw new NotImplementedException(); + } + /// /// Register an action to be executed at completion of semantic analysis of a with an appropriate Kind. /// A syntax node action can report s about s, and can also collect @@ -405,6 +415,16 @@ public virtual void RegisterOperationBlockAction(ActionAction to be executed at completion of parsing of a document. public abstract void RegisterSyntaxTreeAction(Action action); + /// + /// Register an action to be executed for each non-code document. + /// An additional file action reports s about the of a document. + /// + /// Action to be executed for each non-code document. + public virtual void RegisterAdditionalFileAction(Action action) + { + throw new NotImplementedException(); + } + /// /// Register an action to be executed at completion of semantic analysis of a with an appropriate Kind. /// A syntax node action can report s about s, and can also collect @@ -1285,6 +1305,66 @@ public void ReportDiagnostic(Diagnostic diagnostic) } } + /// + /// Context for an additional file action. + /// An additional file action can use an to report s about a non-source document. + /// + public struct AdditionalFileAnalysisContext + { + private readonly Action _reportDiagnostic; + private readonly Func _isSupportedDiagnostic; + + /// + /// that is the subject of the analysis. + /// + public AdditionalText AdditionalFile { get; } + + /// + /// Options specified for the analysis. + /// + public AnalyzerOptions Options { get; } + + /// + /// Token to check for requested cancellation of the analysis. + /// + public CancellationToken CancellationToken { get; } + + /// + /// Compilation being analyzed. + /// + public Compilation Compilation { get; } + + internal AdditionalFileAnalysisContext( + AdditionalText additionalFile, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + Compilation compilation, + CancellationToken cancellationToken) + { + AdditionalFile = additionalFile; + Options = options; + _reportDiagnostic = reportDiagnostic; + _isSupportedDiagnostic = isSupportedDiagnostic; + Compilation = compilation; + CancellationToken = cancellationToken; + } + + /// + /// Report a diagnostic for the given . + /// A diagnostic in a non-source document should be created with a non-source , + /// which can be created using API. + /// + public void ReportDiagnostic(Diagnostic diagnostic) + { + DiagnosticAnalysisContextHelpers.VerifyArguments(diagnostic, Compilation, _isSupportedDiagnostic); + lock (_reportDiagnostic) + { + _reportDiagnostic(diagnostic); + } + } + } + /// /// Context for a syntax node action. /// A syntax node action can use a to report s for a . diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs index 5514fff6cf96e..46593837d0ceb 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs @@ -169,6 +169,17 @@ public SyntaxTreeAnalyzerAction(Action action, Diagno public Action Action { get { return _action; } } } + internal sealed class AdditionalFileAnalyzerAction : AnalyzerAction + { + public AdditionalFileAnalyzerAction(Action action, DiagnosticAnalyzer analyzer) + : base(analyzer) + { + Action = action; + } + + public Action Action { get; } + } + internal sealed class CodeBlockStartAnalyzerAction : AnalyzerAction where TLanguageKindEnum : struct { private readonly Action> _action; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs index 98015e0aa2d4f..71e465685ee9f 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs @@ -114,7 +114,7 @@ public override void Enqueue(Diagnostic diagnostic) public override void EnqueueLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) { - Debug.Assert(diagnostic.Location.IsInSource); + Debug.Assert(diagnostic.Location.Kind == LocationKind.SourceFile || diagnostic.Location.Kind == LocationKind.ExternalFile); if (isSyntaxDiagnostic) { EnqueueCore(ref _lazyLocalSyntaxDiagnostics, diagnostic, analyzer); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs index 7ec819c99e368..ecf83eb34b5b0 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs @@ -46,6 +46,12 @@ public override void RegisterSyntaxTreeAction(Action _scope.RegisterSyntaxTreeAction(_analyzer, action); } + public override void RegisterAdditionalFileAction(Action action) + { + DiagnosticAnalysisContextHelpers.VerifyArguments(action); + _scope.RegisterAdditionalFileAction(_analyzer, action); + } + public override void RegisterSemanticModelAction(Action action) { DiagnosticAnalysisContextHelpers.VerifyArguments(action); @@ -146,6 +152,12 @@ public override void RegisterSyntaxTreeAction(Action _scope.RegisterSyntaxTreeAction(_analyzer, action); } + public override void RegisterAdditionalFileAction(Action action) + { + DiagnosticAnalysisContextHelpers.VerifyArguments(action); + _scope.RegisterAdditionalFileAction(_analyzer, action); + } + public override void RegisterSemanticModelAction(Action action) { DiagnosticAnalysisContextHelpers.VerifyArguments(action); @@ -510,6 +522,12 @@ public void RegisterSyntaxTreeAction(DiagnosticAnalyzer analyzer, Action action) + { + var analyzerAction = new AdditionalFileAnalyzerAction(action, analyzer); + this.GetOrCreateAnalyzerActions(analyzer).Value.AddAdditionalFileAction(analyzerAction); + } + public void RegisterSymbolAction(DiagnosticAnalyzer analyzer, Action action, ImmutableArray symbolKinds) { SymbolAnalyzerAction analyzerAction = new SymbolAnalyzerAction(action, symbolKinds, analyzer); @@ -644,6 +662,7 @@ internal struct AnalyzerActions private ImmutableArray _compilationEndActions; private ImmutableArray _compilationActions; private ImmutableArray _syntaxTreeActions; + private ImmutableArray _additionalFileActions; private ImmutableArray _semanticModelActions; private ImmutableArray _symbolActions; private ImmutableArray _symbolStartActions; @@ -664,6 +683,7 @@ internal AnalyzerActions(bool concurrent) _compilationEndActions = ImmutableArray.Empty; _compilationActions = ImmutableArray.Empty; _syntaxTreeActions = ImmutableArray.Empty; + _additionalFileActions = ImmutableArray.Empty; _semanticModelActions = ImmutableArray.Empty; _symbolActions = ImmutableArray.Empty; _symbolStartActions = ImmutableArray.Empty; @@ -686,6 +706,7 @@ public AnalyzerActions( ImmutableArray compilationEndActions, ImmutableArray compilationActions, ImmutableArray syntaxTreeActions, + ImmutableArray additionalFileActions, ImmutableArray semanticModelActions, ImmutableArray symbolActions, ImmutableArray symbolStartActions, @@ -705,6 +726,7 @@ public AnalyzerActions( _compilationEndActions = compilationEndActions; _compilationActions = compilationActions; _syntaxTreeActions = syntaxTreeActions; + _additionalFileActions = additionalFileActions; _semanticModelActions = semanticModelActions; _symbolActions = symbolActions; _symbolStartActions = symbolStartActions; @@ -725,6 +747,7 @@ public AnalyzerActions( public readonly int CompilationEndActionsCount { get { return _compilationEndActions.Length; } } public readonly int CompilationActionsCount { get { return _compilationActions.Length; } } public readonly int SyntaxTreeActionsCount { get { return _syntaxTreeActions.Length; } } + public readonly int AdditionalFileActionsCount { get { return _additionalFileActions.Length; } } public readonly int SemanticModelActionsCount { get { return _semanticModelActions.Length; } } public readonly int SymbolActionsCount { get { return _symbolActions.Length; } } public readonly int SymbolStartActionsCount { get { return _symbolStartActions.Length; } } @@ -761,6 +784,11 @@ internal readonly ImmutableArray SyntaxTreeActions get { return _syntaxTreeActions; } } + internal readonly ImmutableArray AdditionalFileActions + { + get { return _additionalFileActions; } + } + internal readonly ImmutableArray SemanticModelActions { get { return _semanticModelActions; } @@ -845,6 +873,12 @@ internal void AddSyntaxTreeAction(SyntaxTreeAnalyzerAction action) IsEmpty = false; } + internal void AddAdditionalFileAction(AdditionalFileAnalyzerAction action) + { + _additionalFileActions = _additionalFileActions.Add(action); + IsEmpty = false; + } + internal void AddSemanticModelAction(SemanticModelAnalyzerAction action) { _semanticModelActions = _semanticModelActions.Add(action); @@ -938,6 +972,7 @@ public readonly AnalyzerActions Append(in AnalyzerActions otherActions, bool app actions._compilationEndActions = _compilationEndActions.AddRange(otherActions._compilationEndActions); actions._compilationActions = _compilationActions.AddRange(otherActions._compilationActions); actions._syntaxTreeActions = _syntaxTreeActions.AddRange(otherActions._syntaxTreeActions); + actions._additionalFileActions = _additionalFileActions.AddRange(otherActions._additionalFileActions); actions._semanticModelActions = _semanticModelActions.AddRange(otherActions._semanticModelActions); actions._symbolActions = _symbolActions.AddRange(otherActions._symbolActions); actions._symbolStartActions = appendSymbolStartAndSymbolEndActions ? _symbolStartActions.AddRange(otherActions._symbolStartActions) : _symbolStartActions; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs new file mode 100644 index 0000000000000..92309c15224ea --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs @@ -0,0 +1,72 @@ +// 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 enable + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Represents a source file or a non-source file. + /// For source files, is non-null and is null. + /// For non-source files, is non-null and is null. + /// + internal abstract class SourceOrNonSourceFile + : IEquatable + { + public abstract SyntaxTree? SourceTree { get; } + public abstract AdditionalText? NonSourceFile { get; } + public abstract bool Equals([AllowNull] SourceOrNonSourceFile? other); + public abstract override bool Equals(object? obj); + public abstract override int GetHashCode(); + + public static SourceOrNonSourceFile Create(SyntaxTree tree) + { + return new SourceFileImpl(tree); + } + + public static SourceOrNonSourceFile Create(AdditionalText nonSourceFile) + { + return new NonSourceFileImpl(nonSourceFile); + } + + private sealed class SourceFileImpl : SourceOrNonSourceFile + { + public SourceFileImpl(SyntaxTree tree) + { + SourceTree = tree; + } + + public override SyntaxTree? SourceTree { get; } + public override AdditionalText? NonSourceFile => null; + public override bool Equals(object? obj) + => Equals(obj as SourceFileImpl); + public override bool Equals([AllowNull] SourceOrNonSourceFile? other) + => other is SourceFileImpl otherSource && + SourceTree == otherSource.SourceTree; + public override int GetHashCode() + => SourceTree!.GetHashCode(); + } + + private sealed class NonSourceFileImpl : SourceOrNonSourceFile + { + public NonSourceFileImpl(AdditionalText nonSourceFile) + { + NonSourceFile = nonSourceFile; + } + + public override AdditionalText? NonSourceFile { get; } + public override SyntaxTree? SourceTree => null; + public override bool Equals(object? obj) + => Equals(obj as NonSourceFileImpl); + public override bool Equals([AllowNull] SourceOrNonSourceFile? other) + => other is NonSourceFileImpl otherNonSource && + NonSourceFile == otherNonSource.NonSourceFile; + public override int GetHashCode() + => NonSourceFile!.GetHashCode(); + } + } +} diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 0e12e8a78a856..dfe24ddf4d5b3 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,7 +1,18 @@ Microsoft.CodeAnalysis.Compilation.CreateFunctionPointerTypeSymbol(Microsoft.CodeAnalysis.ITypeSymbol returnType, Microsoft.CodeAnalysis.RefKind returnRefKind, System.Collections.Immutable.ImmutableArray parameterTypes, System.Collections.Immutable.ImmutableArray parameterRefKinds) -> Microsoft.CodeAnalysis.IFunctionPointerTypeSymbol Microsoft.CodeAnalysis.Compilation.CreateNativeIntegerTypeSymbol(bool signed) -> Microsoft.CodeAnalysis.INamedTypeSymbol +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.AdditionalFile.get -> Microsoft.CodeAnalysis.AdditionalText +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.CancellationToken.get -> System.Threading.CancellationToken +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void +Microsoft.CodeAnalysis.Diagnostics.AnalysisResult.NonSourceFileDiagnostics.get -> System.Collections.Immutable.ImmutableDictionary>> Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.AssemblyLoader.get -> Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Equals(Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference other) -> bool +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerAdditionalFileDiagnosticsAsync(Microsoft.CodeAnalysis.AdditionalText file, System.Collections.Immutable.ImmutableArray analyzers, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerAdditionalFileDiagnosticsAsync(Microsoft.CodeAnalysis.AdditionalText file, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.set -> void Microsoft.CodeAnalysis.Emit.EmitOptions.DefaultSourceFileEncoding.get -> System.Text.Encoding Microsoft.CodeAnalysis.Emit.EmitOptions.EmitOptions(bool metadataOnly = false, Microsoft.CodeAnalysis.Emit.DebugInformationFormat debugInformationFormat = (Microsoft.CodeAnalysis.Emit.DebugInformationFormat)0, string pdbFilePath = null, string outputNameOverride = null, int fileAlignment = 0, ulong baseAddress = 0, bool highEntropyVirtualAddressSpace = false, Microsoft.CodeAnalysis.SubsystemVersion subsystemVersion = default(Microsoft.CodeAnalysis.SubsystemVersion), string runtimeMetadataVersion = null, bool tolerateErrors = false, bool includePrivateMembers = true, System.Collections.Immutable.ImmutableArray instrumentationKinds = default(System.Collections.Immutable.ImmutableArray), System.Security.Cryptography.HashAlgorithmName? pdbChecksumAlgorithm = null, System.Text.Encoding defaultSourceFileEncoding = null, System.Text.Encoding fallbackSourceFileEncoding = null) -> void Microsoft.CodeAnalysis.Emit.EmitOptions.EmitOptions(bool metadataOnly, Microsoft.CodeAnalysis.Emit.DebugInformationFormat debugInformationFormat, string pdbFilePath, string outputNameOverride, int fileAlignment, ulong baseAddress, bool highEntropyVirtualAddressSpace, Microsoft.CodeAnalysis.SubsystemVersion subsystemVersion, string runtimeMetadataVersion, bool tolerateErrors, bool includePrivateMembers, System.Collections.Immutable.ImmutableArray instrumentationKinds, System.Security.Cryptography.HashAlgorithmName? pdbChecksumAlgorithm) -> void @@ -41,6 +52,8 @@ Microsoft.CodeAnalysis.SymbolKind.FunctionPointerType = 20 -> Microsoft.CodeAnal Microsoft.CodeAnalysis.TypeKind.FunctionPointer = 13 -> Microsoft.CodeAnalysis.TypeKind abstract Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider.GlobalOptions.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions static Microsoft.CodeAnalysis.AnalyzerConfigSet.Create(TList analyzerConfigs, out System.Collections.Immutable.ImmutableArray diagnostics) -> Microsoft.CodeAnalysis.AnalyzerConfigSet +virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterAdditionalFileAction(System.Action action) -> void +virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterAdditionalFileAction(System.Action action) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBinaryPattern(Microsoft.CodeAnalysis.Operations.IBinaryPatternOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitNegatedPattern(Microsoft.CodeAnalysis.Operations.INegatedPatternOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRelationalPattern(Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation operation) -> void diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf index 40d5dbf3936fd..75a74d2b43e65 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf @@ -44,6 +44,11 @@ Potlačené ID diagnostiky {0} neodpovídá potlačitelnému ID {1} pro daný popisovač potlačení. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. Blok dané operace nepatří do aktuálního analytického kontextu. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf index a16264b79ce1a..655042736750d 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf @@ -44,6 +44,11 @@ Die unterdrückte Diagnose-ID "{0}" entspricht nicht der unterdrückbaren ID "{1}" für den angegebenen Deskriptor zur Unterdrückung. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. Der angegebene Operationsblock gehört nicht zum aktuellen Analysekontext. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf index 7a3cb437dc2a6..feb8c2bf5923f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf @@ -44,6 +44,11 @@ El id. de diagnóstico "{0}" suprimido no coincide con el id. "{1}" que se puede suprimir para el descriptor de supresión dado. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. El bloque de operaciones dado no pertenece al contexto de análisis actual. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf index f0774057d8213..a7ca2b08346d7 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf @@ -44,6 +44,11 @@ L'ID de diagnostic supprimé '{0}' ne correspond pas à l'ID supprimable '{1}' pour le descripteur de suppression spécifié. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. Le bloc d'opérations donné n'appartient pas au contexte d'analyse actuel. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf index 515a5a4974757..c9601fc0f9184 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf @@ -44,6 +44,11 @@ L'ID diagnostica '{0}' eliminato non corrisponde all'ID eliminabile '{1}' per il descrittore di eliminazione specificato. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. Il blocco operazioni specificato non appartiene al contesto di analisi corrente. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf index 1a2135a09b612..9786fad2b7ace 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf @@ -44,6 +44,11 @@ 抑制された診断 ID '{0}' が、指定された抑制記述子の抑制可能な ID '{1}' と一致しません。 + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. 指定した操作ブロックが現在の分析コンテストに属していません。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf index 2faaacfd41e40..366b56c26c4af 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf @@ -44,6 +44,11 @@ 표시되지 않는 진단 ID '{0}'이(가) 지정된 비표시 설명자의 표시하지 않을 수 있는 ID '{1}'과(와) 일치하지 않습니다. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. 지정한 작업 블록이 현재 분석 컨텍스트에 속하지 않습니다. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf index bf39e02888770..6b1b01fdaa3b3 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf @@ -44,6 +44,11 @@ Pominięty identyfikator diagnostyki „{0}” nie pasuje do możliwego do pominięcia identyfikatora „{1}” dla danego deskryptora pomijania. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. Dany blok operacji nie należy do bieżącego kontekstu analizy. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf index 4201e76d383e9..4c86a560281b6 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf @@ -44,6 +44,11 @@ A ID de diagnóstico suprimida '{0}' não corresponde à ID suprimível '{1}' para o descritor de supressão fornecido. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. O bloqueio de operação fornecido não pertence ao contexto de análise atual. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf index 7195026b5db8c..ea5fb78b96a1b 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf @@ -44,6 +44,11 @@ Подавленный идентификатор диагностики "{0}" не соответствует подавленному идентификатору "{1}" для заданного дескриптора подавления. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. Заданный блок операции не принадлежит текущему контексту анализа. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf index a016f43e492f0..7f77300e21fd3 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf @@ -44,6 +44,11 @@ '{0}' gizlenmiş tanılama kimliği, belirtilen gizleme tanımlayıcısı için gizlenebilir '{1}' kimliği ile eşleşmiyor. + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. Belirtilen işlem bloğu geçerli analiz bağlamına ait değil. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf index 6396c5ef50add..82b871fb8003e 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf @@ -44,6 +44,11 @@ 对于给定的禁止显示描述符,禁止显示的诊断 ID“{0}”与可禁止显示的 ID“{1}”不匹配。 + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. 给定操作块不属于当前的分析上下文。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf index e153d6fd575d1..6d44ada97c35f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf @@ -44,6 +44,11 @@ 隱藏的診斷識別碼 '{0}' 不符合指定隱藏描述項的可隱藏識別碼 '{1}'。 + + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Given operation block does not belong to the current analysis context. 指定的作業區塊不屬於目前的分析內容。 diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index 9a230a8a0e2b2..63bfab081fe3e 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -9960,6 +9960,31 @@ End Class") Assert.Contains("warning AD0001: Analyzer 'Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers+NamedTypeAnalyzerWithConfigurableEnabledByDefault' threw an exception of type 'System.NotImplementedException'", output, StringComparison.Ordinal) End If End Sub + + + Public Sub TestAdditionalFileAnalyzer(registerFromInitialize As Boolean) + Dim srcDirectory = Temp.CreateDirectory() + + Dim source = " +Class C +End Class" + Dim srcFile = srcDirectory.CreateFile("a.vb") + srcFile.WriteAllText(source) + + Dim additionalText = "Additional Text" + Dim additionalFile = srcDirectory.CreateFile("b.txt") + additionalFile.WriteAllText(additionalText) + + Dim diagnosticSpan = New TextSpan(2, 2) + Dim analyzer As DiagnosticAnalyzer = New AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan) + + Dim output = VerifyOutput(srcDirectory, srcFile, expectedWarningCount:=1, + includeCurrentAssemblyAsAnalyzerReference:=False, + additionalFlags:={"/additionalfile:" & additionalFile.Path}, + analyzers:=ImmutableArray.Create(analyzer)) + Assert.Contains("b.txt(1) : warning ID0001", output, StringComparison.Ordinal) + CleanupAllGeneratedFiles(srcDirectory.Path) + End Sub End Class diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb index 2f7057b55d14e..afbe014eabcac 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb @@ -44,8 +44,9 @@ End Enum Public Sub AnalyzerDriverIsSafeAgainstAnalyzerExceptions() Dim compilation = CreateCompilationWithMscorlib40({TestResource.AllInOneVisualBasicCode}) + Dim options = New AnalyzerOptions({CType(new TestAdditionalText(), AdditionalText)}.ToImmutableArray()) ThrowingDiagnosticAnalyzer(Of SyntaxKind).VerifyAnalyzerEngineIsSafeAgainstExceptions( - Function(analyzer) compilation.GetAnalyzerDiagnostics({analyzer})) + Function(analyzer) compilation.GetAnalyzerDiagnostics({analyzer}, options)) End Sub diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb index d904e32d6d954..525a61fb41ed5 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb @@ -4,12 +4,14 @@ Imports System.Collections.Immutable Imports System.Runtime.Serialization +Imports System.Threading Imports Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Diagnostics.VisualBasic Imports Microsoft.CodeAnalysis.FlowAnalysis Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities @@ -1560,5 +1562,40 @@ End Namespace compilation.VerifyAnalyzerDiagnostics(analyzers, Nothing, Nothing, Diagnostic("SymbolStartRuleId").WithArguments("MyApplication", "Analyzer1").WithLocation(1, 1)) End Sub + + + Public Async Function TestAdditionalFileAnalyzer(registerFromInitialize As Boolean) As Task + Dim tree = VisualBasicSyntaxTree.ParseText(String.Empty) + Dim compilation = CreateCompilationWithMscorlib45({tree}) + compilation.VerifyDiagnostics() + + Dim additionalFile As AdditionalText = New TestAdditionalText("Additional File Text") + Dim options = New AnalyzerOptions(ImmutableArray.Create(additionalFile)) + Dim diagnosticSpan = New TextSpan(2, 2) + Dim analyzer = New AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan) + Dim analyzers As ImmutableArray(Of DiagnosticAnalyzer) = ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer) + + Dim diagnostics = Await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None) + TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics, diagnosticSpan, analyzer, additionalFile) + + diagnostics = Await compilation.WithAnalyzers(analyzers, options).GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile, CancellationToken.None) + TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics, diagnosticSpan, analyzer, additionalFile) + + Dim analysisResult = Await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.GetAllDiagnostics(), diagnosticSpan, analyzer, additionalFile) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.NonSourceFileDiagnostics(additionalFile)(analyzer), diagnosticSpan, analyzer, additionalFile) + End Function + + Private Shared Sub TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics As ImmutableArray(Of Diagnostic), + expectedDiagnosticSpan As TextSpan, + Analyzer As AdditionalFileAnalyzer, + additionalFile As AdditionalText) + Dim diagnostic = Assert.Single(diagnostics) + Assert.Equal(Analyzer.Descriptor.Id, diagnostic.Id) + Assert.Equal(LocationKind.ExternalFile, diagnostic.Location.Kind) + Dim location = DirectCast(diagnostic.Location, ExternalFileLocation) + Assert.Equal(additionalFile.Path, location.FilePath) + Assert.Equal(expectedDiagnosticSpan, location.SourceSpan) + End Sub End Class End Namespace diff --git a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs index eb9936f114ef2..da4802867cc3a 100644 --- a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs +++ b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs @@ -2144,5 +2144,54 @@ private void OnOperationBlockStart(OperationBlockStartAnalysisContext context) endContext => endContext.ReportDiagnostic(Diagnostic.Create(s_descriptor, context.OwningSymbol.Locations[0]))); } } + + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed 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 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)); + } + } } } diff --git a/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs b/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs index 383abf3dca980..351f7d7172fa4 100644 --- a/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs +++ b/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs @@ -3,14 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.Test.Utilities @@ -21,8 +16,7 @@ public abstract class TestDiagnosticAnalyzer : DiagnosticAnal protected static readonly ImmutableArray AllSyntaxKinds = GetAllEnumValues(); - protected static readonly ImmutableArray AllAnalyzerMemberNames = new string[] { "AnalyzeCodeBlock", "AnalyzeCompilation", "AnalyzeNode", "AnalyzeSemanticModel", "AnalyzeSymbol", "AnalyzeSyntaxTree", "Initialize", "SupportedDiagnostics" }.ToImmutableArray(); - // protected static readonly ImmutableArray AllAbstractMemberNames = ImmutableArray.Empty.AddRange(GetAbstractMemberNames(typeof(CompilationStartAnalysisScope)).Distinct()); + protected static readonly ImmutableArray AllAnalyzerMemberNames = new string[] { "AnalyzeCodeBlock", "AnalyzeCompilation", "AnalyzeNode", "AnalyzeSemanticModel", "AnalyzeSymbol", "AnalyzeSyntaxTree", "AnalyzeAdditionalFile", "Initialize", "SupportedDiagnostics" }.ToImmutableArray(); protected static readonly DiagnosticDescriptor DefaultDiagnostic = #pragma warning disable RS1029 // Do not use reserved diagnostic IDs. @@ -50,6 +44,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCodeBlockAction(this.AnalyzeCodeBlock); context.RegisterSymbolAction(this.AnalyzeSymbol, AllSymbolKinds.ToArray()); context.RegisterSyntaxTreeAction(this.AnalyzeSyntaxTree); + context.RegisterAdditionalFileAction(this.AnalyzeAdditionalFile); context.RegisterSyntaxNodeAction(this.AnalyzeNode, AllSyntaxKinds.ToArray()); } @@ -92,6 +87,12 @@ private void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context) OnOptions(context.Options); } + private void AnalyzeAdditionalFile(AdditionalFileAnalysisContext context) + { + OnAbstractMember("AdditionalFile"); + OnOptions(context.Options); + } + private void AnalyzeNode(SyntaxNodeAnalysisContext context) { OnAbstractMember("SyntaxNode", context.Node); diff --git a/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs b/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs index 0cc04fbe0c190..e5861fbee4e73 100644 --- a/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs +++ b/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs @@ -4,6 +4,7 @@ #nullable enable +using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -20,6 +21,11 @@ public TestAdditionalText(string path, SourceText text) _text = text; } + public TestAdditionalText(string text = "", Encoding? encoding = null, string path = "dummy") + : this(path, new StringText(text, encoding)) + { + } + public override string Path { get; } public override SourceText GetText(CancellationToken cancellationToken = default) => _text; From d2d197fcb65a8002d529f17f6150581a47b85da7 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Wed, 10 Jun 2020 16:57:16 -0700 Subject: [PATCH 02/11] IDE support for analyzing and reporting diagnostics in non-source files Fix tests --- .../DiagnosticAnalyzerTests.AllInOne.cs | 3 +- .../DiagnosticAnalyzerTests.AllInOne.vb | 3 +- .../DiagnosticAnalyzerDriverTests.cs | 4 +- ...eviewSolutionCrawlerRegistrationService.cs | 18 ++- .../DiagnosticAnalyzerServiceTests.cs | 116 +++++++++++++- .../TestUtilities/Workspaces/TestWorkspace.cs | 7 +- .../DiagnosticAnalyzerDriverTests.vb | 4 +- .../Portable/Diagnostics/AnalyzerHelper.cs | 142 +++++++++--------- .../DefaultDiagnosticAnalyzerService.cs | 26 +++- .../DiagnosticAnalyzerTelemetry.cs | 3 + .../Diagnostics/DiagnosticResultSerializer.cs | 3 + .../DiagnosticIncrementalAnalyzer.Executor.cs | 2 +- ...gnosticIncrementalAnalyzer.ProjectState.cs | 15 +- ...gnosticIncrementalAnalyzer.StateManager.cs | 6 +- .../DiagnosticIncrementalAnalyzer.StateSet.cs | 6 +- .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 10 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 43 ++++-- .../AggregateIncrementalAnalyzer.cs | 38 ++++- ...oordinator.IncrementalAnalyzerProcessor.cs | 24 ++- ...WorkCoordinator.NormalPriorityProcessor.cs | 80 ++++++++-- .../Diagnostics/CommonDiagnosticAnalyzers.cs | 2 +- .../DiagnosticAnalyzerRunner.cs | 1 + src/Tools/AnalyzerRunner/Extensions.cs | 1 + .../Diagnostics/DiagnosticAnalysisResult.cs | 3 +- .../DiagnosticAnalysisResultBuilder.cs | 22 +-- .../Portable/Diagnostics/DiagnosticData.cs | 10 +- .../Diagnostics/DiagnosticDataSerializer.cs | 31 ++-- .../Core/Portable/Diagnostics/Extensions.cs | 19 +++ .../Shared/Extensions/ProjectExtensions.cs | 17 +++ .../SolutionCrawler/IIncrementalAnalyzer2.cs | 24 +++ .../Core/Extensions/DocumentExtensions.cs | 2 +- 31 files changed, 509 insertions(+), 176 deletions(-) create mode 100644 src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs index 3ccc91692fde1..af03e0d23f713 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs @@ -34,7 +34,8 @@ public void DiagnosticAnalyzerAllInOne() missingSyntaxKinds.Add(SyntaxKind.FunctionPointerType); var analyzer = new CSharpTrackingDiagnosticAnalyzer(); - CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }); + var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray()); + CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }, options); analyzer.VerifyAllAnalyzerMembersWereCalled(); analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds(); analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(missingSyntaxKinds); diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb index afbe014eabcac..d246cc377540d 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb @@ -19,7 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics Public Sub DiagnosticAnalyzerAllInOne() Dim source = TestResource.AllInOneVisualBasicBaseline Dim analyzer = New BasicTrackingDiagnosticAnalyzer() - CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer}) + Dim options = New AnalyzerOptions({DirectCast(New TestAdditionalText(), AdditionalText)}.ToImmutableArray()) + CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer}, options) analyzer.VerifyAllAnalyzerMembersWereCalled() analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds() analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(New HashSet(Of SyntaxKind)()) diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index 691120f393770..c779d57265197 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -50,7 +50,9 @@ public async Task DiagnosticAnalyzerDriverAllInOne() using var workspace = TestWorkspace.CreateCSharp(source, TestOptions.Regular); var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer)); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); + var newSolution = workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference }) + .Projects.Single().AddAdditionalDocument(name: "dummy.txt", text: "", filePath: "dummy.txt").Project.Solution; + workspace.TryApplyChanges(newSolution); var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); AccessSupportedDiagnostics(analyzer); diff --git a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs index 8ae114b651574..d41db3f891a5a 100644 --- a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs +++ b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.Utilities; @@ -73,7 +74,7 @@ public void Register(Workspace workspace) private async Task AnalyzeAsync() { var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS); - var diagnosticAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); + var incrementalAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); var solution = _workspace.CurrentSolution; var documentIds = _workspace.GetOpenDocumentIds().ToImmutableArray(); @@ -82,8 +83,8 @@ private async Task AnalyzeAsync() { foreach (var documentId in documentIds) { - var document = solution.GetDocument(documentId); - if (document == null) + var textDocument = solution.GetTextDocument(documentId); + if (textDocument == null) { continue; } @@ -92,8 +93,15 @@ private async Task AnalyzeAsync() await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false); // do actual analysis - await diagnosticAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); - await diagnosticAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false); + if (textDocument is Document document) + { + await incrementalAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); + await incrementalAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false); + } + else if (incrementalAnalyzer is IIncrementalAnalyzer2 incrementalAnalyzer2) + { + await incrementalAnalyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); + } // don't call project one. } diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 28e04485a4286..1b4bac408eed9 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -564,6 +565,105 @@ private async Task TestFullSolutionAnalysisForProjectAsync(Project project, bool Assert.Equal(expectAnalyzerExecuted, called); } + [Theory, CombinatorialData] + internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool testMultiple, BackgroundAnalysisScope analysisScope) + { + using var workspace = new AdhocWorkspace(); + var options = workspace.Options.WithChangedOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); + workspace.SetOptions(options); + + var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "CSharpProject", "CSharpProject", LanguageNames.CSharp); + var project = workspace.AddProject(projectInfo); + + var diagnosticSpan = new TextSpan(2, 2); + var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0001"); + var analyzers = ImmutableArray.Create(analyzer); + if (testMultiple) + { + analyzer = new AdditionalFileAnalyzer2(registerFromInitialize, diagnosticSpan, id: "ID0002"); + analyzers = analyzers.Add(analyzer); + } + + var analyzerReference = new AnalyzerImageReference(analyzers); + project = project.WithAnalyzerReferences(new[] { analyzerReference }) + .AddAdditionalDocument(name: "dummy.txt", text: "Additional File Text", filePath: "dummy.txt").Project; + if (testMultiple) + { + project = project.AddAdditionalDocument(name: "dummy2.txt", text: "Additional File2 Text", filePath: "dummy2.txt").Project; + } + + var applied = workspace.TryApplyChanges(project.Solution); + Assert.True(applied); + + // create listener/service/analyzer + var listener = new AsynchronousOperationListener(); + var service = new MyDiagnosticAnalyzerService(listener); + + var diagnostics = new ConcurrentSet(); + service.DiagnosticsUpdated += (s, e) => + { + diagnostics.AddRange(e.Diagnostics); + }; + + var incrementalAnalyzer = (DiagnosticIncrementalAnalyzer)service.CreateIncrementalAnalyzer(workspace); + var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault(); + + switch (analysisScope) + { + case BackgroundAnalysisScope.ActiveFile: + case BackgroundAnalysisScope.OpenFilesAndProjects: + workspace.OpenAdditionalDocument(firstAdditionalDocument.Id); + await incrementalAnalyzer.AnalyzeNonSourceDocumentAsync(firstAdditionalDocument, InvocationReasons.SyntaxChanged, CancellationToken.None); + break; + + case BackgroundAnalysisScope.FullSolution: + await incrementalAnalyzer.AnalyzeProjectAsync(project, semanticsChanged: true, InvocationReasons.Reanalyze, CancellationToken.None); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(analysisScope); + } + + await listener.ExpeditedWaitAsync(); + + var expectedCount = !testMultiple + ? 1 + : analysisScope == BackgroundAnalysisScope.FullSolution ? 4 : 2; + Assert.Equal(expectedCount, diagnostics.Count); + + for (var i = 0; i < analyzers.Length; i++) + { + analyzer = (AdditionalFileAnalyzer)analyzers[i]; + foreach (var additionalDoc in project.AdditionalDocuments) + { + var applicableDiagnostics = diagnostics.Where( + d => d.Id == analyzer.Descriptor.Id && d.DataLocation.OriginalFilePath == additionalDoc.FilePath); + + if (analysisScope != BackgroundAnalysisScope.FullSolution && + firstAdditionalDocument != additionalDoc) + { + Assert.Empty(applicableDiagnostics); + } + else + { + var diagnostic = Assert.Single(applicableDiagnostics); + Assert.Equal(diagnosticSpan, diagnostic.GetTextSpan()); + diagnostics.Remove(diagnostic); + } + } + } + + Assert.Empty(diagnostics); + } + + private class AdditionalFileAnalyzer2 : AdditionalFileAnalyzer + { + public AdditionalFileAnalyzer2(bool registerFromInitialize, TextSpan diagnosticSpan, string id) + : base(registerFromInitialize, diagnosticSpan, id) + { + } + } + [Theory, CombinatorialData] internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeSuppressor, BackgroundAnalysisScope analysisScope) { @@ -678,11 +778,19 @@ private static (bool, bool) CompilerAnalyzerResultSetter(bool syntax, bool seman return (syntax, semantic); } - private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, Document document) + private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument) { - await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); - await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); - await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); + await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); + } + + await analyzer.AnalyzeProjectAsync(textDocument.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); } private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index 28e91578978c7..47af60fbe9bd4 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -307,8 +307,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) var hostProject = this.GetTestProject(info.Id.ProjectId); var hostDocument = new TestHostDocument( text.ToString(), info.Name, info.SourceCodeKind, - info.Id, folders: info.Folders, - exportProvider: ExportProvider); + info.Id, folders: info.Folders, exportProvider: ExportProvider); hostProject.AddDocument(hostDocument); this.OnDocumentAdded(hostDocument.ToDocumentInfo()); } @@ -330,7 +329,7 @@ protected override void ApplyAdditionalDocumentTextChanged(DocumentId document, protected override void ApplyAdditionalDocumentAdded(DocumentInfo info, SourceText text) { var hostProject = this.GetTestProject(info.Id.ProjectId); - var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id); + var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, exportProvider: ExportProvider); hostProject.AddAdditionalDocument(hostDocument); this.OnAdditionalDocumentAdded(hostDocument.ToDocumentInfo()); } @@ -352,7 +351,7 @@ protected override void ApplyAnalyzerConfigDocumentTextChanged(DocumentId docume protected override void ApplyAnalyzerConfigDocumentAdded(DocumentInfo info, SourceText text) { var hostProject = this.GetTestProject(info.Id.ProjectId); - var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders); + var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders, exportProvider: ExportProvider); hostProject.AddAnalyzerConfigDocument(hostDocument); this.OnAnalyzerConfigDocumentAdded(hostDocument.ToDocumentInfo()); } diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb index 7dbf5e70b41b8..a218a6b30ac51 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb @@ -17,7 +17,9 @@ Public Class DiagnosticAnalyzerDriverTests Dim analyzer = New BasicTrackingDiagnosticAnalyzer() Using workspace = TestWorkspace.CreateVisualBasic(source) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) + Dim newSolution = workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}). + Projects.Single().AddAdditionalDocument(name:="dummy.txt", text:="", filePath:="dummy.txt").Project.Solution + workspace.TryApplyChanges(newSolution) Dim document = workspace.CurrentSolution.Projects.Single().Documents.Single() AccessSupportedDiagnostics(analyzer) diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index c7237894f9ede..1dd1b7d98dcf8 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -303,19 +303,22 @@ private static void AssertCompilation(Project project, Compilation compilation1) /// public static async Task> ComputeDiagnosticsAsync( DiagnosticAnalyzer analyzer, - Document document, + TextDocument textDocument, AnalysisKind kind, DiagnosticAnalyzerInfoCache analyzerInfoCache, CompilationWithAnalyzers? compilationWithAnalyzers, TextSpan? span, CancellationToken cancellationToken) { - var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + var document = textDocument as Document; + RoslynDebug.Assert(document != null || kind == AnalysisKind.Syntax, "We only support syntactic analysis for non-source documents"); + + var loadDiagnostic = await textDocument.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (analyzer == FileContentLoadAnalyzer.Instance) { return loadDiagnostic != null ? - SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, document)) : + SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, textDocument)) : SpecializedCollections.EmptyEnumerable(); } @@ -324,9 +327,11 @@ public static async Task> ComputeDiagnosticsAsync( return SpecializedCollections.EmptyEnumerable(); } - if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) + ImmutableArray diagnostics; + if (document != null && + analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { - var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( documentAnalyzer, document, kind, compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false); return diagnostics.ConvertToLocalDiagnostics(document); @@ -338,14 +343,14 @@ public static async Task> ComputeDiagnosticsAsync( if (kind == AnalysisKind.Syntax) { Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, - (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", compilationWithAnalyzers, document, analyzer, kind); + (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", compilationWithAnalyzers, textDocument, analyzer, kind); } return SpecializedCollections.EmptyEnumerable(); } // if project is not loaded successfully then, we disable semantic errors for compiler analyzers - if (kind != AnalysisKind.Syntax && analyzer.IsCompilerAnalyzer()) + if (kind != AnalysisKind.Syntax && analyzer.IsCompilerAnalyzer() && document != null) { var isEnabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); @@ -359,52 +364,61 @@ public static async Task> ComputeDiagnosticsAsync( // REVIEW: more unnecessary allocations just to get diagnostics per analyzer var singleAnalyzer = ImmutableArray.Create(analyzer); - var skippedAnalyzerInfo = document.Project.GetSkippedAnalyzersInfo(analyzerInfoCache); + var skippedAnalyzerInfo = textDocument.Project.GetSkippedAnalyzersInfo(analyzerInfoCache); ImmutableArray filteredIds; switch (kind) { case AnalysisKind.Syntax: - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - if (tree == null) + if (document != null) { - return SpecializedCollections.EmptyEnumerable(); - } - - var diagnostics = await compilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(tree, singleAnalyzer, cancellationToken).ConfigureAwait(false); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (tree == null) + { + return SpecializedCollections.EmptyEnumerable(); + } - if (diagnostics.IsDefaultOrEmpty) - { - Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (d, a, t) => $"{d.Id}, {d.Project.Id}, {a}, {t.Length}", document, analyzer, tree); + diagnostics = await compilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(tree, singleAnalyzer, cancellationToken).ConfigureAwait(false); } - else if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) + else { - diagnostics = diagnostics.Filter(filteredIds); - } + // Currently, we only support analysis for additional documents. In future, we may support analyzer config documents. + if (textDocument.Kind == TextDocumentKind.AdditionalDocument) + { + var filePath = textDocument.FilePath ?? textDocument.Name; + var additionalFile = compilationWithAnalyzers.AnalysisOptions.Options?.AdditionalFiles.FirstOrDefault(a => PathUtilities.Comparer.Equals(a.Path, filePath)); + if (additionalFile != null) + { + diagnostics = await compilationWithAnalyzers.GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile, singleAnalyzer, cancellationToken).ConfigureAwait(false); + break; + } + } - Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); - return diagnostics.ConvertToLocalDiagnostics(document); + return SpecializedCollections.EmptyEnumerable(); + } + break; case AnalysisKind.Semantic: - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var model = await document!.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (model == null) { return SpecializedCollections.EmptyEnumerable(); } diagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, span, singleAnalyzer, cancellationToken).ConfigureAwait(false); - - if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) - { - diagnostics = diagnostics.Filter(filteredIds); - } - - Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); - return diagnostics.ConvertToLocalDiagnostics(document); + break; default: throw ExceptionUtilities.UnexpectedValue(kind); } + + if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) + { + diagnostics = diagnostics.Filter(filteredIds); + } + + Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); + return diagnostics.ConvertToLocalDiagnostics(textDocument); } public static async Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( @@ -567,60 +581,38 @@ async Task VerifyDiagnosticLocationAsync(string id, Location location) } #endif - public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, Document targetDocument, TextSpan? span = null) + public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, TextDocument targetDocument, TextSpan? span = null) { - var project = targetDocument.Project; - - if (project.SupportsCompilation) - { - return ConvertToLocalDiagnosticsWithCompilation(); - } - - return ConvertToLocalDiagnosticsWithoutCompilation(); - - IEnumerable ConvertToLocalDiagnosticsWithoutCompilation() + foreach (var diagnostic in diagnostics) { - Contract.ThrowIfTrue(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) + if (!IsReportedInDocument(diagnostic, targetDocument)) { - var location = diagnostic.Location; - if (location.Kind != LocationKind.ExternalFile) - { - continue; - } - - var lineSpan = location.GetLineSpan(); - - var documentIds = project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); - if (documentIds.IsEmpty || documentIds.All(id => id != targetDocument.Id)) - { - continue; - } + continue; + } - yield return DiagnosticData.Create(diagnostic, targetDocument); + if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) + { + continue; } + + yield return DiagnosticData.Create(diagnostic, targetDocument); } - IEnumerable ConvertToLocalDiagnosticsWithCompilation() + static bool IsReportedInDocument(Diagnostic diagnostic, TextDocument targetDocument) { - Contract.ThrowIfFalse(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) + if (diagnostic.Location.SourceTree != null) { - var document = project.GetDocument(diagnostic.Location.SourceTree); - if (document == null || document != targetDocument) - { - continue; - } - - if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) - { - continue; - } + return targetDocument.Project.GetDocument(diagnostic.Location.SourceTree) == targetDocument; + } + else if (diagnostic.Location.Kind == LocationKind.ExternalFile) + { + var lineSpan = diagnostic.Location.GetLineSpan(); - yield return DiagnosticData.Create(diagnostic, document); + var documentIds = targetDocument.Project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); + return documentIds.Any(id => id == targetDocument.Id); } + + return false; } } @@ -708,6 +700,7 @@ public override void RegisterOperationAction(Action ac public override void RegisterOperationBlockAction(Action action) { } public override void RegisterOperationBlockStartAction(Action action) { } public override void RegisterSymbolStartAction(Action action, SymbolKind symbolKind) { } + public override void RegisterAdditionalFileAction(Action action) { } #endregion private class CollectNestedCompilationContext : CompilationStartAnalysisContext @@ -740,6 +733,7 @@ public override void RegisterOperationAction(Action ac public override void RegisterOperationBlockAction(Action action) { } public override void RegisterOperationBlockStartAction(Action action) { } public override void RegisterSymbolStartAction(Action action, SymbolKind symbolKind) { } + public override void RegisterAdditionalFileAction(Action action) { } #endregion } } diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index 127f4d84c5e10..e34b02c4e4c7e 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -59,7 +59,7 @@ public ImmutableArray GetDiagnostics(Workspace workspace, Projec internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs state) => DiagnosticsUpdated?.Invoke(this, state); - private class DefaultDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer + private class DefaultDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 { private readonly DefaultDiagnosticAnalyzerService _service; private readonly Workspace _workspace; @@ -82,7 +82,13 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs return false; } - public async Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) + public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeSyntaxOrNonSourceDocumentAsync(document, cancellationToken); + + public Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeSyntaxOrNonSourceDocumentAsync(document, cancellationToken); + + private async Task AnalyzeSyntaxOrNonSourceDocumentAsync(TextDocument document, CancellationToken cancellationToken) { Debug.Assert(document.Project.Solution.Workspace == _workspace); @@ -124,7 +130,7 @@ bool IsSemanticAnalysisOn() } } - private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, CancellationToken cancellationToken) + private async Task AnalyzeForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { var diagnosticData = await GetDiagnosticsAsync(document, kind, cancellationToken).ConfigureAwait(false); @@ -146,7 +152,7 @@ private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, Can /// that provide all kinds of knobs/cache/persistency/OOP to get better perf over simplicity. /// private async Task> GetDiagnosticsAsync( - Document document, AnalysisKind kind, CancellationToken cancellationToken) + TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (loadDiagnostic != null) @@ -203,9 +209,18 @@ public Task DocumentResetAsync(Document document, CancellationToken cancellation return RemoveDocumentAsync(document.Id, cancellationToken); } + public Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + { + // no closed file diagnostic and file is not opened, remove any existing diagnostics + return RemoveDocumentAsync(document.Id, cancellationToken); + } + public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) => DocumentResetAsync(document, cancellationToken); + public Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + => NonSourceDocumentResetAsync(document, cancellationToken); + private void RaiseEmptyDiagnosticUpdated(AnalysisKind kind, DocumentId documentId) { _service.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( @@ -218,6 +233,9 @@ public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, Invocati public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) => Task.CompletedTask; + public Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + => Task.CompletedTask; + public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs index 246697b293e36..c7b55e5a38110 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs @@ -23,6 +23,7 @@ private readonly struct Data public readonly int CompilationEndActionsCount; public readonly int CompilationActionsCount; public readonly int SyntaxTreeActionsCount; + public readonly int AdditionalFileActionsCount; public readonly int SemanticModelActionsCount; public readonly int SymbolActionsCount; public readonly int SymbolStartActionsCount; @@ -51,6 +52,7 @@ public Data(AnalyzerTelemetryInfo analyzerTelemetryInfo, bool isTelemetryCollect SymbolActionsCount = analyzerTelemetryInfo.SymbolActionsCount; SyntaxNodeActionsCount = analyzerTelemetryInfo.SyntaxNodeActionsCount; SyntaxTreeActionsCount = analyzerTelemetryInfo.SyntaxTreeActionsCount; + AdditionalFileActionsCount = analyzerTelemetryInfo.AdditionalFileActionsCount; OperationActionsCount = analyzerTelemetryInfo.OperationActionsCount; OperationBlockActionsCount = analyzerTelemetryInfo.OperationBlockActionsCount; OperationBlockEndActionsCount = analyzerTelemetryInfo.OperationBlockEndActionsCount; @@ -114,6 +116,7 @@ public void ReportAndClear(int correlationId) m["Analyzer.SemanticModel"] = analyzerInfo.SemanticModelActionsCount; m["Analyzer.SyntaxNode"] = analyzerInfo.SyntaxNodeActionsCount; m["Analyzer.SyntaxTree"] = analyzerInfo.SyntaxTreeActionsCount; + m["Analyzer.AdditionalFile"] = analyzerInfo.AdditionalFileActionsCount; m["Analyzer.Operation"] = analyzerInfo.OperationActionsCount; m["Analyzer.OperationBlock"] = analyzerInfo.OperationBlockActionsCount; m["Analyzer.OperationBlockStart"] = analyzerInfo.OperationBlockStartActionsCount; diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs index af68a61c56533..7b00eba9f8ff2 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs @@ -148,6 +148,7 @@ private static void WriteTelemetry(ObjectWriter writer, AnalyzerTelemetryInfo te writer.WriteInt32(telemetryInfo.CompilationEndActionsCount); writer.WriteInt32(telemetryInfo.CompilationActionsCount); writer.WriteInt32(telemetryInfo.SyntaxTreeActionsCount); + writer.WriteInt32(telemetryInfo.AdditionalFileActionsCount); writer.WriteInt32(telemetryInfo.SemanticModelActionsCount); writer.WriteInt32(telemetryInfo.SymbolActionsCount); writer.WriteInt32(telemetryInfo.SymbolStartActionsCount); @@ -173,6 +174,7 @@ private static AnalyzerTelemetryInfo ReadTelemetry(ObjectReader reader, Cancella var compilationEndActionsCount = reader.ReadInt32(); var compilationActionsCount = reader.ReadInt32(); var syntaxTreeActionsCount = reader.ReadInt32(); + var additionalFileActionsCount = reader.ReadInt32(); var semanticModelActionsCount = reader.ReadInt32(); var symbolActionsCount = reader.ReadInt32(); var symbolStartActionsCount = reader.ReadInt32(); @@ -196,6 +198,7 @@ private static AnalyzerTelemetryInfo ReadTelemetry(ObjectReader reader, Cancella CompilationActionsCount = compilationActionsCount, SyntaxTreeActionsCount = syntaxTreeActionsCount, + AdditionalFileActionsCount = additionalFileActionsCount, SemanticModelActionsCount = semanticModelActionsCount, SymbolActionsCount = symbolActionsCount, SymbolStartActionsCount = symbolStartActionsCount, diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index c308fd4b66447..e946e676d380d 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -26,7 +26,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) either from cache or by calculating them /// private async Task GetDocumentAnalysisDataAsync( - CompilationWithAnalyzers? compilation, Document document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilation, TextDocument document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) { // get log title and functionId GetLogFunctionIdAndTitle(kind, out var functionId, out var title); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index cf79282a1934e..7dcd99ec13024 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; @@ -111,7 +112,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor /// /// Return all diagnostics for the given document stored in this state including non local diagnostics for this document /// - public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, Document document, bool avoidLoadingData, CancellationToken cancellationToken) + public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; @@ -202,7 +203,7 @@ public async Task SaveAsync(IPersistentStorageService persistentService, Project var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, result.Version); foreach (var documentId in result.DocumentIds) { - var document = project.GetDocument(documentId); + var document = project.GetTextDocument(documentId); if (document == null) { // it can happen with build synchronization since, in build case, @@ -229,7 +230,7 @@ public void ResetVersion() _lastResult = _lastResult.Reset(); } - public async Task MergeAsync(IPersistentStorageService persistentService, ActiveFileState state, Document document) + public async Task MergeAsync(IPersistentStorageService persistentService, ActiveFileState state, TextDocument document) { Contract.ThrowIfFalse(state.DocumentId == document.Id); @@ -312,7 +313,7 @@ private async Task LoadInitialAnalysisDataAsync(IPersi return builder.ToResult(); } - private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, Document document, CancellationToken cancellationToken) + private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, CancellationToken cancellationToken) { // loading data can be cancelled any time. var project = document.Project; @@ -344,7 +345,7 @@ private async Task LoadInitialProjectAnalysisDataAsync return builder.ToResult(); } - private async Task SerializeAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Document? document, object key, string stateKey, ImmutableArray diagnostics) + private async Task SerializeAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, ImmutableArray diagnostics) { Contract.ThrowIfFalse(document == null || document.Project == project); @@ -360,7 +361,7 @@ private async Task SerializeAsync(IPersistentStorageService persistentService, D InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializer.Version, diagnostics)); } - private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken) + private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, TextDocument document, Builder builder, CancellationToken cancellationToken) { var success = true; var project = document.Project; @@ -411,7 +412,7 @@ private async Task TryDeserializeProjectDiagnosticsAsync(IPersistentStorag return false; } - private ValueTask> DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Document? document, object key, string stateKey, CancellationToken cancellationToken) + private ValueTask> DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, CancellationToken cancellationToken) { Contract.ThrowIfFalse(document == null || document.Project == project); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 3b174d098f4d4..a0d338bc57271 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -192,7 +192,7 @@ public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) return stateSets.ToImmutable(); } - public static bool OnDocumentReset(IEnumerable stateSets, Document document) + public static bool OnDocumentReset(IEnumerable stateSets, TextDocument document) { // can not be cancelled var removed = false; @@ -204,7 +204,7 @@ public static bool OnDocumentReset(IEnumerable stateSets, Document doc return removed; } - public async Task OnDocumentOpenedAsync(IEnumerable stateSets, Document document) + public async Task OnDocumentOpenedAsync(IEnumerable stateSets, TextDocument document) { // can not be cancelled var opened = false; @@ -216,7 +216,7 @@ public async Task OnDocumentOpenedAsync(IEnumerable stateSets, D return opened; } - public async Task OnDocumentClosedAsync(IEnumerable stateSets, Document document) + public async Task OnDocumentClosedAsync(IEnumerable stateSets, TextDocument document) { // can not be cancelled var removed = false; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs index 171823b7fff72..7c4873479858f 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -138,7 +138,7 @@ public ActiveFileState GetOrCreateActiveFileState(DocumentId documentId) public ProjectState GetOrCreateProjectState(ProjectId projectId) => _projectStates.GetOrAdd(projectId, id => new ProjectState(this, id)); - public async Task OnDocumentOpenedAsync(IPersistentStorageService persistentStorageService, Document document) + public async Task OnDocumentOpenedAsync(IPersistentStorageService persistentStorageService, TextDocument document) { // can not be cancelled if (!TryGetProjectState(document.Project.Id, out var projectState) || @@ -159,7 +159,7 @@ public async Task OnDocumentOpenedAsync(IPersistentStorageService persiste return true; } - public async Task OnDocumentClosedAsync(IPersistentStorageService persistentStorageService, Document document) + public async Task OnDocumentClosedAsync(IPersistentStorageService persistentStorageService, TextDocument document) { // can not be cancelled // remove active file state and put it in project state @@ -174,7 +174,7 @@ public async Task OnDocumentClosedAsync(IPersistentStorageService persiste return true; } - public bool OnDocumentReset(Document document) + public bool OnDocumentReset(TextDocument document) { var changed = false; // can not be cancelled diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 92994854a0fca..a7cb47e034732 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -29,7 +29,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 /// /// This one follows pattern compiler has set for diagnostic analyzer. /// - internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 { private readonly int _correlationId; private readonly DiagnosticAnalyzerTelemetry _telemetry; @@ -187,7 +187,7 @@ private void RaiseDiagnosticsRemoved( } private void RaiseDiagnosticsCreated( - Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) { Contract.ThrowIfFalse(document.Project.Solution.Workspace == Workspace); @@ -238,16 +238,16 @@ public void LogAnalyzerCountSummary() internal IEnumerable GetAnalyzersTestOnly(Project project) => _stateManager.GetOrCreateStateSets(project).Select(s => s.Analyzer); - private static string GetDocumentLogMessage(string title, Document document, DiagnosticAnalyzer analyzer) + private static string GetDocumentLogMessage(string title, TextDocument document, DiagnosticAnalyzer analyzer) => $"{title}: ({document.Id}, {document.Project.Id}), ({analyzer})"; private static string GetProjectLogMessage(Project project, IEnumerable stateSets) => $"project: ({project.Id}), ({string.Join(Environment.NewLine, stateSets.Select(s => s.Analyzer.ToString()))})"; - private static string GetResetLogMessage(Document document) + private static string GetResetLogMessage(TextDocument document) => $"document close/reset: ({document.FilePath ?? document.Name})"; - private static string GetOpenLogMessage(Document document) + private static string GetOpenLogMessage(TextDocument document) => $"document open: ({document.FilePath ?? document.Name})"; private static string GetRemoveLogMessage(DocumentId id) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index cb6b5f0be52a2..f104babdf67e5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -32,7 +32,10 @@ public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, Can public Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) => AnalyzeDocumentForKindAsync(document, AnalysisKind.Semantic, cancellationToken); - private async Task AnalyzeDocumentForKindAsync(Document document, AnalysisKind kind, CancellationToken cancellationToken) + public Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeDocumentForKindAsync(textDocument, AnalysisKind.Syntax, cancellationToken); + + private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { try { @@ -133,7 +136,13 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C } } - public async Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + => TextDocumentOpenAsync(document, cancellationToken); + + public Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentOpenAsync(document, cancellationToken); + + private async Task TextDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken)) { @@ -145,7 +154,13 @@ public async Task DocumentOpenAsync(Document document, CancellationToken cancell } } - public async Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) + public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) + => TextDocumentCloseAsync(document, cancellationToken); + + public Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentCloseAsync(document, cancellationToken); + + private async Task TextDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentClose, GetResetLogMessage, document, cancellationToken)) { @@ -159,6 +174,12 @@ public async Task DocumentCloseAsync(Document document, CancellationToken cancel } public Task DocumentResetAsync(Document document, CancellationToken cancellationToken) + => TextDocumentResetAsync(document, cancellationToken); + + public Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentResetAsync(document, cancellationToken); + + private Task TextDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentReset, GetResetLogMessage, document, cancellationToken)) { @@ -173,7 +194,7 @@ public Task DocumentResetAsync(Document document, CancellationToken cancellation return Task.CompletedTask; } - private void RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(Document document, IEnumerable stateSets, bool documentHadDiagnostics) + private void RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(TextDocument document, IEnumerable stateSets, bool documentHadDiagnostics) { // if there was no diagnostic reported for this document OR Full solution analysis is enabled, nothing to clean up if (!documentHadDiagnostics || @@ -266,7 +287,7 @@ public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancel return Task.CompletedTask; } - private static bool AnalysisEnabled(Document document) + private static bool AnalysisEnabled(TextDocument document) { if (document.Services.GetService()?.DiagnosticsLspClientName != null) { @@ -407,17 +428,17 @@ private void RaiseProjectDiagnosticsIfNeeded( }); } - private void RaiseDocumentDiagnosticsIfNeeded(Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) + private void RaiseDocumentDiagnosticsIfNeeded(TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) => RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, ImmutableArray.Empty, items); private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) { RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, AnalyzerService.RaiseDiagnosticsUpdated, forceUpdate: false); } private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, + TextDocument document, StateSet stateSet, AnalysisKind kind, DiagnosticAnalysisResult oldResult, DiagnosticAnalysisResult newResult, Action raiseEvents) { @@ -438,7 +459,7 @@ private void RaiseDocumentDiagnosticsIfNeeded( } private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems, Action raiseEvents, bool forceUpdate) @@ -458,7 +479,7 @@ private void RaiseProjectDiagnosticsCreated(Project project, StateSet stateSet, foreach (var documentId in newAnalysisResult.DocumentIds) { - var document = project.GetDocument(documentId); + var document = project.GetTextDocument(documentId); if (document == null) { // it can happen with build synchronization since, in build case, @@ -505,7 +526,7 @@ private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId project RaiseDiagnosticsRemoved(projectId, solution: null, stateSet, raiseEvents); } - private async Task ReportAnalyzerPerformanceAsync(Document document, CompilationWithAnalyzers? compilation, CancellationToken cancellationToken) + private async Task ReportAnalyzerPerformanceAsync(TextDocument document, CompilationWithAnalyzers? compilation, CancellationToken cancellationToken) { try { diff --git a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs index 5c4aef52807d2..a8cf77d146f8c 100644 --- a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler { - internal class AggregateIncrementalAnalyzer : IIncrementalAnalyzer + internal class AggregateIncrementalAnalyzer : IIncrementalAnalyzer2 { public readonly ImmutableDictionary> Analyzers; @@ -126,5 +126,41 @@ public async Task RemoveProjectAsync(ProjectId projectId, CancellationToken canc } } } + + public async Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentOpenAsync(document, cancellationToken).ConfigureAwait(false); + } + } + + public async Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentCloseAsync(document, cancellationToken).ConfigureAwait(false); + } + } + + public async Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentResetAsync(document, cancellationToken).ConfigureAwait(false); + } + } + + public async Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(document, reasons, cancellationToken).ConfigureAwait(false); + } + } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index 54910f9581b5a..84217fe489888 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -237,13 +237,19 @@ private void ReportPendingWorkItemCount() } private async Task ProcessDocumentAnalyzersAsync( - Document document, ImmutableArray analyzers, WorkItem workItem, CancellationToken cancellationToken) + TextDocument textDocument, ImmutableArray analyzers, WorkItem workItem, CancellationToken cancellationToken) { // process all analyzers for each categories in this order - syntax, body, document var reasons = workItem.InvocationReasons; if (workItem.MustRefresh || reasons.Contains(PredefinedInvocationReasons.SyntaxChanged)) { - await RunAnalyzersAsync(analyzers, document, workItem, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false); + await RunAnalyzersAsync(analyzers, textDocument, workItem, (a, d, c) => AnalyzeSyntaxAsync(a, d, reasons, c), cancellationToken).ConfigureAwait(false); + } + + if (!(textDocument is Document document)) + { + // Semantic analysis is not supported for non-source documents. + return; } if (workItem.MustRefresh || reasons.Contains(PredefinedInvocationReasons.SemanticChanged)) @@ -255,6 +261,20 @@ private async Task ProcessDocumentAnalyzersAsync( // if we don't need to re-analyze whole body, see whether we need to at least re-analyze one method. await RunBodyAnalyzersAsync(analyzers, workItem, document, cancellationToken).ConfigureAwait(false); } + + return; + + static async Task AnalyzeSyntaxAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync(document, reasons, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); + } + } } private async Task RunAnalyzersAsync( diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 5844f27fa6fbe..5ecee271076fe 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -334,7 +334,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) { - var document = solution.GetDocument(documentId); + var document = solution.GetTextDocument(documentId); if (document != null) { @@ -389,7 +389,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana } } - private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, Document document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument document, bool isOpen, CancellationToken cancellationToken) { if (!isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentOpened)) { @@ -398,10 +398,23 @@ private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray a.DocumentOpenAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(analyzers, document, workItem, DocumentOpenAsync, cancellationToken).ConfigureAwait(false); + return; + + static async Task DocumentOpenAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentOpenAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentOpenAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } } - private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, Document document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument document, bool isOpen, CancellationToken cancellationToken) { if (isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentClosed)) { @@ -410,10 +423,23 @@ private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray a.DocumentCloseAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(analyzers, document, workItem, DocumentCloseAsync, cancellationToken).ConfigureAwait(false); + return; + + static async Task DocumentCloseAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentCloseAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentCloseAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } } - private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document document, CancellationToken cancellationToken) + private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, TextDocument document, CancellationToken cancellationToken) { try { @@ -421,7 +447,7 @@ private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document doc Debug.Assert(!workItem.InvocationReasons.Contains(PredefinedInvocationReasons.Reanalyze) || workItem.SpecificAnalyzers.Count > 0); #endif - // no-reanalyze request or we already have a request to re-analyze every thing + // No-reanalyze request or we already have a request to re-analyze every thing if (workItem.MustRefresh || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.Reanalyze)) { return; @@ -429,25 +455,53 @@ private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document doc // First reset the document state in analyzers. var reanalyzers = workItem.SpecificAnalyzers.ToImmutableArray(); - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.DocumentResetAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, DocumentResetAsync, cancellationToken).ConfigureAwait(false); - // no request to re-run syntax change analysis. run it here + // No request to re-run syntax change analysis. run it here var reasons = workItem.InvocationReasons; if (!reasons.Contains(PredefinedInvocationReasons.SyntaxChanged)) { - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => AnalyzeSyntaxAsync(a, d, reasons, c), cancellationToken).ConfigureAwait(false); } - // no request to re-run semantic change analysis. run it here - if (!workItem.InvocationReasons.Contains(PredefinedInvocationReasons.SemanticChanged)) + // No request to re-run semantic change analysis. run it here + // Note: Semantic analysis is not supported for non-source documents. + if (document is Document sourceDocument && + !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.SemanticChanged)) { - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.AnalyzeDocumentAsync(d, null, reasons, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, sourceDocument, workItem, (a, d, c) => a.AnalyzeDocumentAsync(d, null, reasons, c), cancellationToken).ConfigureAwait(false); } } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } + + return; + + static async Task DocumentResetAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentResetAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentResetAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } + + static async Task AnalyzeSyntaxAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync((Document)document, reasons, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); + } + } } private Task RemoveDocumentAsync(DocumentId documentId, CancellationToken cancellationToken) diff --git a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs index da4802867cc3a..42e1b857bd6d2 100644 --- a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs +++ b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs @@ -2146,7 +2146,7 @@ private void OnOperationBlockStart(OperationBlockStartAnalysisContext context) } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class AdditionalFileAnalyzer : DiagnosticAnalyzer + public class AdditionalFileAnalyzer : DiagnosticAnalyzer { private readonly bool _registerFromInitialize; private readonly TextSpan _diagnosticSpan; diff --git a/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs index 20ff953ba976e..817b030f828f3 100644 --- a/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs @@ -464,6 +464,7 @@ private static void WriteTelemetry(string analyzerName, AnalyzerTelemetryInfo te WriteLine($"Symbol End Actions: {telemetry.SymbolEndActionsCount}", ConsoleColor.White); WriteLine($"Syntax Node Actions: {telemetry.SyntaxNodeActionsCount}", ConsoleColor.White); WriteLine($"Syntax Tree Actions: {telemetry.SyntaxTreeActionsCount}", ConsoleColor.White); + WriteLine($"Additional File Actions: {telemetry.AdditionalFileActionsCount}", ConsoleColor.White); WriteLine($"Suppression Actions: {telemetry.SuppressionActionsCount}", ConsoleColor.White); } diff --git a/src/Tools/AnalyzerRunner/Extensions.cs b/src/Tools/AnalyzerRunner/Extensions.cs index 80cf66d64818a..d4156887be9b3 100644 --- a/src/Tools/AnalyzerRunner/Extensions.cs +++ b/src/Tools/AnalyzerRunner/Extensions.cs @@ -26,6 +26,7 @@ internal static void Add(this AnalyzerTelemetryInfo analyzerTelemetryInfo, Analy analyzerTelemetryInfo.SymbolEndActionsCount += addendum.SymbolEndActionsCount; analyzerTelemetryInfo.SyntaxNodeActionsCount += addendum.SyntaxNodeActionsCount; analyzerTelemetryInfo.SyntaxTreeActionsCount += addendum.SyntaxTreeActionsCount; + analyzerTelemetryInfo.AdditionalFileActionsCount += addendum.AdditionalFileActionsCount; analyzerTelemetryInfo.SuppressionActionsCount += addendum.SuppressionActionsCount; } } diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs index ebed68d65ba84..c21cf402f684e 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Workspaces.Diagnostics @@ -318,7 +319,7 @@ private static void VerifyDocumentMap(Project project, ImmutableDictionary diagnostics) { - // this is for diagnostic producer that doesnt use compiler based DiagnosticAnalyzer such as TypeScript. AddExternalDiagnostics(ref _lazySyntaxLocals, documentId, diagnostics); } public void AddExternalSemanticDiagnostics(DocumentId documentId, IEnumerable diagnostics) { // this is for diagnostic producer that doesnt use compiler based DiagnosticAnalyzer such as TypeScript. + Contract.ThrowIfTrue(Project.SupportsCompilation); + AddExternalDiagnostics(ref _lazySemanticLocals, documentId, diagnostics); } private void AddExternalDiagnostics( ref Dictionary>? lazyLocals, DocumentId documentId, IEnumerable diagnostics) { - Contract.ThrowIfTrue(Project.SupportsCompilation); - foreach (var diagnostic in diagnostics) { // REVIEW: what is our plan for additional locations? @@ -73,16 +73,16 @@ private void AddExternalDiagnostics( { case LocationKind.ExternalFile: { - var diagnosticDocumentId = GetExternalDocumentId(Project, diagnostic); + var diagnosticDocumentId = Project.GetDocumentForExternalLocation(diagnostic.Location); if (documentId == diagnosticDocumentId) { // local diagnostics to a file - AddDocumentDiagnostic(ref lazyLocals, Project.GetDocument(diagnosticDocumentId), diagnostic); + AddDocumentDiagnostic(ref lazyLocals, Project.GetTextDocument(diagnosticDocumentId), diagnostic); } else if (diagnosticDocumentId != null) { // non local diagnostics to a file - AddDocumentDiagnostic(ref _lazyNonLocals, Project.GetDocument(diagnosticDocumentId), diagnostic); + AddDocumentDiagnostic(ref _lazyNonLocals, Project.GetTextDocument(diagnosticDocumentId), diagnostic); } else { @@ -109,7 +109,7 @@ private void AddExternalDiagnostics( } } - private void AddDocumentDiagnostic(ref Dictionary>? map, Document? document, Diagnostic diagnostic) + private void AddDocumentDiagnostic(ref Dictionary>? map, TextDocument? document, Diagnostic diagnostic) { if (document is null || !document.SupportsDiagnostics()) { @@ -191,14 +191,6 @@ private void AddDiagnostics( } } - private static DocumentId GetExternalDocumentId(Project project, Diagnostic diagnostic) - { - var projectId = project.Id; - var lineSpan = diagnostic.Location.GetLineSpan(); - - return project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path).FirstOrDefault(id => id.ProjectId == projectId); - } - private static ImmutableDictionary> Convert(Dictionary>? map) { return map == null ? diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs index 14ca51dbd6ed1..934f8e10f3cdd 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs @@ -301,7 +301,7 @@ private static void SwapIfNeeded(ref LinePosition startLinePosition, ref LinePos } } - private static DiagnosticDataLocation? CreateLocation(Document? document, Location location) + private static DiagnosticDataLocation? CreateLocation(TextDocument? document, Location location) { if (document == null) { @@ -337,7 +337,7 @@ public static DiagnosticData Create(Diagnostic diagnostic, Project project) return Create(diagnostic, project.Id, project.Language, project.Solution.Options, location: null, additionalLocations: null, additionalProperties: null); } - public static DiagnosticData Create(Diagnostic diagnostic, Document document) + public static DiagnosticData Create(Diagnostic diagnostic, TextDocument document) { var project = document.Project; var location = CreateLocation(document, diagnostic.Location); @@ -403,9 +403,9 @@ private static DiagnosticData Create( isSuppressed: diagnostic.IsSuppressed); } - private static ImmutableDictionary? GetAdditionalProperties(Document document, Diagnostic diagnostic) + private static ImmutableDictionary? GetAdditionalProperties(TextDocument document, Diagnostic diagnostic) { - var service = document.GetLanguageService(); + var service = document.Project.GetLanguageService(); return service?.GetAdditionalProperties(diagnostic); } @@ -465,7 +465,7 @@ private static DiagnosticSeverity GetEffectiveSeverity(ReportDiagnostic effectiv } } - private static void GetLocationInfo(Document document, Location location, out TextSpan sourceSpan, out FileLinePositionSpan originalLineInfo, out FileLinePositionSpan mappedLineInfo) + private static void GetLocationInfo(TextDocument document, Location location, out TextSpan sourceSpan, out FileLinePositionSpan originalLineInfo, out FileLinePositionSpan mappedLineInfo) { var diagnosticSpanMappingService = document.Project.Solution.Workspace.Services.GetService(); if (diagnosticSpanMappingService != null) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs index 5741cee5afee8..9e657d9263f0b 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs @@ -39,9 +39,9 @@ public DiagnosticDataSerializer(VersionStamp analyzerVersion, VersionStamp versi Version = version; } - public async Task SerializeAsync(IPersistentStorageService persistentService, Project project, Document? document, string key, ImmutableArray items, CancellationToken cancellationToken) + public async Task SerializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, ImmutableArray items, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(document == null || document.Project == project); + Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); using var stream = SerializableBytes.CreateWritableStream(); @@ -54,21 +54,28 @@ public async Task SerializeAsync(IPersistentStorageService persistentServi stream.Position = 0; - var writeTask = (document != null) ? - storage.WriteStreamAsync(document, key, stream, cancellationToken) : + var writeTask = (textDocument != null) ? + textDocument is Document document ? + storage.WriteStreamAsync(document, key, stream, cancellationToken) : + storage.WriteStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), stream, cancellationToken) : storage.WriteStreamAsync(project, key, stream, cancellationToken); return await writeTask.ConfigureAwait(false); } - public async ValueTask> DeserializeAsync(IPersistentStorageService persistentService, Project project, Document? document, string key, CancellationToken cancellationToken) + private static string GetSerializationKeyForNonSourceDocument(TextDocument document, string key) + => document.Id + ";" + key; + + public async ValueTask> DeserializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(document == null || document.Project == project); + Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); using var storage = persistentService.GetStorage(project.Solution); - var readTask = (document != null) ? - storage.ReadStreamAsync(document, key, cancellationToken) : + var readTask = (textDocument != null) ? + textDocument is Document document ? + storage.ReadStreamAsync(document, key, cancellationToken) : + storage.ReadStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), cancellationToken) : storage.ReadStreamAsync(project, key, cancellationToken); using var stream = await readTask.ConfigureAwait(false); @@ -79,7 +86,7 @@ public async ValueTask> DeserializeAsync(IPersist return default; } - return ReadDiagnosticData(reader, project, document, cancellationToken); + return ReadDiagnosticData(reader, project, textDocument, cancellationToken); } public void WriteDiagnosticData(ObjectWriter writer, ImmutableArray items, CancellationToken cancellationToken) @@ -176,7 +183,7 @@ private static void WriteLocation(ObjectWriter writer, DiagnosticDataLocation? i writer.WriteInt32(item.MappedEndColumn); } - public ImmutableArray ReadDiagnosticData(ObjectReader reader, Project project, Document? document, CancellationToken cancellationToken) + public ImmutableArray ReadDiagnosticData(ObjectReader reader, Project project, TextDocument? document, CancellationToken cancellationToken) { try { @@ -207,7 +214,7 @@ public ImmutableArray ReadDiagnosticData(ObjectReader reader, Pr } } - private static ImmutableArray ReadDiagnosticDataArray(ObjectReader reader, Project project, Document? document, CancellationToken cancellationToken) + private static ImmutableArray ReadDiagnosticDataArray(ObjectReader reader, Project project, TextDocument? document, CancellationToken cancellationToken) { var count = reader.ReadInt32(); if (count == 0) @@ -272,7 +279,7 @@ private static ImmutableArray ReadDiagnosticDataArray(ObjectRead return builder.ToImmutableAndFree(); } - private static DiagnosticDataLocation? ReadLocation(Project project, ObjectReader reader, Document? document) + private static DiagnosticDataLocation? ReadLocation(Project project, ObjectReader reader, TextDocument? document) { var exists = reader.ReadBoolean(); if (!exists) diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 43f7a8a782ce6..0cde7b6e4edfb 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; @@ -174,6 +175,24 @@ public static ImmutableDictionary IsForkedProjectWithSemanticChangesAsync(this Proj internal static Project WithSolutionOptions(this Project project, OptionSet options) => project.Solution.WithOptions(options).GetProject(project.Id)!; + + public static TextDocument? GetTextDocument(this Project project, DocumentId? documentId) + => project.Solution.GetTextDocument(documentId); + + internal static DocumentId? GetDocumentForExternalLocation(this Project project, Location location) + { + Debug.Assert(location.Kind == LocationKind.ExternalFile); + return project.GetDocumentIdWithFilePath(location.GetLineSpan().Path); + } + + internal static DocumentId? GetDocumentForFile(this Project project, AdditionalText additionalText) + => project.GetDocumentIdWithFilePath(additionalText.Path); + + private static DocumentId? GetDocumentIdWithFilePath(this Project project, string filePath) + => project.Solution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(id => id.ProjectId == project.Id); } } diff --git a/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs b/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs new file mode 100644 index 0000000000000..0423fce9120da --- /dev/null +++ b/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs @@ -0,0 +1,24 @@ +// 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. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.SolutionCrawler +{ + // CONSIDER: We can merge IIncrementalAnalyzer2 with IIncrementalAnalyzer once all of our + // IVT partners that use IIncrementalAnalyzer have migrated to ExternalAccess layer. + internal interface IIncrementalAnalyzer2 : IIncrementalAnalyzer + { + Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken); + Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken); + + /// + /// Resets all the document state cached by the analyzer. + /// + Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken); + + Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs index e4a5b9310dfe4..bae54bfa04efd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs @@ -58,7 +58,7 @@ public static async Task GetRequiredSyntaxRootAsync(this Document do return root ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } - public static bool IsOpen(this Document document) + public static bool IsOpen(this TextDocument document) { var workspace = document.Project.Solution.Workspace as Workspace; return workspace != null && workspace.IsDocumentOpen(document.Id); From bdc7a4a9ad8678eb37f21052fc8cc2c15d6c0028 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 22 Jun 2020 07:44:23 -0700 Subject: [PATCH 03/11] Update resource string for invalid additional file --- src/Compilers/Core/Portable/CodeAnalysisResources.resx | 4 ++-- .../DiagnosticAnalyzer/CompilationWithAnalyzers.cs | 2 +- .../Core/Portable/xlf/CodeAnalysisResources.cs.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.de.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.es.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.fr.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.it.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.ja.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.ko.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.pl.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.ru.xlf | 10 +++++----- .../Core/Portable/xlf/CodeAnalysisResources.tr.xlf | 10 +++++----- .../Portable/xlf/CodeAnalysisResources.zh-Hans.xlf | 10 +++++----- .../Portable/xlf/CodeAnalysisResources.zh-Hant.xlf | 10 +++++----- 15 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index d115074cfea40..d261b625c3c9d 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -556,8 +556,8 @@ Syntax tree doesn't belong to the underlying 'Compilation'. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. Resource stream ended at {0} bytes, expected {1} bytes. diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index 02140b1bc5b1a..fbc8013108116 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -254,7 +254,7 @@ private void VerifyAdditionalFile(AdditionalText file) if (_analysisOptions.Options == null || !_analysisOptions.Options.AdditionalFiles.Contains(file)) { - throw new ArgumentException(CodeAnalysisResources.InvalidNonSourceFile, nameof(file)); + throw new ArgumentException(CodeAnalysisResources.InvalidAdditionalFile, nameof(file)); } } diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf index 75a74d2b43e65..e142530153807 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Potlačené ID diagnostiky {0} neodpovídá potlačitelnému ID {1} pro daný popisovač potlačení. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. Blok dané operace nepatří do aktuálního analytického kontextu. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf index 655042736750d..f5060dd8cbb9b 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Die unterdrückte Diagnose-ID "{0}" entspricht nicht der unterdrückbaren ID "{1}" für den angegebenen Deskriptor zur Unterdrückung. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. Der angegebene Operationsblock gehört nicht zum aktuellen Analysekontext. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf index feb8c2bf5923f..d07e6c88d818b 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. El id. de diagnóstico "{0}" suprimido no coincide con el id. "{1}" que se puede suprimir para el descriptor de supresión dado. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. El bloque de operaciones dado no pertenece al contexto de análisis actual. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf index a7ca2b08346d7..4f52067068ea0 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. L'ID de diagnostic supprimé '{0}' ne correspond pas à l'ID supprimable '{1}' pour le descripteur de suppression spécifié. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. Le bloc d'opérations donné n'appartient pas au contexte d'analyse actuel. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf index c9601fc0f9184..a49824fa3310f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. L'ID diagnostica '{0}' eliminato non corrisponde all'ID eliminabile '{1}' per il descrittore di eliminazione specificato. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. Il blocco operazioni specificato non appartiene al contesto di analisi corrente. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf index 9786fad2b7ace..47f38ebb4170d 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 抑制された診断 ID '{0}' が、指定された抑制記述子の抑制可能な ID '{1}' と一致しません。 - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. 指定した操作ブロックが現在の分析コンテストに属していません。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf index 366b56c26c4af..721ae2818be61 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 표시되지 않는 진단 ID '{0}'이(가) 지정된 비표시 설명자의 표시하지 않을 수 있는 ID '{1}'과(와) 일치하지 않습니다. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. 지정한 작업 블록이 현재 분석 컨텍스트에 속하지 않습니다. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf index 6b1b01fdaa3b3..d0fc007db556f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Pominięty identyfikator diagnostyki „{0}” nie pasuje do możliwego do pominięcia identyfikatora „{1}” dla danego deskryptora pomijania. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. Dany blok operacji nie należy do bieżącego kontekstu analizy. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf index 4c86a560281b6..a9dde446a1232 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. A ID de diagnóstico suprimida '{0}' não corresponde à ID suprimível '{1}' para o descritor de supressão fornecido. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. O bloqueio de operação fornecido não pertence ao contexto de análise atual. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf index ea5fb78b96a1b..5b6b82a24f1db 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Подавленный идентификатор диагностики "{0}" не соответствует подавленному идентификатору "{1}" для заданного дескриптора подавления. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. Заданный блок операции не принадлежит текущему контексту анализа. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf index 7f77300e21fd3..2b48c77614d3d 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. '{0}' gizlenmiş tanılama kimliği, belirtilen gizleme tanımlayıcısı için gizlenebilir '{1}' kimliği ile eşleşmiyor. - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. Belirtilen işlem bloğu geçerli analiz bağlamına ait değil. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf index 82b871fb8003e..952caebc8bd89 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 对于给定的禁止显示描述符,禁止显示的诊断 ID“{0}”与可禁止显示的 ID“{1}”不匹配。 - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. 给定操作块不属于当前的分析上下文。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf index 6d44ada97c35f..8aa6e210dbd5f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf @@ -39,16 +39,16 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 隱藏的診斷識別碼 '{0}' 不符合指定隱藏描述項的可隱藏識別碼 '{1}'。 - - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - Non-source file doesn't belong to the underlying 'CompilationWithAnalyzers'. - - Given operation block does not belong to the current analysis context. 指定的作業區塊不屬於目前的分析內容。 From 6bc9119ff59c4ca7c1f55d950dfbf8cb6f42e411 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 22 Jun 2020 08:07:02 -0700 Subject: [PATCH 04/11] Rename `AnalysisResult.NonSourceFileDiagnostics` to `AnalysisResult.AdditionalFileDiagnostics` --- .../Semantic/Diagnostics/DiagnosticAnalyzerTests.cs | 4 ++-- .../Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs | 10 +++++----- src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 2 +- .../Semantic/Diagnostics/DiagnosticAnalyzerTests.vb | 2 +- src/Workspaces/Core/Portable/Diagnostics/Extensions.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index 73f888abc2186..3a1c9bbecff72 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -3542,7 +3542,7 @@ public async Task TestAdditionalFileAnalyzer(bool registerFromInitialize) var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None); verifyDiagnostics(analysisResult.GetAllDiagnostics()); - verifyDiagnostics(analysisResult.NonSourceFileDiagnostics[additionalFile][analyzer]); + verifyDiagnostics(analysisResult.AdditionalFileDiagnostics[additionalFile][analyzer]); void verifyDiagnostics(ImmutableArray diagnostics) { @@ -3603,7 +3603,7 @@ public async Task TestMultipleAdditionalFileAnalyzers(bool registerFromInitializ static ImmutableArray getReportedDiagnostics(AnalysisResult analysisResult, DiagnosticAnalyzer analyzer, AdditionalText additionalFile) { - if (analysisResult.NonSourceFileDiagnostics.TryGetValue(additionalFile, out var diagnosticsMap) && + if (analysisResult.AdditionalFileDiagnostics.TryGetValue(additionalFile, out var diagnosticsMap) && diagnosticsMap.TryGetValue(analyzer, out var diagnostics)) { return diagnostics; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs index 15026fbaf1a19..27e8d709117ac 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs @@ -31,7 +31,7 @@ internal AnalysisResult( Analyzers = analyzers; SyntaxDiagnostics = localSyntaxDiagnostics; SemanticDiagnostics = localSemanticDiagnostics; - NonSourceFileDiagnostics = localNonSourceFileDiagnostics; + AdditionalFileDiagnostics = localNonSourceFileDiagnostics; CompilationDiagnostics = nonLocalDiagnostics; AnalyzerTelemetryInfo = analyzerTelemetryInfo; } @@ -52,9 +52,9 @@ internal AnalysisResult( public ImmutableDictionary>> SemanticDiagnostics { get; } /// - /// Diagnostics in non-source files reported by the . + /// Diagnostics in additional files reported by the . /// - public ImmutableDictionary>> NonSourceFileDiagnostics { get; } + public ImmutableDictionary>> AdditionalFileDiagnostics { get; } /// /// Compilation diagnostics reported by the . @@ -96,12 +96,12 @@ private ImmutableArray GetDiagnostics(IEnumerable GetDiagnostics(ImmutableHashSet excludedAnalyzers) { - if (SyntaxDiagnostics.Count > 0 || SemanticDiagnostics.Count > 0 || NonSourceFileDiagnostics.Count > 0 || CompilationDiagnostics.Count > 0) + if (SyntaxDiagnostics.Count > 0 || SemanticDiagnostics.Count > 0 || AdditionalFileDiagnostics.Count > 0 || CompilationDiagnostics.Count > 0) { var builder = ImmutableArray.CreateBuilder(); AddLocalDiagnostics(SyntaxDiagnostics, excludedAnalyzers, builder); AddLocalDiagnostics(SemanticDiagnostics, excludedAnalyzers, builder); - AddLocalDiagnostics(NonSourceFileDiagnostics, excludedAnalyzers, builder); + AddLocalDiagnostics(AdditionalFileDiagnostics, excludedAnalyzers, builder); AddNonLocalDiagnostics(CompilationDiagnostics, excludedAnalyzers, builder); return builder.ToImmutable(); diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index dfe24ddf4d5b3..750c20a782c90 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -6,7 +6,7 @@ Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.CancellationTok Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void -Microsoft.CodeAnalysis.Diagnostics.AnalysisResult.NonSourceFileDiagnostics.get -> System.Collections.Immutable.ImmutableDictionary>> +Microsoft.CodeAnalysis.Diagnostics.AnalysisResult.AdditionalFileDiagnostics.get -> System.Collections.Immutable.ImmutableDictionary>> Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.AssemblyLoader.get -> Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Equals(Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference other) -> bool Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerAdditionalFileDiagnosticsAsync(Microsoft.CodeAnalysis.AdditionalText file, System.Collections.Immutable.ImmutableArray analyzers, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb index 525a61fb41ed5..de95109ccc545 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb @@ -1583,7 +1583,7 @@ End Namespace Dim analysisResult = Await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None) TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.GetAllDiagnostics(), diagnosticSpan, analyzer, additionalFile) - TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.NonSourceFileDiagnostics(additionalFile)(analyzer), diagnosticSpan, analyzer, additionalFile) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.AdditionalFileDiagnostics(additionalFile)(analyzer), diagnosticSpan, analyzer, additionalFile) End Function Private Shared Sub TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics As ImmutableArray(Of Diagnostic), diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 0cde7b6e4edfb..10abf3a2aff65 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -175,7 +175,7 @@ public static ImmutableDictionary Date: Tue, 30 Jun 2020 07:06:49 -0700 Subject: [PATCH 05/11] Fix test --- .../Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index 627d8dd2004d3..7d60dd0635341 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -3650,13 +3650,13 @@ public async Task TestMultipleAdditionalFileAnalyzers(bool registerFromInitializ var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile1, CancellationToken.None); verifyAnalysisResult(analysisResult, analyzers, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths); analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile2, CancellationToken.None); - verifyAnalysisResult(analysisResult, analyzers, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths); + verifyAnalysisResult(analysisResult, analyzers, ImmutableArray.Create(additionalFile2), diagnosticSpan, additionalFilesHaveSamePaths); var singleAnalyzerArray = ImmutableArray.Create(analyzer1); analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile1, singleAnalyzerArray, CancellationToken.None); verifyAnalysisResult(analysisResult, singleAnalyzerArray, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths); analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile2, singleAnalyzerArray, CancellationToken.None); - verifyAnalysisResult(analysisResult, singleAnalyzerArray, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths); + verifyAnalysisResult(analysisResult, singleAnalyzerArray, ImmutableArray.Create(additionalFile2), diagnosticSpan, additionalFilesHaveSamePaths); analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None); verifyDiagnostics(analysisResult.GetAllDiagnostics(), analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths); From 85f2ac056b4f75855eec0bd11acfc02f2ac121a5 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Tue, 30 Jun 2020 07:22:37 -0700 Subject: [PATCH 06/11] Rename field --- .../Portable/Diagnostics/DocumentAnalysisExecutor.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs index 7075d3485a442..725d10026219c 100644 --- a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs @@ -31,7 +31,7 @@ internal sealed class DocumentAnalysisExecutor private ImmutableDictionary>? _lazySyntaxDiagnostics; private ImmutableDictionary>? _lazySemanticDiagnostics; - private ImmutableDictionary>? _lazyAdditionalFileDiagnostics; + private ImmutableDictionary>? _lazyAdditionalDocumentDiagnostics; public DocumentAnalysisExecutor( DocumentAnalysisScope analysisScope, @@ -176,7 +176,7 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti } Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, _compilationWithAnalyzers.Compilation).Count()); - return diagnostics.ConvertToLocalDiagnostics(textDocument); + return diagnostics.ConvertToLocalDiagnostics(textDocument, span); } private async Task> GetSyntaxDiagnosticsAsync(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) @@ -222,7 +222,7 @@ private async Task> GetAdditionalDocumentDiagnosticsA return ImmutableArray.Empty; } - if (_lazyAdditionalFileDiagnostics == null) + if (_lazyAdditionalDocumentDiagnostics == null) { var filePath = document.FilePath ?? document.Name; var additionalFile = _compilationWithAnalyzers.AnalysisOptions.Options?.AdditionalFiles.FirstOrDefault(a => PathUtilities.Comparer.Equals(a.Path, filePath)); @@ -238,10 +238,10 @@ private async Task> GetAdditionalDocumentDiagnosticsA diagnosticsMap = analysisResult.AdditionalFileDiagnostics.TryGetValue(additionalFile, out var value) ? value : ImmutableDictionary>.Empty; } - Interlocked.CompareExchange(ref _lazyAdditionalFileDiagnostics, diagnosticsMap, null); + Interlocked.CompareExchange(ref _lazyAdditionalDocumentDiagnostics, diagnosticsMap, null); } - return _lazyAdditionalFileDiagnostics.TryGetValue(analyzer, out var diagnostics) ? + return _lazyAdditionalDocumentDiagnostics.TryGetValue(analyzer, out var diagnostics) ? diagnostics : ImmutableArray.Empty; } From b73bd2f78c6a3674c596c4d01608586a2d52e80f Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Tue, 30 Jun 2020 07:50:26 -0700 Subject: [PATCH 07/11] Fix VB test --- .../Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb index de95109ccc545..ad31393e47e0a 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb @@ -1578,10 +1578,11 @@ End Namespace Dim diagnostics = Await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None) TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics, diagnosticSpan, analyzer, additionalFile) - diagnostics = Await compilation.WithAnalyzers(analyzers, options).GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile, CancellationToken.None) - TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics, diagnosticSpan, analyzer, additionalFile) + Dim analysisResult = Await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile, CancellationToken.None) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.GetAllDiagnostics(), diagnosticSpan, analyzer, additionalFile) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.AdditionalFileDiagnostics(additionalFile)(analyzer), diagnosticSpan, analyzer, additionalFile) - Dim analysisResult = Await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None) + analysisResult = Await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None) TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.GetAllDiagnostics(), diagnosticSpan, analyzer, additionalFile) TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.AdditionalFileDiagnostics(additionalFile)(analyzer), diagnosticSpan, analyzer, additionalFile) End Function From 3e8535ef38e5b3feebf783cafa393f1734584acc Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 10 Jul 2020 14:01:34 -0700 Subject: [PATCH 08/11] Address feedback --- .../Diagnostics/DiagnosticAnalyzerTests.cs | 2 +- .../Diagnostics/AnalysisContextInfoTests.cs | 4 +- .../DiagnosticAnalyzer/AnalysisContextInfo.cs | 8 +-- .../DiagnosticAnalyzer/AnalysisResult.cs | 4 +- .../AnalysisResultBuilder.cs | 38 +++++----- .../DiagnosticAnalyzer/AnalysisScope.cs | 15 ++-- .../DiagnosticAnalyzer/AnalyzerDriver.cs | 6 +- .../DiagnosticAnalyzer/AnalyzerExecutor.cs | 10 +-- .../CompilationWithAnalyzers.cs | 18 ++--- .../SourceOrNonSourceFile.cs | 69 +++++++------------ 10 files changed, 75 insertions(+), 99 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index 7d60dd0635341..70df3fe0708b8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -3593,7 +3593,7 @@ await compilationWithAnalyzers.GetAnalysisResultAsync(tree1, CancellationToken.N public async Task TestAdditionalFileAnalyzer(bool registerFromInitialize) { var tree = CSharpSyntaxTree.ParseText(string.Empty); - var compilation = CreateCompilationWithMscorlib45(new[] { tree }); + var compilation = CreateCompilation(new[] { tree }); compilation.VerifyDiagnostics(); AdditionalText additionalFile = new TestAdditionalText("Additional File Text"); diff --git a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs index 39ac477be413d..d83975fc1f81b 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs @@ -76,8 +76,8 @@ public override void Initialize(AnalysisContext c) c.RegisterSemanticModelAction(b => ThrowIfMatch(nameof(c.RegisterSemanticModelAction), new AnalysisContextInfo(b.SemanticModel))); c.RegisterSymbolAction(b => ThrowIfMatch(nameof(c.RegisterSymbolAction), new AnalysisContextInfo(b.Compilation, b.Symbol)), SymbolKind.NamedType); c.RegisterSyntaxNodeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxNodeAction), new AnalysisContextInfo(b.SemanticModel.Compilation, b.Node)), SyntaxKind.ReturnStatement); - c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, b.Tree))); - c.RegisterAdditionalFileAction(b => ThrowIfMatch(nameof(c.RegisterAdditionalFileAction), new AnalysisContextInfo(b.Compilation, b.AdditionalFile))); + c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, new SourceOrNonSourceFile(b.Tree)))); + c.RegisterAdditionalFileAction(b => ThrowIfMatch(nameof(c.RegisterAdditionalFileAction), new AnalysisContextInfo(b.Compilation, new SourceOrNonSourceFile(b.AdditionalFile)))); } private void ThrowIfMatch(string context, AnalysisContextInfo info) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs index 86f3c1eacf3e5..f769f41014832 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs @@ -26,7 +26,7 @@ public AnalysisContextInfo(Compilation compilation) : } public AnalysisContextInfo(SemanticModel model) : - this(model.Compilation, SourceOrNonSourceFile.Create(model.SyntaxTree)) + this(model.Compilation, new SourceOrNonSourceFile(model.SyntaxTree)) { } @@ -41,17 +41,17 @@ public AnalysisContextInfo(Compilation compilation, SourceOrNonSourceFile file) } public AnalysisContextInfo(Compilation compilation, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: null, file: SourceOrNonSourceFile.Create(node.SyntaxTree), node) + this(compilation: compilation, operation: null, symbol: null, file: new SourceOrNonSourceFile(node.SyntaxTree), node) { } public AnalysisContextInfo(Compilation compilation, IOperation operation) : - this(compilation: compilation, operation: operation, symbol: null, file: SourceOrNonSourceFile.Create(operation.Syntax.SyntaxTree), node: operation.Syntax) + this(compilation: compilation, operation: operation, symbol: null, file: new SourceOrNonSourceFile(operation.Syntax.SyntaxTree), node: operation.Syntax) { } public AnalysisContextInfo(Compilation compilation, ISymbol symbol, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: symbol, file: SourceOrNonSourceFile.Create(node.SyntaxTree), node) + this(compilation: compilation, operation: null, symbol: symbol, file: new SourceOrNonSourceFile(node.SyntaxTree), node) { } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs index 27e8d709117ac..80869bba51319 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs @@ -24,14 +24,14 @@ internal AnalysisResult( ImmutableArray analyzers, ImmutableDictionary>> localSyntaxDiagnostics, ImmutableDictionary>> localSemanticDiagnostics, - ImmutableDictionary>> localNonSourceFileDiagnostics, + ImmutableDictionary>> localAdditionalFileDiagnostics, ImmutableDictionary> nonLocalDiagnostics, ImmutableDictionary analyzerTelemetryInfo) { Analyzers = analyzers; SyntaxDiagnostics = localSyntaxDiagnostics; SemanticDiagnostics = localSemanticDiagnostics; - AdditionalFileDiagnostics = localNonSourceFileDiagnostics; + AdditionalFileDiagnostics = localAdditionalFileDiagnostics; CompilationDiagnostics = nonLocalDiagnostics; AnalyzerTelemetryInfo = analyzerTelemetryInfo; } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs index 23581051885fa..76557556713a9 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs @@ -31,15 +31,15 @@ internal sealed class AnalysisResultBuilder private Dictionary.Builder>>? _localSemanticDiagnosticsOpt = null; private Dictionary.Builder>>? _localSyntaxDiagnosticsOpt = null; - private Dictionary.Builder>>? _localNonSourceFileDiagnosticsOpt = null; + private Dictionary.Builder>>? _localAdditionalFileDiagnosticsOpt = null; private Dictionary.Builder>? _nonLocalDiagnosticsOpt = null; - internal AnalysisResultBuilder(bool logAnalyzerExecutionTime, ImmutableArray analyzers, ImmutableArray nonSourceFiles) + internal AnalysisResultBuilder(bool logAnalyzerExecutionTime, ImmutableArray analyzers, ImmutableArray additionalFiles) { _analyzerExecutionTimeOpt = logAnalyzerExecutionTime ? CreateAnalyzerExecutionTimeMap(analyzers) : null; _completedAnalyzers = new HashSet(); _analyzerActionCounts = new Dictionary(analyzers.Length); - _pathToAdditionalTextMap = CreatePathToAdditionalTextMap(nonSourceFiles); + _pathToAdditionalTextMap = CreatePathToAdditionalTextMap(additionalFiles); } private static Dictionary CreateAnalyzerExecutionTimeMap(ImmutableArray analyzers) @@ -53,15 +53,15 @@ private static Dictionary CreateAnalyzerExecutionT return map; } - private static ImmutableDictionary> CreatePathToAdditionalTextMap(ImmutableArray nonSourceFiles) + private static ImmutableDictionary> CreatePathToAdditionalTextMap(ImmutableArray additionalFiles) { - if (nonSourceFiles.IsEmpty) + if (additionalFiles.IsEmpty) { return ImmutableDictionary>.Empty; } var builder = ImmutableDictionary.CreateBuilder>(PathUtilities.Comparer); - foreach (var file in nonSourceFiles) + foreach (var file in additionalFiles) { // Null file path for additional files is not possible from IDE or command line compiler host. // However, it is possible from custom third party analysis hosts. @@ -134,7 +134,7 @@ internal void ApplySuppressionsAndStoreAnalysisResult(AnalysisScope analysisScop if (syntaxDiagnostics.Length > 0 || semanticDiagnostics.Length > 0 || compilationDiagnostics.Length > 0 || fullAnalysisResultForAnalyzersInScope) { UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, getSourceTree, ref _localSyntaxDiagnosticsOpt); - UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, getAdditionalTextKey, ref _localNonSourceFileDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, getAdditionalTextKey, ref _localAdditionalFileDiagnosticsOpt); UpdateLocalDiagnostics_NoLock(analyzer, semanticDiagnostics, fullAnalysisResultForAnalyzersInScope, getSourceTree, ref _localSemanticDiagnosticsOpt); UpdateNonLocalDiagnostics_NoLock(analyzer, compilationDiagnostics, fullAnalysisResultForAnalyzersInScope); } @@ -167,17 +167,11 @@ internal void ApplySuppressionsAndStoreAnalysisResult(AnalysisScope analysisScop // Fetch the first additional file that matches diagnostic location. if (diagnostic.Location is ExternalFileLocation externalFileLocation) { - var path = externalFileLocation.FilePath; if (_pathToAdditionalTextMap.TryGetValue(externalFileLocation.FilePath, out var additionalTexts)) { - if (additionalTexts.Count == 1) - { - return additionalTexts[0]; - } - foreach (var additionalText in additionalTexts) { - if (analysisScope.NonSourceFiles.Contains(additionalText)) + if (analysisScope.AdditionalFiles.Contains(additionalText)) { return additionalText; } @@ -193,7 +187,7 @@ private void UpdateLocalDiagnostics_NoLock( DiagnosticAnalyzer analyzer, ImmutableArray diagnostics, bool overwrite, - Func getKey, + Func getKeyFunc, ref Dictionary.Builder>>? lazyLocalDiagnostics) where TKey : class { @@ -204,7 +198,7 @@ private void UpdateLocalDiagnostics_NoLock( lazyLocalDiagnostics = lazyLocalDiagnostics ?? new Dictionary.Builder>>(); - foreach (var diagsByKey in diagnostics.GroupBy(getKey)) + foreach (var diagsByKey in diagnostics.GroupBy(getKeyFunc)) { var key = diagsByKey.Key; if (key is null) @@ -278,12 +272,12 @@ private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisS { AddAllLocalDiagnostics_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); AddAllLocalDiagnostics_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder); - AddAllLocalDiagnostics_NoLock(_localNonSourceFileDiagnosticsOpt, analysisScope, builder); + AddAllLocalDiagnostics_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder); } else if (analysisScope.IsSyntaxOnlyTreeAnalysis) { AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); - AddLocalDiagnosticsForPartialAnalysis_NoLock(_localNonSourceFileDiagnosticsOpt, analysisScope, builder); + AddLocalDiagnosticsForPartialAnalysis_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder); } else { @@ -300,14 +294,14 @@ private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisS } private static void AddAllLocalDiagnostics_NoLock( - Dictionary.Builder>>? localDiagnostics, + Dictionary.Builder>>? lazyLocalDiagnostics, AnalysisScope analysisScope, ImmutableArray.Builder builder) where TKey : class { - if (localDiagnostics != null) + if (lazyLocalDiagnostics != null) { - foreach (var localDiagsByTree in localDiagnostics.Values) + foreach (var localDiagsByTree in lazyLocalDiagnostics.Values) { AddDiagnostics_NoLock(localDiagsByTree, analysisScope.Analyzers, builder); } @@ -371,7 +365,7 @@ internal AnalysisResult ToAnalysisResult(ImmutableArray anal { localSyntaxDiagnostics = GetImmutable(analyzersSet, _localSyntaxDiagnosticsOpt); localSemanticDiagnostics = GetImmutable(analyzersSet, _localSemanticDiagnosticsOpt); - localNonSourceFileDiagnostics = GetImmutable(analyzersSet, _localNonSourceFileDiagnosticsOpt); + localNonSourceFileDiagnostics = GetImmutable(analyzersSet, _localAdditionalFileDiagnosticsOpt); nonLocalDiagnostics = GetImmutable(analyzersSet, _nonLocalDiagnosticsOpt); } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs index 39af9fe35c6fe..599bf5a68a457 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs @@ -37,7 +37,7 @@ internal class AnalysisScope /// /// Non-source files on which we need to perform analysis. /// - public IEnumerable NonSourceFiles { get; } + public IEnumerable AdditionalFiles { get; } public bool ConcurrentAnalysis { get; } @@ -75,14 +75,14 @@ public AnalysisScope(ImmutableArray analyzers, SourceOrNonSo { } - private AnalysisScope(IEnumerable trees, IEnumerable nonSourceFiles, ImmutableArray analyzers, bool isPartialAnalysis, SourceOrNonSourceFile? filterFile, TextSpan? filterSpanOpt, bool isSyntaxOnlyTreeAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + private AnalysisScope(IEnumerable trees, IEnumerable additionalFiles, ImmutableArray analyzers, bool isPartialAnalysis, SourceOrNonSourceFile? filterFile, TextSpan? filterSpanOpt, bool isSyntaxOnlyTreeAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) { Debug.Assert(isPartialAnalysis || FilterFileOpt == null); Debug.Assert(isPartialAnalysis || FilterSpanOpt == null); Debug.Assert(isPartialAnalysis || !isSyntaxOnlyTreeAnalysis); SyntaxTrees = trees; - NonSourceFiles = nonSourceFiles; + AdditionalFiles = additionalFiles; Analyzers = analyzers; IsPartialAnalysis = isPartialAnalysis; FilterFileOpt = filterFile; @@ -110,7 +110,7 @@ public bool Contains(DiagnosticAnalyzer analyzer) public AnalysisScope WithAnalyzers(ImmutableArray analyzers, bool hasAllAnalyzers) { var isPartialAnalysis = IsTreeAnalysis || !hasAllAnalyzers; - return new AnalysisScope(SyntaxTrees, NonSourceFiles, analyzers, isPartialAnalysis, FilterFileOpt, FilterSpanOpt, IsSyntaxOnlyTreeAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); + return new AnalysisScope(SyntaxTrees, AdditionalFiles, analyzers, isPartialAnalysis, FilterFileOpt, FilterSpanOpt, IsSyntaxOnlyTreeAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); } public static bool ShouldSkipSymbolAnalysis(SymbolDeclaredCompilationEvent symbolEvent) @@ -150,7 +150,7 @@ public bool ShouldAnalyze(ISymbol symbol) foreach (var location in symbol.Locations) { - if (location.SourceTree != null && FilterFileOpt.SourceTree == location.SourceTree && ShouldInclude(location.SourceSpan)) + if (FilterFileOpt.SourceTree == location.SourceTree && ShouldInclude(location.SourceSpan)) { return true; } @@ -193,15 +193,14 @@ public bool ShouldInclude(Diagnostic diagnostic) if (diagnostic.Location.IsInSource) { - if (FilterFileOpt?.SourceTree == null || - diagnostic.Location.SourceTree != FilterFileOpt.SourceTree) + if (diagnostic.Location.SourceTree != FilterFileOpt.SourceTree) { return false; } } else if (diagnostic.Location is ExternalFileLocation externalFileLocation) { - if (FilterFileOpt?.NonSourceFile == null || + if (FilterFileOpt.NonSourceFile == null || !PathUtilities.Comparer.Equals(externalFileLocation.FilePath, FilterFileOpt.NonSourceFile.Path)) { return false; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 6aee66b1136c0..855a3004dc54d 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -569,7 +569,7 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState foreach (var tree in analysisScope.SyntaxTrees) { var isGeneratedCode = IsGeneratedCode(tree); - var file = SourceOrNonSourceFile.Create(tree); + var file = new SourceOrNonSourceFile(tree); if (isGeneratedCode && DoNotAnalyzeGeneratedCode) { analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analysisScope.Analyzers); @@ -605,9 +605,9 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState private void ExecuteAdditionalFileActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { - foreach (var additionalFile in analysisScope.NonSourceFiles) + foreach (var additionalFile in analysisScope.AdditionalFiles) { - var file = SourceOrNonSourceFile.Create(additionalFile); + var file = new SourceOrNonSourceFile(additionalFile); var processedAnalyzers = analysisStateOpt != null ? PooledHashSet.GetInstance() : null; try diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index 81ad76514fc92..787fbb1f5ceb6 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -774,8 +774,8 @@ private void ExecuteSyntaxTreeActionsCore( ExecuteAndCatchIfThrows( syntaxTreeAction.Analyzer, data => data.action(data.context), - (action: syntaxTreeAction.Action, context: context), - new AnalysisContextInfo(_compilation, tree)); + (action: syntaxTreeAction.Action, context), + new AnalysisContextInfo(_compilation, file)); analyzerStateOpt?.ProcessedActions.Add(syntaxTreeAction); } @@ -848,7 +848,7 @@ private void ExecuteAdditionalFileActionsCore( additionalFileAction.Analyzer, data => data.action(data.context), (action: additionalFileAction.Action, context), - new AnalysisContextInfo(_compilation, additionalFile)); + new AnalysisContextInfo(_compilation, file)); analyzerStateOpt?.ProcessedActions.Add(additionalFileAction); } @@ -1796,14 +1796,14 @@ private Action GetAddCompilationDiagnostic(DiagnosticAnalyzer analyz private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(SourceOrNonSourceFile.Create(tree), null, _compilation, analyzer, isSyntaxDiagnostic: false, + return AnalyzerDiagnosticReporter.GetInstance(new SourceOrNonSourceFile(tree), null, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(SourceOrNonSourceFile.Create(tree), span, _compilation, analyzer, isSyntaxDiagnostic: false, + return AnalyzerDiagnosticReporter.GetInstance(new SourceOrNonSourceFile(tree), span, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index 75f565cab7602..edb248496b15e 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -506,7 +506,7 @@ public Task GetAnalysisResultAsync(SyntaxTree tree, Cancellation { VerifyTree(tree); - return GetAnalysisResultCoreAsync(SourceOrNonSourceFile.Create(tree), Analyzers, cancellationToken); + return GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(tree), Analyzers, cancellationToken); } /// @@ -521,7 +521,7 @@ public Task GetAnalysisResultAsync(SyntaxTree tree, ImmutableArr VerifyTree(tree); VerifyExistingAnalyzersArgument(analyzers); - return GetAnalysisResultCoreAsync(SourceOrNonSourceFile.Create(tree), analyzers, cancellationToken); + return GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(tree), analyzers, cancellationToken); } /// @@ -535,7 +535,7 @@ public async Task GetAnalysisResultAsync(AdditionalText file, Ca { VerifyAdditionalFile(file); - return await GetAnalysisResultCoreAsync(SourceOrNonSourceFile.Create(file), Analyzers, cancellationToken).ConfigureAwait(false); + return await GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(file), Analyzers, cancellationToken).ConfigureAwait(false); } /// @@ -551,7 +551,7 @@ public async Task GetAnalysisResultAsync(AdditionalText file, Im VerifyAdditionalFile(file); VerifyExistingAnalyzersArgument(analyzers); - return await GetAnalysisResultCoreAsync(SourceOrNonSourceFile.Create(file), analyzers, cancellationToken).ConfigureAwait(false); + return await GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(file), analyzers, cancellationToken).ConfigureAwait(false); } private async Task GetAnalysisResultCoreAsync(SourceOrNonSourceFile file, ImmutableArray analyzers, CancellationToken cancellationToken) @@ -563,7 +563,7 @@ private async Task GetAnalysisResultCoreAsync(SourceOrNonSourceF private async Task> GetAnalyzerSyntaxDiagnosticsCoreAsync(SyntaxTree tree, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, SourceOrNonSourceFile.Create(tree), filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrNonSourceFile(tree), filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSyntaxDiagnosticsAsync(analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); } @@ -651,14 +651,14 @@ public Task GetAnalysisResultAsync(SemanticModel model, TextSpan private async Task GetAnalysisResultCoreAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, SourceOrNonSourceFile.Create(model.SyntaxTree), filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrNonSourceFile(model.SyntaxTree), filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSemanticDiagnosticsAsync(model, analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.ToAnalysisResult(analyzers, cancellationToken); } private async Task> GetAnalyzerSemanticDiagnosticsCoreAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, SourceOrNonSourceFile.Create(model.SyntaxTree), filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrNonSourceFile(model.SyntaxTree), filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSemanticDiagnosticsAsync(model, analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); } @@ -741,7 +741,7 @@ await Task.WhenAll(partialTrees.Select(tree => Task.Run(() => { var treeModel = _compilationData.GetOrCreateCachedSemanticModel(tree, _compilation, cancellationToken); - analysisScope = new AnalysisScope(analysisScope.Analyzers, SourceOrNonSourceFile.Create(tree), filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); + analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrNonSourceFile(tree), filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); return ComputeAnalyzerSemanticDiagnosticsAsync(treeModel, analysisScope, cancellationToken, forceCompletePartialTrees: false); }, cancellationToken))).ConfigureAwait(false); } @@ -751,7 +751,7 @@ await Task.WhenAll(partialTrees.Select(tree => { cancellationToken.ThrowIfCancellationRequested(); var treeModel = _compilationData.GetOrCreateCachedSemanticModel(tree, _compilation, cancellationToken); - analysisScope = new AnalysisScope(analysisScope.Analyzers, SourceOrNonSourceFile.Create(tree), filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); + analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrNonSourceFile(tree), filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); await ComputeAnalyzerSemanticDiagnosticsAsync(treeModel, analysisScope, cancellationToken, forceCompletePartialTrees: false).ConfigureAwait(false); } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs index 92309c15224ea..0c0bd96083446 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -14,59 +15,41 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// For source files, is non-null and is null. /// For non-source files, is non-null and is null. /// - internal abstract class SourceOrNonSourceFile + internal sealed class SourceOrNonSourceFile : IEquatable { - public abstract SyntaxTree? SourceTree { get; } - public abstract AdditionalText? NonSourceFile { get; } - public abstract bool Equals([AllowNull] SourceOrNonSourceFile? other); - public abstract override bool Equals(object? obj); - public abstract override int GetHashCode(); + public SyntaxTree? SourceTree { get; } + public AdditionalText? NonSourceFile { get; } - public static SourceOrNonSourceFile Create(SyntaxTree tree) - { - return new SourceFileImpl(tree); - } + public SourceOrNonSourceFile(SyntaxTree tree) + => SourceTree = tree; - public static SourceOrNonSourceFile Create(AdditionalText nonSourceFile) - { - return new NonSourceFileImpl(nonSourceFile); - } + public SourceOrNonSourceFile(AdditionalText file) + => NonSourceFile = file; - private sealed class SourceFileImpl : SourceOrNonSourceFile - { - public SourceFileImpl(SyntaxTree tree) - { - SourceTree = tree; - } + public bool Equals([AllowNull] SourceOrNonSourceFile? other) + => other != null && SourceTree == other.SourceTree && NonSourceFile == other.NonSourceFile; - public override SyntaxTree? SourceTree { get; } - public override AdditionalText? NonSourceFile => null; - public override bool Equals(object? obj) - => Equals(obj as SourceFileImpl); - public override bool Equals([AllowNull] SourceOrNonSourceFile? other) - => other is SourceFileImpl otherSource && - SourceTree == otherSource.SourceTree; - public override int GetHashCode() - => SourceTree!.GetHashCode(); - } + public override bool Equals(object? obj) + => Equals(obj as SourceOrNonSourceFile); + + public static bool operator ==([AllowNull] SourceOrNonSourceFile? left, [AllowNull] SourceOrNonSourceFile? right) + => Equals(left, right); + + public static bool operator !=([AllowNull] SourceOrNonSourceFile? left, [AllowNull] SourceOrNonSourceFile? right) + => !Equals(left, right); - private sealed class NonSourceFileImpl : SourceOrNonSourceFile + public override int GetHashCode() { - public NonSourceFileImpl(AdditionalText nonSourceFile) + if (SourceTree != null) { - NonSourceFile = nonSourceFile; + return Hash.Combine(true, SourceTree.GetHashCode()); + } + else + { + RoslynDebug.Assert(NonSourceFile != null); + return Hash.Combine(false, NonSourceFile.GetHashCode()); } - - public override AdditionalText? NonSourceFile { get; } - public override SyntaxTree? SourceTree => null; - public override bool Equals(object? obj) - => Equals(obj as NonSourceFileImpl); - public override bool Equals([AllowNull] SourceOrNonSourceFile? other) - => other is NonSourceFileImpl otherNonSource && - NonSourceFile == otherNonSource.NonSourceFile; - public override int GetHashCode() - => NonSourceFile!.GetHashCode(); } } } From d38a3b357407a86fe700d4514e8776b42b6d23bd Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 10 Jul 2020 14:17:29 -0700 Subject: [PATCH 09/11] Use PathUtilities.Comparer --- .../Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs index 76557556713a9..22738335c257c 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs @@ -57,7 +57,7 @@ private static ImmutableDictionary> CreatePath { if (additionalFiles.IsEmpty) { - return ImmutableDictionary>.Empty; + return ImmutableDictionary>.Empty.WithComparers(PathUtilities.Comparer); } var builder = ImmutableDictionary.CreateBuilder>(PathUtilities.Comparer); From f561d882fc50c8e99bdcc139b322237555733772 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 10 Jul 2020 17:56:23 -0700 Subject: [PATCH 10/11] Address feedback --- .../Diagnostics/DiagnosticAnalyzerTests.cs | 1 - .../Diagnostics/AnalysisContextInfoTests.cs | 4 +- .../DiagnosticAnalyzer/AnalysisContextInfo.cs | 35 +++++----- .../AnalysisResultBuilder.cs | 19 +++--- .../DiagnosticAnalyzer/AnalysisScope.cs | 53 ++++++++------- .../AnalysisState.PerAnalyzerState.cs | 29 ++++----- .../DiagnosticAnalyzer/AnalysisState.cs | 16 ++--- .../DiagnosticAnalyzer/AnalyzerDriver.cs | 65 +++++-------------- ...yzerExecutor.AnalyzerDiagnosticReporter.cs | 12 ++-- .../DiagnosticAnalyzer/AnalyzerExecutor.cs | 22 +++---- .../CompilationWithAnalyzers.cs | 46 ++++++------- .../DiagnosticAnalysisContext.cs | 2 +- .../SourceOrAdditionalFile.cs | 60 +++++++++++++++++ .../SourceOrNonSourceFile.cs | 55 ---------------- 14 files changed, 197 insertions(+), 222 deletions(-) create mode 100644 src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrAdditionalFile.cs delete mode 100644 src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index 70df3fe0708b8..304fcbd950d67 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -10,7 +10,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.Cci; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Diagnostics; diff --git a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs index d83975fc1f81b..74ef0bf56f1e7 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs @@ -76,8 +76,8 @@ public override void Initialize(AnalysisContext c) c.RegisterSemanticModelAction(b => ThrowIfMatch(nameof(c.RegisterSemanticModelAction), new AnalysisContextInfo(b.SemanticModel))); c.RegisterSymbolAction(b => ThrowIfMatch(nameof(c.RegisterSymbolAction), new AnalysisContextInfo(b.Compilation, b.Symbol)), SymbolKind.NamedType); c.RegisterSyntaxNodeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxNodeAction), new AnalysisContextInfo(b.SemanticModel.Compilation, b.Node)), SyntaxKind.ReturnStatement); - c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, new SourceOrNonSourceFile(b.Tree)))); - c.RegisterAdditionalFileAction(b => ThrowIfMatch(nameof(c.RegisterAdditionalFileAction), new AnalysisContextInfo(b.Compilation, new SourceOrNonSourceFile(b.AdditionalFile)))); + c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, new SourceOrAdditionalFile(b.Tree)))); + c.RegisterAdditionalFileAction(b => ThrowIfMatch(nameof(c.RegisterAdditionalFileAction), new AnalysisContextInfo(b.Compilation, new SourceOrAdditionalFile(b.AdditionalFile)))); } private void ThrowIfMatch(string context, AnalysisContextInfo info) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs index f769f41014832..1043481b34c93 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs @@ -4,6 +4,7 @@ #nullable enable +using System.Diagnostics; using System.Text; using Roslyn.Utilities; @@ -17,7 +18,7 @@ internal readonly struct AnalysisContextInfo private readonly Compilation? _compilation; private readonly IOperation? _operation; private readonly ISymbol? _symbol; - private readonly SourceOrNonSourceFile? _file; + private readonly SourceOrAdditionalFile? _file; private readonly SyntaxNode? _node; public AnalysisContextInfo(Compilation compilation) : @@ -26,7 +27,7 @@ public AnalysisContextInfo(Compilation compilation) : } public AnalysisContextInfo(SemanticModel model) : - this(model.Compilation, new SourceOrNonSourceFile(model.SyntaxTree)) + this(model.Compilation, new SourceOrAdditionalFile(model.SyntaxTree)) { } @@ -35,23 +36,23 @@ public AnalysisContextInfo(Compilation compilation, ISymbol symbol) : { } - public AnalysisContextInfo(Compilation compilation, SourceOrNonSourceFile file) : + public AnalysisContextInfo(Compilation compilation, SourceOrAdditionalFile file) : this(compilation: compilation, operation: null, symbol: null, file: file, node: null) { } public AnalysisContextInfo(Compilation compilation, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: null, file: new SourceOrNonSourceFile(node.SyntaxTree), node) + this(compilation: compilation, operation: null, symbol: null, file: new SourceOrAdditionalFile(node.SyntaxTree), node) { } public AnalysisContextInfo(Compilation compilation, IOperation operation) : - this(compilation: compilation, operation: operation, symbol: null, file: new SourceOrNonSourceFile(operation.Syntax.SyntaxTree), node: operation.Syntax) + this(compilation: compilation, operation: operation, symbol: null, file: new SourceOrAdditionalFile(operation.Syntax.SyntaxTree), node: operation.Syntax) { } public AnalysisContextInfo(Compilation compilation, ISymbol symbol, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: symbol, file: new SourceOrNonSourceFile(node.SyntaxTree), node) + this(compilation: compilation, operation: null, symbol: symbol, file: new SourceOrAdditionalFile(node.SyntaxTree), node) { } @@ -59,11 +60,11 @@ private AnalysisContextInfo( Compilation? compilation, IOperation? operation, ISymbol? symbol, - SourceOrNonSourceFile? file, + SourceOrAdditionalFile? file, SyntaxNode? node) { - RoslynDebug.Assert(node == null || file?.SourceTree != null); - RoslynDebug.Assert(operation == null || file?.SourceTree != null); + Debug.Assert(node == null || file?.SourceTree != null); + Debug.Assert(operation == null || file?.SourceTree != null); _compilation = compilation; _operation = operation; @@ -91,25 +92,25 @@ public string GetContext() sb.AppendLine($"{nameof(ISymbol)}: {_symbol.Name} ({_symbol.Kind})"); } - if (_file != null) + if (_file.HasValue) { - if (_file.SourceTree != null) + if (_file.Value.SourceTree != null) { - sb.AppendLine($"{nameof(SyntaxTree)}: {_file.SourceTree.FilePath}"); + sb.AppendLine($"{nameof(SyntaxTree)}: {_file.Value.SourceTree.FilePath}"); } else { - RoslynDebug.Assert(_file.NonSourceFile != null); - sb.AppendLine($"{nameof(AdditionalText)}: {_file.NonSourceFile.Path}"); + RoslynDebug.Assert(_file.Value.AdditionalFile != null); + sb.AppendLine($"{nameof(AdditionalText)}: {_file.Value.AdditionalFile.Path}"); } } if (_node != null) { - RoslynDebug.Assert(_file != null); - RoslynDebug.Assert(_file.SourceTree != null); + RoslynDebug.Assert(_file.HasValue); + RoslynDebug.Assert(_file.Value.SourceTree != null); - var text = _file.SourceTree.GetText(); + var text = _file.Value.SourceTree.GetText(); var lineSpan = text?.Lines?.GetLinePositionSpan(_node.Span); // can't use Kind since that is language specific. instead will output typename. diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs index 22738335c257c..36259ff7d37bf 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs @@ -23,6 +23,9 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal sealed class AnalysisResultBuilder { + private static readonly ImmutableDictionary> s_emptyPathToAdditionalTextMap = + ImmutableDictionary>.Empty.WithComparers(PathUtilities.Comparer); + private readonly object _gate = new object(); private readonly Dictionary? _analyzerExecutionTimeOpt; private readonly HashSet _completedAnalyzers; @@ -57,7 +60,7 @@ private static ImmutableDictionary> CreatePath { if (additionalFiles.IsEmpty) { - return ImmutableDictionary>.Empty.WithComparers(PathUtilities.Comparer); + return s_emptyPathToAdditionalTextMap; } var builder = ImmutableDictionary.CreateBuilder>(PathUtilities.Comparer); @@ -268,13 +271,13 @@ private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisS var builder = ImmutableArray.CreateBuilder(); if (getLocalDiagnostics) { - if (!analysisScope.IsTreeAnalysis) + if (!analysisScope.IsSingleFileAnalysis) { AddAllLocalDiagnostics_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); AddAllLocalDiagnostics_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder); AddAllLocalDiagnostics_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder); } - else if (analysisScope.IsSyntaxOnlyTreeAnalysis) + else if (analysisScope.IsSyntacticSingleFileAnalysis) { AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); AddLocalDiagnosticsForPartialAnalysis_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder); @@ -312,13 +315,13 @@ private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( Dictionary.Builder>>? localDiagnostics, AnalysisScope analysisScope, ImmutableArray.Builder builder) - => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.SourceTree, analysisScope.Analyzers, builder); + => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.Value.SourceTree, analysisScope.Analyzers, builder); private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( Dictionary.Builder>>? localDiagnostics, AnalysisScope analysisScope, ImmutableArray.Builder builder) - => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.NonSourceFile, analysisScope.Analyzers, builder); + => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.Value.AdditionalFile, analysisScope.Analyzers, builder); private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( Dictionary.Builder>>? localDiagnostics, @@ -357,7 +360,7 @@ internal AnalysisResult ToAnalysisResult(ImmutableArray anal ImmutableDictionary>> localSyntaxDiagnostics; ImmutableDictionary>> localSemanticDiagnostics; - ImmutableDictionary>> localNonSourceFileDiagnostics; + ImmutableDictionary>> localAdditionalFileDiagnostics; ImmutableDictionary> nonLocalDiagnostics; var analyzersSet = analyzers.ToImmutableHashSet(); @@ -365,13 +368,13 @@ internal AnalysisResult ToAnalysisResult(ImmutableArray anal { localSyntaxDiagnostics = GetImmutable(analyzersSet, _localSyntaxDiagnosticsOpt); localSemanticDiagnostics = GetImmutable(analyzersSet, _localSemanticDiagnosticsOpt); - localNonSourceFileDiagnostics = GetImmutable(analyzersSet, _localAdditionalFileDiagnosticsOpt); + localAdditionalFileDiagnostics = GetImmutable(analyzersSet, _localAdditionalFileDiagnosticsOpt); nonLocalDiagnostics = GetImmutable(analyzersSet, _nonLocalDiagnosticsOpt); } cancellationToken.ThrowIfCancellationRequested(); var analyzerTelemetryInfo = GetTelemetryInfo(analyzers); - return new AnalysisResult(analyzers, localSyntaxDiagnostics, localSemanticDiagnostics, localNonSourceFileDiagnostics, nonLocalDiagnostics, analyzerTelemetryInfo); + return new AnalysisResult(analyzers, localSyntaxDiagnostics, localSemanticDiagnostics, localAdditionalFileDiagnostics, nonLocalDiagnostics, analyzerTelemetryInfo); } private static ImmutableDictionary>> GetImmutable( diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs index 599bf5a68a457..d59a54884ad1c 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs @@ -9,7 +9,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -24,7 +23,7 @@ internal class AnalysisScope { private readonly Lazy> _lazyAnalyzersSet; - public SourceOrNonSourceFile? FilterFileOpt { get; } + public SourceOrAdditionalFile? FilterFileOpt { get; } public TextSpan? FilterSpanOpt { get; } public ImmutableArray Analyzers { get; } @@ -47,39 +46,39 @@ internal class AnalysisScope public bool CategorizeDiagnostics { get; } /// - /// True if we need to perform only syntax analysis for a single tree or non-source file. + /// True if we need to perform only syntax analysis for a single source or additional file. /// - public bool IsSyntaxOnlyTreeAnalysis { get; } + public bool IsSyntacticSingleFileAnalysis { get; } /// - /// True if we need to perform analysis for a single tree or non-source file. + /// True if we need to perform analysis for a single source or additional file. /// - public bool IsTreeAnalysis => FilterFileOpt != null; + public bool IsSingleFileAnalysis => FilterFileOpt != null; /// /// Flag indicating if this is a partial analysis for the corresponding , - /// i.e. is true and/or is a subset of . + /// i.e. is true and/or is a subset of . /// public bool IsPartialAnalysis { get; } public AnalysisScope(Compilation compilation, AnalyzerOptions? analyzerOptions, ImmutableArray analyzers, bool hasAllAnalyzers, bool concurrentAnalysis, bool categorizeDiagnostics) : this(compilation.SyntaxTrees, analyzerOptions?.AdditionalFiles ?? ImmutableArray.Empty, - analyzers, isPartialAnalysis: !hasAllAnalyzers, filterFile: null, filterSpanOpt: null, isSyntaxOnlyTreeAnalysis: false, concurrentAnalysis: concurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics) + analyzers, isPartialAnalysis: !hasAllAnalyzers, filterFile: null, filterSpanOpt: null, isSyntacticSingleFileAnalysis: false, concurrentAnalysis: concurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics) { } - public AnalysisScope(ImmutableArray analyzers, SourceOrNonSourceFile filterFile, TextSpan? filterSpan, bool syntaxAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + public AnalysisScope(ImmutableArray analyzers, SourceOrAdditionalFile filterFile, TextSpan? filterSpan, bool isSyntacticSingleFileAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) : this(filterFile.SourceTree != null ? SpecializedCollections.SingletonEnumerable(filterFile.SourceTree) : SpecializedCollections.EmptyEnumerable(), - filterFile.NonSourceFile != null ? SpecializedCollections.SingletonEnumerable(filterFile.NonSourceFile) : SpecializedCollections.EmptyEnumerable(), - analyzers, isPartialAnalysis: true, filterFile, filterSpan, syntaxAnalysis, concurrentAnalysis, categorizeDiagnostics) + filterFile.AdditionalFile != null ? SpecializedCollections.SingletonEnumerable(filterFile.AdditionalFile) : SpecializedCollections.EmptyEnumerable(), + analyzers, isPartialAnalysis: true, filterFile, filterSpan, isSyntacticSingleFileAnalysis, concurrentAnalysis, categorizeDiagnostics) { } - private AnalysisScope(IEnumerable trees, IEnumerable additionalFiles, ImmutableArray analyzers, bool isPartialAnalysis, SourceOrNonSourceFile? filterFile, TextSpan? filterSpanOpt, bool isSyntaxOnlyTreeAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + private AnalysisScope(IEnumerable trees, IEnumerable additionalFiles, ImmutableArray analyzers, bool isPartialAnalysis, SourceOrAdditionalFile? filterFile, TextSpan? filterSpanOpt, bool isSyntacticSingleFileAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) { Debug.Assert(isPartialAnalysis || FilterFileOpt == null); Debug.Assert(isPartialAnalysis || FilterSpanOpt == null); - Debug.Assert(isPartialAnalysis || !isSyntaxOnlyTreeAnalysis); + Debug.Assert(isPartialAnalysis || !isSyntacticSingleFileAnalysis); SyntaxTrees = trees; AdditionalFiles = additionalFiles; @@ -87,7 +86,7 @@ private AnalysisScope(IEnumerable trees, IEnumerable IsPartialAnalysis = isPartialAnalysis; FilterFileOpt = filterFile; FilterSpanOpt = filterSpanOpt; - IsSyntaxOnlyTreeAnalysis = isSyntaxOnlyTreeAnalysis; + IsSyntacticSingleFileAnalysis = isSyntacticSingleFileAnalysis; ConcurrentAnalysis = concurrentAnalysis; CategorizeDiagnostics = categorizeDiagnostics; @@ -109,8 +108,8 @@ public bool Contains(DiagnosticAnalyzer analyzer) public AnalysisScope WithAnalyzers(ImmutableArray analyzers, bool hasAllAnalyzers) { - var isPartialAnalysis = IsTreeAnalysis || !hasAllAnalyzers; - return new AnalysisScope(SyntaxTrees, AdditionalFiles, analyzers, isPartialAnalysis, FilterFileOpt, FilterSpanOpt, IsSyntaxOnlyTreeAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); + var isPartialAnalysis = IsSingleFileAnalysis || !hasAllAnalyzers; + return new AnalysisScope(SyntaxTrees, AdditionalFiles, analyzers, isPartialAnalysis, FilterFileOpt, FilterSpanOpt, IsSyntacticSingleFileAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); } public static bool ShouldSkipSymbolAnalysis(SymbolDeclaredCompilationEvent symbolEvent) @@ -128,29 +127,29 @@ public static bool ShouldSkipDeclarationAnalysis(ISymbol symbol) public bool ShouldAnalyze(SyntaxTree tree) { - return FilterFileOpt == null || FilterFileOpt.SourceTree == tree; + return !FilterFileOpt.HasValue || FilterFileOpt.Value.SourceTree == tree; } public bool ShouldAnalyze(AdditionalText file) { - return FilterFileOpt == null || FilterFileOpt.NonSourceFile == file; + return !FilterFileOpt.HasValue || FilterFileOpt.Value.AdditionalFile == file; } public bool ShouldAnalyze(ISymbol symbol) { - if (FilterFileOpt == null) + if (!FilterFileOpt.HasValue) { return true; } - if (FilterFileOpt.SourceTree == null) + if (FilterFileOpt.Value.SourceTree == null) { return false; } foreach (var location in symbol.Locations) { - if (FilterFileOpt.SourceTree == location.SourceTree && ShouldInclude(location.SourceSpan)) + if (FilterFileOpt.Value.SourceTree == location.SourceTree && ShouldInclude(location.SourceSpan)) { return true; } @@ -161,12 +160,12 @@ public bool ShouldAnalyze(ISymbol symbol) public bool ShouldAnalyze(SyntaxNode node) { - if (FilterFileOpt == null) + if (!FilterFileOpt.HasValue) { return true; } - if (FilterFileOpt.SourceTree == null) + if (FilterFileOpt.Value.SourceTree == null) { return false; } @@ -186,22 +185,22 @@ public bool ContainsSpan(TextSpan filterSpan) public bool ShouldInclude(Diagnostic diagnostic) { - if (FilterFileOpt == null) + if (!FilterFileOpt.HasValue) { return true; } if (diagnostic.Location.IsInSource) { - if (diagnostic.Location.SourceTree != FilterFileOpt.SourceTree) + if (diagnostic.Location.SourceTree != FilterFileOpt.Value.SourceTree) { return false; } } else if (diagnostic.Location is ExternalFileLocation externalFileLocation) { - if (FilterFileOpt.NonSourceFile == null || - !PathUtilities.Comparer.Equals(externalFileLocation.FilePath, FilterFileOpt.NonSourceFile.Path)) + if (FilterFileOpt.Value.AdditionalFile == null || + !PathUtilities.Comparer.Equals(externalFileLocation.FilePath, FilterFileOpt.Value.AdditionalFile.Path)) { return false; } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs index 3b761a7485d7b..c702bcd66d514 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs @@ -23,7 +23,7 @@ private class PerAnalyzerState private readonly Dictionary _pendingSymbols = new Dictionary(); private readonly Dictionary> _pendingDeclarations = new Dictionary>(); - private Dictionary _lazyFilesWithAnalysisData = null; + private Dictionary _lazyFilesWithAnalysisData = null; private int _pendingSyntaxAnalysisFilesCount = 0; private Dictionary _lazyPendingSymbolEndAnalyses = null; @@ -52,7 +52,7 @@ public void AddPendingEvents(HashSet uniqueEvents) } } - public bool HasPendingSyntaxAnalysis(SourceOrNonSourceFile fileOpt) + public bool HasPendingSyntaxAnalysis(SourceOrAdditionalFile? fileOpt) { lock (_gate) { @@ -63,14 +63,14 @@ public bool HasPendingSyntaxAnalysis(SourceOrNonSourceFile fileOpt) Debug.Assert(_lazyFilesWithAnalysisData != null); - if (fileOpt == null) + if (!fileOpt.HasValue) { // We have syntax analysis pending for at least one file. return true; } AnalyzerStateData state; - if (!_lazyFilesWithAnalysisData.TryGetValue(fileOpt, out state)) + if (!_lazyFilesWithAnalysisData.TryGetValue(fileOpt.Value, out state)) { // We haven't even started analysis for this file. return true; @@ -143,7 +143,7 @@ private static bool MarkEntityProcessed_NoLock 0 || actionCounts.AdditionalFileActionsCount > 0) + var fileCount = actionCounts.SyntaxTreeActionsCount > 0 ? compilationEvent.Compilation.SyntaxTrees.Count() : 0; + fileCount += actionCounts.AdditionalFileActionsCount > 0 ? compilationStartedEvent.AdditionalFiles.Length : 0; + if (fileCount > 0) { - var fileCount = actionCounts.SyntaxTreeActionsCount > 0 ? compilationEvent.Compilation.SyntaxTrees.Count() : 0; - fileCount += actionCounts.AdditionalFileActionsCount > 0 ? compilationStartedEvent.AdditionalFiles.Length : 0; - if (fileCount > 0) - { - _lazyFilesWithAnalysisData = new Dictionary(); - _pendingSyntaxAnalysisFilesCount = fileCount; - } + _lazyFilesWithAnalysisData = new Dictionary(); + _pendingSyntaxAnalysisFilesCount = fileCount; } if (actionCounts.CompilationActionsCount == 0) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs index 5975a7378e5e0..93502c8871036 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs @@ -487,7 +487,7 @@ private void Free(HashSet events) /// public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) { - if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) + if (analysisScope.IsSingleFileAnalysis && !analysisScope.IsSyntacticSingleFileAnalysis) { return false; } @@ -509,10 +509,10 @@ public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) /// public bool HasPendingSymbolAnalysis(AnalysisScope analysisScope, CancellationToken cancellationToken) { - RoslynDebug.Assert(analysisScope.FilterFileOpt != null); - RoslynDebug.Assert(analysisScope.FilterFileOpt.SourceTree != null); + RoslynDebug.Assert(analysisScope.FilterFileOpt.HasValue); + RoslynDebug.Assert(analysisScope.FilterFileOpt.Value.SourceTree != null); - var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterFileOpt.SourceTree, cancellationToken); + var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterFileOpt.Value.SourceTree, cancellationToken); foreach (var symbolDeclaredEvent in symbolDeclaredEvents) { if (analysisScope.ShouldAnalyze(symbolDeclaredEvent.Symbol)) @@ -750,7 +750,7 @@ public void MarkDeclarationsComplete(ISymbol symbol, IEnumerable representing partial syntax analysis state for the given tree for the given analyzer. /// - public bool TryStartSyntaxAnalysis(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) + public bool TryStartSyntaxAnalysis(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) { return GetAnalyzerState(analyzer).TryStartSyntaxAnalysis(file, out state); } @@ -758,7 +758,7 @@ public bool TryStartSyntaxAnalysis(SourceOrNonSourceFile file, DiagnosticAnalyze /// /// Marks the given file as fully syntactically analyzed for the given analyzer. /// - public void MarkSyntaxAnalysisComplete(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer) + public void MarkSyntaxAnalysisComplete(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer) { GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(file); } @@ -766,7 +766,7 @@ public void MarkSyntaxAnalysisComplete(SourceOrNonSourceFile file, DiagnosticAna /// /// Marks the given file as fully syntactically analyzed for the given analyzers. /// - public void MarkSyntaxAnalysisComplete(SourceOrNonSourceFile file, IEnumerable analyzers) + public void MarkSyntaxAnalysisComplete(SourceOrAdditionalFile file, IEnumerable analyzers) { foreach (var analyzer in analyzers) { @@ -778,7 +778,7 @@ public void MarkSyntaxAnalysisComplete(SourceOrNonSourceFile file, IEnumerable public void MarkSyntaxAnalysisCompleteForUnprocessedAnalyzers( - SourceOrNonSourceFile file, + SourceOrAdditionalFile file, AnalysisScope analysisScope, HashSet processedAnalyzers) => MarkAnalysisCompleteForUnprocessedAnalyzers(analysisScope, processedAnalyzers, MarkSyntaxAnalysisComplete, file); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 855a3004dc54d..3ed956d7c6ee1 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -246,11 +246,11 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn _generatedCodeAttribute = analyzerExecutor.Compilation?.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute"); _symbolActionsByKind = MakeSymbolActionsByKind(in AnalyzerActions); - _semanticModelActions = MakeSemanticModelActionsByAnalyzer(in AnalyzerActions); - _syntaxTreeActions = MakeSyntaxTreeActionsByAnalyzer(in AnalyzerActions); - _additionalFileActions = MakeAdditionalFileActionsByAnalyzer(in AnalyzerActions); - _compilationActions = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationActions); - _compilationEndActions = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationEndActions); + _semanticModelActions = MakeActionsByAnalyzer(AnalyzerActions.SemanticModelActions); + _syntaxTreeActions = MakeActionsByAnalyzer(AnalyzerActions.SyntaxTreeActions); + _additionalFileActions = MakeActionsByAnalyzer(AnalyzerActions.AdditionalFileActions); + _compilationActions = MakeActionsByAnalyzer(this.AnalyzerActions.CompilationActions); + _compilationEndActions = MakeActionsByAnalyzer(this.AnalyzerActions.CompilationEndActions); _compilationEndAnalyzers = MakeCompilationEndAnalyzers(_compilationEndActions); if (this.AnalyzerActions.SymbolStartActionsCount > 0) @@ -560,7 +560,7 @@ private static void OnDriverException(Task faultedTask, AnalyzerExecutor analyze private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { - if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) + if (analysisScope.IsSingleFileAnalysis && !analysisScope.IsSyntacticSingleFileAnalysis) { // For partial analysis, only execute syntax tree actions if performing syntax analysis. return; @@ -569,7 +569,7 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState foreach (var tree in analysisScope.SyntaxTrees) { var isGeneratedCode = IsGeneratedCode(tree); - var file = new SourceOrNonSourceFile(tree); + var file = new SourceOrAdditionalFile(tree); if (isGeneratedCode && DoNotAnalyzeGeneratedCode) { analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analysisScope.Analyzers); @@ -605,9 +605,15 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState private void ExecuteAdditionalFileActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { + if (analysisScope.IsSingleFileAnalysis && !analysisScope.IsSyntacticSingleFileAnalysis) + { + // For partial analysis, only execute additional file actions if performing syntactic single file analysis. + return; + } + foreach (var additionalFile in analysisScope.AdditionalFiles) { - var file = new SourceOrNonSourceFile(additionalFile); + var file = new SourceOrAdditionalFile(additionalFile); var processedAnalyzers = analysisStateOpt != null ? PooledHashSet.GetInstance() : null; try @@ -1151,46 +1157,11 @@ private ImmutableHashSet ComputeSuppressedAnalyzersForTree(S return builder.ToImmutableAndFree(); } - private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeSyntaxTreeActionsByAnalyzer(in AnalyzerActions analyzerActions) - { - var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); - var actionsByAnalyzers = analyzerActions.SyntaxTreeActions.GroupBy(action => action.Analyzer); - foreach (var analyzerAndActions in actionsByAnalyzers) - { - builder.Add((analyzerAndActions.Key, analyzerAndActions.ToImmutableArray())); - } - - return builder.ToImmutableAndFree(); - } - - private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeAdditionalFileActionsByAnalyzer(in AnalyzerActions analyzerActions) - { - var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); - var actionsByAnalyzers = analyzerActions.AdditionalFileActions.GroupBy(action => action.Analyzer); - foreach (var analyzerAndActions in actionsByAnalyzers) - { - builder.Add((analyzerAndActions.Key, analyzerAndActions.ToImmutableArray())); - } - - return builder.ToImmutableAndFree(); - } - - private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeSemanticModelActionsByAnalyzer(in AnalyzerActions analyzerActions) - { - var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); - var actionsByAnalyzers = analyzerActions.SemanticModelActions.GroupBy(action => action.Analyzer); - foreach (var analyzerAndActions in actionsByAnalyzers) - { - builder.Add((analyzerAndActions.Key, analyzerAndActions.ToImmutableArray())); - } - - return builder.ToImmutableAndFree(); - } - - private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeCompilationActionsByAnalyzer(ImmutableArray compilationActions) + private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeActionsByAnalyzer(in ImmutableArray analyzerActions) + where TAnalyzerAction : AnalyzerAction { - var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); - var actionsByAnalyzers = compilationActions.GroupBy(action => action.Analyzer); + var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); + var actionsByAnalyzers = analyzerActions.GroupBy(action => action.Analyzer); foreach (var analyzerAndActions in actionsByAnalyzers) { builder.Add((analyzerAndActions.Key, analyzerAndActions.ToImmutableArray())); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs index e00a8f91edbb4..1d5eb85cb8a27 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs @@ -27,7 +27,7 @@ private sealed class AnalyzerDiagnosticReporter new ObjectPool(() => new AnalyzerDiagnosticReporter(), 10); public static AnalyzerDiagnosticReporter GetInstance( - SourceOrNonSourceFile contextFile, + SourceOrAdditionalFile contextFile, TextSpan? span, Compilation compilation, DiagnosticAnalyzer analyzer, @@ -67,7 +67,7 @@ public void Free() s_objectPool.Free(this); } - private SourceOrNonSourceFile _contextFile; + private SourceOrAdditionalFile? _contextFile; private TextSpan? _span; private Compilation _compilation; private DiagnosticAnalyzer _analyzer; @@ -120,13 +120,13 @@ bool isLocalDiagnostic(Diagnostic diagnostic) if (diagnostic.Location.IsInSource) { return _contextFile?.SourceTree != null && - _contextFile.SourceTree == diagnostic.Location.SourceTree; + _contextFile.Value.SourceTree == diagnostic.Location.SourceTree; } - if (diagnostic.Location is ExternalFileLocation externalFileLocation) + if (_contextFile?.AdditionalFile != null && + diagnostic.Location is ExternalFileLocation externalFileLocation) { - return _contextFile?.NonSourceFile != null && - PathUtilities.Comparer.Equals(_contextFile.NonSourceFile.Path, externalFileLocation.FilePath); + return PathUtilities.Comparer.Equals(_contextFile.Value.AdditionalFile.Path, externalFileLocation.FilePath); } return false; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index 787fbb1f5ceb6..1a0856b8abfc3 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -717,7 +717,7 @@ private void ExecuteSemanticModelActionsCore( public bool TryExecuteSyntaxTreeActions( ImmutableArray syntaxTreeActions, DiagnosticAnalyzer analyzer, - SourceOrNonSourceFile file, + SourceOrAdditionalFile file, AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool isGeneratedCode) @@ -745,7 +745,7 @@ public bool TryExecuteSyntaxTreeActions( private void ExecuteSyntaxTreeActionsCore( ImmutableArray syntaxTreeActions, DiagnosticAnalyzer analyzer, - SourceOrNonSourceFile file, + SourceOrAdditionalFile file, AnalyzerStateData analyzerStateOpt, bool isGeneratedCode) { @@ -799,11 +799,11 @@ private void ExecuteSyntaxTreeActionsCore( public bool TryExecuteAdditionalFileActions( ImmutableArray additionalFileActions, DiagnosticAnalyzer analyzer, - SourceOrNonSourceFile file, + SourceOrAdditionalFile file, AnalysisScope analysisScope, AnalysisState analysisStateOpt) { - RoslynDebug.Assert(file.NonSourceFile != null); + RoslynDebug.Assert(file.AdditionalFile != null); AnalyzerStateData analyzerStateOpt = null; try @@ -826,11 +826,11 @@ public bool TryExecuteAdditionalFileActions( private void ExecuteAdditionalFileActionsCore( ImmutableArray additionalFileActions, DiagnosticAnalyzer analyzer, - SourceOrNonSourceFile file, + SourceOrAdditionalFile file, AnalyzerStateData analyzerStateOpt) { - RoslynDebug.Assert(file.NonSourceFile != null); - var additionalFile = file.NonSourceFile; + RoslynDebug.Assert(file.AdditionalFile != null); + var additionalFile = file.AdditionalFile; var diagReporter = GetAddSyntaxDiagnostic(file, analyzer); @@ -1796,19 +1796,19 @@ private Action GetAddCompilationDiagnostic(DiagnosticAnalyzer analyz private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(new SourceOrNonSourceFile(tree), null, _compilation, analyzer, isSyntaxDiagnostic: false, + return AnalyzerDiagnosticReporter.GetInstance(new SourceOrAdditionalFile(tree), null, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(new SourceOrNonSourceFile(tree), span, _compilation, analyzer, isSyntaxDiagnostic: false, + return AnalyzerDiagnosticReporter.GetInstance(new SourceOrAdditionalFile(tree), span, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } - private AnalyzerDiagnosticReporter GetAddSyntaxDiagnostic(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer) + private AnalyzerDiagnosticReporter GetAddSyntaxDiagnostic(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer) { return AnalyzerDiagnosticReporter.GetInstance(file, null, _compilation, analyzer, isSyntaxDiagnostic: true, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, @@ -1883,7 +1883,7 @@ private static bool TryStartProcessingEvent(CompilationEvent nonSymbolCompilatio return analysisStateOpt == null || analysisStateOpt.TryStartProcessingEvent(nonSymbolCompilationEvent, analyzer, out analyzerStateOpt); } - private static bool TryStartSyntaxAnalysis(SourceOrNonSourceFile file, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) + private static bool TryStartSyntaxAnalysis(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) { Debug.Assert(analysisScope.Contains(analyzer)); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index edb248496b15e..67340996ab413 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -56,7 +56,7 @@ public class CompilationWithAnalyzers /// Lock to track the set of active tasks computing tree diagnostics and task computing compilation diagnostics. /// private readonly object _executingTasksLock = new object(); - private readonly Dictionary>? _executingConcurrentTreeTasksOpt; + private readonly Dictionary>? _executingConcurrentTreeTasksOpt; private Tuple? _executingCompilationOrNonConcurrentTreeTask; /// @@ -139,7 +139,7 @@ private CompilationWithAnalyzers(Compilation compilation, ImmutableArray.Empty); _analyzerManager = new AnalyzerManager(analyzers); _driverPool = new ObjectPool(() => _compilation.CreateAnalyzerDriver(analyzers, _analyzerManager, severityFilter: SeverityFilter.None)); - _executingConcurrentTreeTasksOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary>() : null; + _executingConcurrentTreeTasksOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary>() : null; _concurrentTreeTaskTokensOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary() : null; _executingCompilationOrNonConcurrentTreeTask = null; } @@ -506,7 +506,7 @@ public Task GetAnalysisResultAsync(SyntaxTree tree, Cancellation { VerifyTree(tree); - return GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(tree), Analyzers, cancellationToken); + return GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(tree), Analyzers, cancellationToken); } /// @@ -521,7 +521,7 @@ public Task GetAnalysisResultAsync(SyntaxTree tree, ImmutableArr VerifyTree(tree); VerifyExistingAnalyzersArgument(analyzers); - return GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(tree), analyzers, cancellationToken); + return GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(tree), analyzers, cancellationToken); } /// @@ -535,7 +535,7 @@ public async Task GetAnalysisResultAsync(AdditionalText file, Ca { VerifyAdditionalFile(file); - return await GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(file), Analyzers, cancellationToken).ConfigureAwait(false); + return await GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(file), Analyzers, cancellationToken).ConfigureAwait(false); } /// @@ -551,19 +551,19 @@ public async Task GetAnalysisResultAsync(AdditionalText file, Im VerifyAdditionalFile(file); VerifyExistingAnalyzersArgument(analyzers); - return await GetAnalysisResultCoreAsync(new SourceOrNonSourceFile(file), analyzers, cancellationToken).ConfigureAwait(false); + return await GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(file), analyzers, cancellationToken).ConfigureAwait(false); } - private async Task GetAnalysisResultCoreAsync(SourceOrNonSourceFile file, ImmutableArray analyzers, CancellationToken cancellationToken) + private async Task GetAnalysisResultCoreAsync(SourceOrAdditionalFile file, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, file, filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, file, filterSpan: null, isSyntacticSingleFileAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSyntaxDiagnosticsAsync(analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.ToAnalysisResult(analyzers, cancellationToken); } private async Task> GetAnalyzerSyntaxDiagnosticsCoreAsync(SyntaxTree tree, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, new SourceOrNonSourceFile(tree), filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrAdditionalFile(tree), filterSpan: null, isSyntacticSingleFileAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSyntaxDiagnosticsAsync(analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); } @@ -651,14 +651,14 @@ public Task GetAnalysisResultAsync(SemanticModel model, TextSpan private async Task GetAnalysisResultCoreAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, new SourceOrNonSourceFile(model.SyntaxTree), filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrAdditionalFile(model.SyntaxTree), filterSpan, isSyntacticSingleFileAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSemanticDiagnosticsAsync(model, analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.ToAnalysisResult(analyzers, cancellationToken); } private async Task> GetAnalyzerSemanticDiagnosticsCoreAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, new SourceOrNonSourceFile(model.SyntaxTree), filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrAdditionalFile(model.SyntaxTree), filterSpan, isSyntacticSingleFileAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSemanticDiagnosticsAsync(model, analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); } @@ -741,7 +741,7 @@ await Task.WhenAll(partialTrees.Select(tree => Task.Run(() => { var treeModel = _compilationData.GetOrCreateCachedSemanticModel(tree, _compilation, cancellationToken); - analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrNonSourceFile(tree), filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); + analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrAdditionalFile(tree), filterSpan: null, isSyntacticSingleFileAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); return ComputeAnalyzerSemanticDiagnosticsAsync(treeModel, analysisScope, cancellationToken, forceCompletePartialTrees: false); }, cancellationToken))).ConfigureAwait(false); } @@ -751,7 +751,7 @@ await Task.WhenAll(partialTrees.Select(tree => { cancellationToken.ThrowIfCancellationRequested(); var treeModel = _compilationData.GetOrCreateCachedSemanticModel(tree, _compilation, cancellationToken); - analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrNonSourceFile(tree), filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); + analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrAdditionalFile(tree), filterSpan: null, isSyntacticSingleFileAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); await ComputeAnalyzerSemanticDiagnosticsAsync(treeModel, analysisScope, cancellationToken, forceCompletePartialTrees: false).ConfigureAwait(false); } } @@ -880,9 +880,9 @@ private void GenerateCompilationEvents(AnalysisScope analysisScope, Cancellation { _ = _compilation.GetDiagnostics(cancellationToken); } - else if (!analysisScope.IsSyntaxOnlyTreeAnalysis) + else if (!analysisScope.IsSyntacticSingleFileAnalysis) { - var mappedModel = _compilationData.GetOrCreateCachedSemanticModel(analysisScope.FilterFileOpt!.SourceTree!, _compilation, cancellationToken); + var mappedModel = _compilationData.GetOrCreateCachedSemanticModel(analysisScope.FilterFileOpt!.Value.SourceTree!, _compilation, cancellationToken); _ = mappedModel.GetDiagnostics(cancellationToken: cancellationToken); } } @@ -1010,11 +1010,11 @@ private async Task ComputeAnalyzerDiagnosticsCoreAsync(AnalyzerDriver driver, As } } - private Task SetActiveAnalysisTaskAsync(Func> getNewAnalysisTask, SourceOrNonSourceFile? treeOpt, int newTaskToken, CancellationToken cancellationToken) + private Task SetActiveAnalysisTaskAsync(Func> getNewAnalysisTask, SourceOrAdditionalFile? fileOpt, int newTaskToken, CancellationToken cancellationToken) { - if (treeOpt != null) + if (fileOpt.HasValue) { - return SetActiveTreeAnalysisTaskAsync(getNewAnalysisTask, treeOpt, newTaskToken, cancellationToken); + return SetActiveTreeAnalysisTaskAsync(getNewAnalysisTask, fileOpt.Value, newTaskToken, cancellationToken); } else { @@ -1080,7 +1080,7 @@ private async Task WaitForActiveAnalysisTasksAsync(bool waitForTreeTasks, bool w } } - private async Task SetActiveTreeAnalysisTaskAsync(Func> getNewTreeAnalysisTask, SourceOrNonSourceFile tree, int newTaskToken, CancellationToken cancellationToken) + private async Task SetActiveTreeAnalysisTaskAsync(Func> getNewTreeAnalysisTask, SourceOrAdditionalFile tree, int newTaskToken, CancellationToken cancellationToken) { try { @@ -1147,22 +1147,22 @@ private void SuspendAnalysis_NoLock(Task computeTask, CancellationTokenSource ct } } - private void ClearExecutingTask(Task? computeTask, SourceOrNonSourceFile? treeOpt) + private void ClearExecutingTask(Task? computeTask, SourceOrAdditionalFile? fileOpt) { if (computeTask != null) { lock (_executingTasksLock) { Tuple? executingTask; - if (treeOpt != null && _analysisOptions.ConcurrentAnalysis) + if (fileOpt.HasValue && _analysisOptions.ConcurrentAnalysis) { Debug.Assert(_executingConcurrentTreeTasksOpt != null); Debug.Assert(_concurrentTreeTaskTokensOpt != null); - if (_executingConcurrentTreeTasksOpt.TryGetValue(treeOpt, out executingTask) && + if (_executingConcurrentTreeTasksOpt.TryGetValue(fileOpt.Value, out executingTask) && executingTask.Item1 == computeTask) { - _executingConcurrentTreeTasksOpt.Remove(treeOpt); + _executingConcurrentTreeTasksOpt.Remove(fileOpt.Value); } if (_concurrentTreeTaskTokensOpt.ContainsKey(computeTask)) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs index b8dd222e38637..b0c476cf5af44 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs @@ -1309,7 +1309,7 @@ public void ReportDiagnostic(Diagnostic diagnostic) /// Context for an additional file action. /// An additional file action can use an to report s about a non-source document. /// - public struct AdditionalFileAnalysisContext + public readonly struct AdditionalFileAnalysisContext { private readonly Action _reportDiagnostic; private readonly Func _isSupportedDiagnostic; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrAdditionalFile.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrAdditionalFile.cs new file mode 100644 index 0000000000000..4ee354b34ba2a --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrAdditionalFile.cs @@ -0,0 +1,60 @@ +// 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 enable + +using System; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Represents a source file or an additional file. + /// For source files, is non-null and is null. + /// For additional files, is non-null and is null. + /// + internal readonly struct SourceOrAdditionalFile + : IEquatable + { + public SyntaxTree? SourceTree { get; } + public AdditionalText? AdditionalFile { get; } + + public SourceOrAdditionalFile(SyntaxTree tree) + { + SourceTree = tree; + AdditionalFile = null; + } + + public SourceOrAdditionalFile(AdditionalText file) + { + AdditionalFile = file; + SourceTree = null; + } + + public override bool Equals(object? obj) + => obj is SourceOrAdditionalFile file && Equals(file); + + public bool Equals(SourceOrAdditionalFile other) + => SourceTree == other.SourceTree && AdditionalFile == other.AdditionalFile; + + public static bool operator ==(SourceOrAdditionalFile left, SourceOrAdditionalFile right) + => Equals(left, right); + + public static bool operator !=(SourceOrAdditionalFile left, SourceOrAdditionalFile right) + => !Equals(left, right); + + public override int GetHashCode() + { + if (SourceTree != null) + { + return Hash.Combine(true, SourceTree.GetHashCode()); + } + else + { + RoslynDebug.Assert(AdditionalFile != null); + return Hash.Combine(false, AdditionalFile.GetHashCode()); + } + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs deleted file mode 100644 index 0c0bd96083446..0000000000000 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrNonSourceFile.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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 enable - -using System; -using System.Diagnostics.CodeAnalysis; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics -{ - /// - /// Represents a source file or a non-source file. - /// For source files, is non-null and is null. - /// For non-source files, is non-null and is null. - /// - internal sealed class SourceOrNonSourceFile - : IEquatable - { - public SyntaxTree? SourceTree { get; } - public AdditionalText? NonSourceFile { get; } - - public SourceOrNonSourceFile(SyntaxTree tree) - => SourceTree = tree; - - public SourceOrNonSourceFile(AdditionalText file) - => NonSourceFile = file; - - public bool Equals([AllowNull] SourceOrNonSourceFile? other) - => other != null && SourceTree == other.SourceTree && NonSourceFile == other.NonSourceFile; - - public override bool Equals(object? obj) - => Equals(obj as SourceOrNonSourceFile); - - public static bool operator ==([AllowNull] SourceOrNonSourceFile? left, [AllowNull] SourceOrNonSourceFile? right) - => Equals(left, right); - - public static bool operator !=([AllowNull] SourceOrNonSourceFile? left, [AllowNull] SourceOrNonSourceFile? right) - => !Equals(left, right); - - public override int GetHashCode() - { - if (SourceTree != null) - { - return Hash.Combine(true, SourceTree.GetHashCode()); - } - else - { - RoslynDebug.Assert(NonSourceFile != null); - return Hash.Combine(false, NonSourceFile.GetHashCode()); - } - } - } -} From 34babaa0b41990c1399664ac25e6c8193ac3ca7e Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 13 Jul 2020 10:38:30 -0700 Subject: [PATCH 11/11] Address feedback --- .../Portable/Diagnostics/AnalyzerHelper.cs | 14 +++++----- .../DefaultDiagnosticAnalyzerService.cs | 22 ++++++++-------- .../AggregateIncrementalAnalyzer.cs | 24 ++++++++--------- ...WorkCoordinator.NormalPriorityProcessor.cs | 26 +++++++++---------- .../SolutionCrawler/IIncrementalAnalyzer2.cs | 8 +++--- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index 514ef550a85db..c43e8510794e4 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -459,11 +459,11 @@ async Task VerifyDiagnosticLocationAsync(string id, Location location) } #endif - public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, TextDocument targetDocument, TextSpan? span = null) + public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, TextDocument targetTextDocument, TextSpan? span = null) { foreach (var diagnostic in diagnostics) { - if (!IsReportedInDocument(diagnostic, targetDocument)) + if (!IsReportedInDocument(diagnostic, targetTextDocument)) { continue; } @@ -473,21 +473,21 @@ public static IEnumerable ConvertToLocalDiagnostics(this IEnumer continue; } - yield return DiagnosticData.Create(diagnostic, targetDocument); + yield return DiagnosticData.Create(diagnostic, targetTextDocument); } - static bool IsReportedInDocument(Diagnostic diagnostic, TextDocument targetDocument) + static bool IsReportedInDocument(Diagnostic diagnostic, TextDocument targetTextDocument) { if (diagnostic.Location.SourceTree != null) { - return targetDocument.Project.GetDocument(diagnostic.Location.SourceTree) == targetDocument; + return targetTextDocument.Project.GetDocument(diagnostic.Location.SourceTree) == targetTextDocument; } else if (diagnostic.Location.Kind == LocationKind.ExternalFile) { var lineSpan = diagnostic.Location.GetLineSpan(); - var documentIds = targetDocument.Project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); - return documentIds.Any(id => id == targetDocument.Id); + var documentIds = targetTextDocument.Project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); + return documentIds.Any(id => id == targetTextDocument.Id); } return false; diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index edc4dfad222a8..91076762f0b49 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -84,21 +84,21 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) => AnalyzeSyntaxOrNonSourceDocumentAsync(document, cancellationToken); - public Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken) - => AnalyzeSyntaxOrNonSourceDocumentAsync(document, cancellationToken); + public Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeSyntaxOrNonSourceDocumentAsync(textDocument, cancellationToken); - private async Task AnalyzeSyntaxOrNonSourceDocumentAsync(TextDocument document, CancellationToken cancellationToken) + private async Task AnalyzeSyntaxOrNonSourceDocumentAsync(TextDocument textDocument, CancellationToken cancellationToken) { - Debug.Assert(document.Project.Solution.Workspace == _workspace); + Debug.Assert(textDocument.Project.Solution.Workspace == _workspace); // right now, there is no way to observe diagnostics for closed file. - if (!_workspace.IsDocumentOpen(document.Id) || + if (!_workspace.IsDocumentOpen(textDocument.Id) || !_workspace.Options.GetOption(InternalRuntimeDiagnosticOptions.Syntax)) { return; } - await AnalyzeForKindAsync(document, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false); + await AnalyzeForKindAsync(textDocument, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false); } public async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) @@ -213,17 +213,17 @@ public Task DocumentResetAsync(Document document, CancellationToken cancellation return RemoveDocumentAsync(document.Id, cancellationToken); } - public Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + public Task NonSourceDocumentResetAsync(TextDocument textDocument, CancellationToken cancellationToken) { // no closed file diagnostic and file is not opened, remove any existing diagnostics - return RemoveDocumentAsync(document.Id, cancellationToken); + return RemoveDocumentAsync(textDocument.Id, cancellationToken); } public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) => DocumentResetAsync(document, cancellationToken); - public Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) - => NonSourceDocumentResetAsync(document, cancellationToken); + public Task NonSourceDocumentCloseAsync(TextDocument textDocument, CancellationToken cancellationToken) + => NonSourceDocumentResetAsync(textDocument, cancellationToken); private void RaiseEmptyDiagnosticUpdated(AnalysisKind kind, DocumentId documentId) { @@ -237,7 +237,7 @@ public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, Invocati public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) => Task.CompletedTask; - public Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + public Task NonSourceDocumentOpenAsync(TextDocument textDocument, CancellationToken cancellationToken) => Task.CompletedTask; public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs index a8cf77d146f8c..8d94fcf2853d9 100644 --- a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs @@ -127,39 +127,39 @@ public async Task RemoveProjectAsync(ProjectId projectId, CancellationToken canc } } - public async Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + public async Task NonSourceDocumentOpenAsync(TextDocument textDocument, CancellationToken cancellationToken) { - if (TryGetAnalyzer(document.Project, out var analyzer) && + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && analyzer is IIncrementalAnalyzer2 analyzer2) { - await analyzer2.NonSourceDocumentOpenAsync(document, cancellationToken).ConfigureAwait(false); + await analyzer2.NonSourceDocumentOpenAsync(textDocument, cancellationToken).ConfigureAwait(false); } } - public async Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + public async Task NonSourceDocumentCloseAsync(TextDocument textDocument, CancellationToken cancellationToken) { - if (TryGetAnalyzer(document.Project, out var analyzer) && + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && analyzer is IIncrementalAnalyzer2 analyzer2) { - await analyzer2.NonSourceDocumentCloseAsync(document, cancellationToken).ConfigureAwait(false); + await analyzer2.NonSourceDocumentCloseAsync(textDocument, cancellationToken).ConfigureAwait(false); } } - public async Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + public async Task NonSourceDocumentResetAsync(TextDocument textDocument, CancellationToken cancellationToken) { - if (TryGetAnalyzer(document.Project, out var analyzer) && + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && analyzer is IIncrementalAnalyzer2 analyzer2) { - await analyzer2.NonSourceDocumentResetAsync(document, cancellationToken).ConfigureAwait(false); + await analyzer2.NonSourceDocumentResetAsync(textDocument, cancellationToken).ConfigureAwait(false); } } - public async Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken) + public async Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) { - if (TryGetAnalyzer(document.Project, out var analyzer) && + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && analyzer is IIncrementalAnalyzer2 analyzer2) { - await analyzer2.AnalyzeNonSourceDocumentAsync(document, reasons, cancellationToken).ConfigureAwait(false); + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 5ecee271076fe..6d2b6b04cce8e 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -334,25 +334,25 @@ private async Task ProcessDocumentAsync(ImmutableArray ana { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) { - var document = solution.GetTextDocument(documentId); + var textDocument = solution.GetTextDocument(documentId); - if (document != null) + if (textDocument != null) { // if we are called because a document is opened, we invalidate the document so that // it can be re-analyzed. otherwise, since newly opened document has same version as before // analyzer will simply return same data back if (workItem.MustRefresh && !workItem.IsRetry) { - var isOpen = document.IsOpen(); + var isOpen = textDocument.IsOpen(); - await ProcessOpenDocumentIfNeededAsync(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); - await ProcessCloseDocumentIfNeededAsync(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); + await ProcessOpenDocumentIfNeededAsync(analyzers, workItem, textDocument, isOpen, cancellationToken).ConfigureAwait(false); + await ProcessCloseDocumentIfNeededAsync(analyzers, workItem, textDocument, isOpen, cancellationToken).ConfigureAwait(false); } // check whether we are having special reanalyze request - await ProcessReanalyzeDocumentAsync(workItem, document, cancellationToken).ConfigureAwait(false); + await ProcessReanalyzeDocumentAsync(workItem, textDocument, cancellationToken).ConfigureAwait(false); - await Processor.ProcessDocumentAnalyzersAsync(document, analyzers, workItem, cancellationToken).ConfigureAwait(false); + await Processor.ProcessDocumentAnalyzersAsync(textDocument, analyzers, workItem, cancellationToken).ConfigureAwait(false); } else { @@ -389,16 +389,16 @@ private async Task ProcessDocumentAsync(ImmutableArray ana } } - private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument textDocument, bool isOpen, CancellationToken cancellationToken) { if (!isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentOpened)) { return; } - SolutionCrawlerLogger.LogProcessOpenDocument(Processor._logAggregator, document.Id.Id); + SolutionCrawlerLogger.LogProcessOpenDocument(Processor._logAggregator, textDocument.Id.Id); - await Processor.RunAnalyzersAsync(analyzers, document, workItem, DocumentOpenAsync, cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(analyzers, textDocument, workItem, DocumentOpenAsync, cancellationToken).ConfigureAwait(false); return; static async Task DocumentOpenAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) @@ -414,16 +414,16 @@ static async Task DocumentOpenAsync(IIncrementalAnalyzer analyzer, TextDocument } } - private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument textDocument, bool isOpen, CancellationToken cancellationToken) { if (isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentClosed)) { return; } - SolutionCrawlerLogger.LogProcessCloseDocument(Processor._logAggregator, document.Id.Id); + SolutionCrawlerLogger.LogProcessCloseDocument(Processor._logAggregator, textDocument.Id.Id); - await Processor.RunAnalyzersAsync(analyzers, document, workItem, DocumentCloseAsync, cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(analyzers, textDocument, workItem, DocumentCloseAsync, cancellationToken).ConfigureAwait(false); return; static async Task DocumentCloseAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs b/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs index 0423fce9120da..47918061fb6e0 100644 --- a/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs +++ b/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs @@ -11,14 +11,14 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler // IVT partners that use IIncrementalAnalyzer have migrated to ExternalAccess layer. internal interface IIncrementalAnalyzer2 : IIncrementalAnalyzer { - Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken); - Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken); + Task NonSourceDocumentOpenAsync(TextDocument textDocument, CancellationToken cancellationToken); + Task NonSourceDocumentCloseAsync(TextDocument textDocument, CancellationToken cancellationToken); /// /// Resets all the document state cached by the analyzer. /// - Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken); + Task NonSourceDocumentResetAsync(TextDocument textDocument, CancellationToken cancellationToken); - Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken); + Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken); } }