Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analyzer action for reporting diagnostics in additional files #45076

Merged
16 commits merged into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12594,6 +12594,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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public void DiagnosticAnalyzerAllInOne()
missingSyntaxKinds.Add(SyntaxKind.FunctionPointerType);

var analyzer = new CSharpTrackingDiagnosticAnalyzer();
CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer });
var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray<AdditionalText>());
CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }, options);
analyzer.VerifyAllAnalyzerMembersWereCalled();
analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds();
analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(missingSyntaxKinds);
Expand Down Expand Up @@ -94,8 +95,10 @@ public class C
public void AnalyzerDriverIsSafeAgainstAnalyzerExceptions()
{
var compilation = CreateCompilationWithMscorlib45(TestResource.AllInOneCSharpCode);
var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray<AdditionalText>());

ThrowingDiagnosticAnalyzer<SyntaxKind>.VerifyAnalyzerEngineIsSafeAgainstExceptions(analyzer =>
compilation.GetAnalyzerDiagnostics(new[] { analyzer }, null));
compilation.GetAnalyzerDiagnostics(new[] { analyzer }, options));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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;
Expand Down Expand Up @@ -3586,5 +3587,163 @@ await compilationWithAnalyzers.GetAnalysisResultAsync(tree1, CancellationToken.N
}
}
}

[Theory, CombinatorialData]
public async Task TestAdditionalFileAnalyzer(bool registerFromInitialize)
{
var tree = CSharpSyntaxTree.ParseText(string.Empty);
var compilation = CreateCompilation(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<DiagnosticAnalyzer>(analyzer);

var diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None);
verifyDiagnostics(diagnostics);

var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile, CancellationToken.None);
verifyDiagnostics(analysisResult.GetAllDiagnostics());
verifyDiagnostics(analysisResult.AdditionalFileDiagnostics[additionalFile][analyzer]);

analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None);
verifyDiagnostics(analysisResult.GetAllDiagnostics());
verifyDiagnostics(analysisResult.AdditionalFileDiagnostics[additionalFile][analyzer]);

void verifyDiagnostics(ImmutableArray<Diagnostic> 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<DiagnosticAnalyzer>(analyzer1, analyzer2);

var diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None);
verifyDiagnostics(diagnostics, analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths);

var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile1, CancellationToken.None);
verifyAnalysisResult(analysisResult, analyzers, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths);
analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile2, CancellationToken.None);
verifyAnalysisResult(analysisResult, analyzers, ImmutableArray.Create(additionalFile2), diagnosticSpan, additionalFilesHaveSamePaths);

var singleAnalyzerArray = ImmutableArray.Create<DiagnosticAnalyzer>(analyzer1);
analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile1, singleAnalyzerArray, CancellationToken.None);
verifyAnalysisResult(analysisResult, singleAnalyzerArray, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths);
analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile2, singleAnalyzerArray, CancellationToken.None);
verifyAnalysisResult(analysisResult, singleAnalyzerArray, ImmutableArray.Create(additionalFile2), diagnosticSpan, additionalFilesHaveSamePaths);

analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None);
verifyDiagnostics(analysisResult.GetAllDiagnostics(), analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths);

if (!additionalFilesHaveSamePaths)
{
verifyAnalysisResult(analysisResult, analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths, verifyGetAllDiagnostics: false);
}

return;

static void verifyDiagnostics(
ImmutableArray<Diagnostic> diagnostics,
ImmutableArray<DiagnosticAnalyzer> analyzers,
ImmutableArray<AdditionalText> additionalFiles,
TextSpan diagnosticSpan,
bool additionalFilesHaveSamePaths)
{
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);
}

static void verifyAnalysisResult(
AnalysisResult analysisResult,
ImmutableArray<DiagnosticAnalyzer> analyzers,
ImmutableArray<AdditionalText> additionalFiles,
TextSpan diagnosticSpan,
bool additionalFilesHaveSamePaths,
bool verifyGetAllDiagnostics = true)
{
if (verifyGetAllDiagnostics)
{
verifyDiagnostics(analysisResult.GetAllDiagnostics(), analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths);
}

foreach (var analyzer in analyzers)
{
var singleAnalyzerArray = ImmutableArray.Create(analyzer);
foreach (var additionalFile in additionalFiles)
{
var reportedDiagnostics = getReportedDiagnostics(analysisResult, analyzer, additionalFile);
verifyDiagnostics(reportedDiagnostics, singleAnalyzerArray, ImmutableArray.Create(additionalFile), diagnosticSpan, additionalFilesHaveSamePaths);
}
}

return;

static ImmutableArray<Diagnostic> getReportedDiagnostics(AnalysisResult analysisResult, DiagnosticAnalyzer analyzer, AdditionalText additionalFile)
{
if (analysisResult.AdditionalFileDiagnostics.TryGetValue(additionalFile, out var diagnosticsMap) &&
diagnosticsMap.TryGetValue(analyzer, out var diagnostics))
{
return diagnostics;
}

return ImmutableArray<Diagnostic>.Empty;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,23 +24,25 @@ public void InitializeTest()
var parseOptions = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.None)
.WithFeatures(new[] { new KeyValuePair<string, string>("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<AdditionalText>());

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);
Expand Down Expand Up @@ -73,7 +76,8 @@ public override void Initialize(AnalysisContext c)
c.RegisterSemanticModelAction(b => ThrowIfMatch(nameof(c.RegisterSemanticModelAction), new AnalysisContextInfo(b.SemanticModel)));
c.RegisterSymbolAction(b => ThrowIfMatch(nameof(c.RegisterSymbolAction), new AnalysisContextInfo(b.Compilation, b.Symbol)), SymbolKind.NamedType);
c.RegisterSyntaxNodeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxNodeAction), new AnalysisContextInfo(b.SemanticModel.Compilation, b.Node)), SyntaxKind.ReturnStatement);
c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, b.Tree)));
c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, new SourceOrAdditionalFile(b.Tree))));
c.RegisterAdditionalFileAction(b => ThrowIfMatch(nameof(c.RegisterAdditionalFileAction), new AnalysisContextInfo(b.Compilation, new SourceOrAdditionalFile(b.AdditionalFile))));
}

private void ThrowIfMatch(string context, AnalysisContextInfo info)
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/Core/Portable/CodeAnalysisResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,9 @@
<data name="InvalidTree" xml:space="preserve">
<value>Syntax tree doesn't belong to the underlying 'Compilation'.</value>
</data>
<data name="InvalidAdditionalFile" xml:space="preserve">
<value>Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'.</value>
</data>
<data name="ResourceStreamEndedUnexpectedly" xml:space="preserve">
<value>Resource stream ended at {0} bytes, expected {1} bytes.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public override TextSpan SourceSpan
}
}

public string FilePath => _lineSpan.Path;

public override FileLinePositionSpan GetLineSpan()
{
return _lineSpan;
Expand Down
Loading