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;