提交 f876a57f 编写于 作者: M Manish Vasani

Allow analyzers to configure generated code analysis.

1. Add a new API: AnalysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags analysisMode)
Configures analyzer callbacks + diagnostic reporting for the analyzer. Recommended for analyzer authors to always invoke this API.

2. Analyzer driver uses a heuristic to identify generated code:
  1. Symbols marked with GeneratedCodeAttribute.
  2. Files with specific extensions (see [here](http://source.roslyn.io/Microsoft.CodeAnalysis.Workspaces/R/ef3599fb042e3706.html)).
  3. Files which have a single line comment starting with <auto-generated> at the beginning of a source file.

3. Driver defaults for non-configured analyzers:
  1. Run analysis on generated code: This avoids semantic breaks for analyzers and will be guaranteed to be always enabled in future.
  2. Report all diagnostics on generated code: This will likely be changed to perform some level of default filtering after #3705 is implemented.

Fixes #6998
上级 860f5c5f
......@@ -2878,7 +2878,9 @@ internal bool HasDynamicEmitAttributes()
internal override AnalyzerDriver AnalyzerForLanguage(ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerManager analyzerManager)
{
return new AnalyzerDriver<SyntaxKind>(analyzers, n => n.Kind(), analyzerManager);
Func<SyntaxNode, SyntaxKind> getKind = node => node.Kind();
Func<SyntaxTrivia, bool> isComment = trivia => trivia.Kind() == SyntaxKind.SingleLineCommentTrivia || trivia.Kind() == SyntaxKind.MultiLineCommentTrivia;
return new AnalyzerDriver<SyntaxKind>(analyzers, getKind, analyzerManager, isComment);
}
internal void SymbolDeclaredEvent(Symbol symbol)
......
......@@ -1233,5 +1233,263 @@ public void TestConcurrentAnalyzer()
analyzers = new DiagnosticAnalyzer[] { new ConcurrentAnalyzer(typeNames), new NonConcurrentAnalyzer() };
compilation.VerifyAnalyzerDiagnostics(analyzers, expected: expected);
}
[Fact, WorkItem(6998, "https://github.com/dotnet/roslyn/issues/6998")]
public void TestGeneratedCodeAnalyzer()
{
string source = @"
[System.CodeDom.Compiler.GeneratedCodeAttribute(""tool"", ""version"")]
class GeneratedCode{0}
{{
private class Nested{0} {{ }}
}}
class NonGeneratedCode{0}
{{
[System.CodeDom.Compiler.GeneratedCodeAttribute(""tool"", ""version"")]
private class NestedGeneratedCode{0} {{ }}
}}
";
var generatedFileNames = new string[]
{
"TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs",
"Test.designer.cs",
"Test.Designer.cs",
"Test.generated.cs",
"Test.g.cs",
"Test.g.i.cs"
};
var builder = ImmutableArray.CreateBuilder<SyntaxTree>();
int treeNum = 0;
// Trees with non-generated code file names
var tree = CSharpSyntaxTree.ParseText(string.Format(source, treeNum++), path: "SourceFileRegular.cs");
builder.Add(tree);
tree = CSharpSyntaxTree.ParseText(string.Format(source, treeNum++), path: "AssemblyInfo.cs");
builder.Add(tree);
// Trees with generated code file names
foreach (var fileName in generatedFileNames)
{
tree = CSharpSyntaxTree.ParseText(string.Format(source, treeNum++), path: fileName);
builder.Add(tree);
}
var autoGeneratedPrefixes = new[] { @"// <auto-generated>", @"// <autogenerated>", @"/* <auto-generated> */" };
for (var i = 0; i < autoGeneratedPrefixes.Length; i++)
{
// Tree with '<auto-generated>' comment
var autoGeneratedPrefix = autoGeneratedPrefixes[i];
tree = CSharpSyntaxTree.ParseText(string.Format(autoGeneratedPrefix + source, treeNum++), path: $"SourceFileWithAutoGeneratedComment{i++}.cs");
builder.Add(tree);
}
// Verify no compiler diagnostics.
var trees = builder.ToImmutable();
var compilation = CreateCompilationWithMscorlib45(trees, new MetadataReference[] { SystemRef });
compilation.VerifyDiagnostics();
Func<string, bool> isGeneratedFile = fileName => fileName.Contains("SourceFileWithAutoGeneratedComment") || generatedFileNames.Contains(fileName);
// (1) Verify default mode of analysis when there is no generated code configuration.
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, generatedCodeAnalysisFlagsOpt: null);
// (2) Verify ConfigureGeneratedCodeAnalysis with different combinations of GeneratedCodeAnalysisFlags.
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.None);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.Default);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.Analyze);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.ReportDiagnostics);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
// (4) Ensure warnaserror doesn't produce noise in generated files.
var options = compilation.Options.WithGeneralDiagnosticOption(ReportDiagnostic.Error);
var warnAsErrorCompilation = compilation.WithOptions(options);
VerifyGeneratedCodeAnalyzerDiagnostics(warnAsErrorCompilation, isGeneratedFile, generatedCodeAnalysisFlagsOpt: null);
}
[Fact, WorkItem(6998, "https://github.com/dotnet/roslyn/issues/6998")]
public void TestGeneratedCodeAnalyzerPartialType()
{
string source = @"
[System.CodeDom.Compiler.GeneratedCodeAttribute(""tool"", ""version"")]
partial class PartialType
{
}
partial class PartialType
{
}
";
var tree = CSharpSyntaxTree.ParseText(source, path: "SourceFileRegular.cs");
var compilation = CreateCompilationWithMscorlib45(new[] { tree }, new MetadataReference[] { SystemRef });
compilation.VerifyDiagnostics();
var builder = ArrayBuilder<DiagnosticDescription>.GetInstance();
// Expected symbol diagnostics
var squiggledText = "PartialType";
var diagnosticArgument = squiggledText;
var line = 3;
var column = 15;
AddExpectedLocalDiagnostics(builder, false, squiggledText, line, column, GeneratedCodeAnalysisFlags.ReportDiagnostics, diagnosticArgument);
// Expected tree diagnostics
squiggledText = "}";
diagnosticArgument = tree.FilePath;
line = 9;
column = 1;
AddExpectedLocalDiagnostics(builder, false, squiggledText, line, column, GeneratedCodeAnalysisFlags.ReportDiagnostics, diagnosticArgument);
// Expected compilation diagnostics
AddExpectedNonLocalDiagnostic(builder, "PartialType", compilation.SyntaxTrees[0].FilePath);
var expected = builder.ToArrayAndFree();
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, generatedCodeAnalysisFlagsOpt: null);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.None);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Default);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Analyze);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.ReportDiagnostics);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
}
private static void VerifyGeneratedCodeAnalyzerDiagnostics(Compilation compilation, Func<string, bool> isGeneratedFileName, GeneratedCodeAnalysisFlags? generatedCodeAnalysisFlagsOpt)
{
var expected = GetExpectedGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFileName, generatedCodeAnalysisFlagsOpt);
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, generatedCodeAnalysisFlagsOpt);
}
private static void VerifyGeneratedCodeAnalyzerDiagnostics(Compilation compilation, DiagnosticDescription[] expected, GeneratedCodeAnalysisFlags? generatedCodeAnalysisFlagsOpt)
{
var analyzers = new DiagnosticAnalyzer[] { new GeneratedCodeAnalyzer(generatedCodeAnalysisFlagsOpt) };
compilation.VerifyAnalyzerDiagnostics(analyzers, null, null, logAnalyzerExceptionAsDiagnostics: false, expected: expected);
}
private static DiagnosticDescription[] GetExpectedGeneratedCodeAnalyzerDiagnostics(Compilation compilation, Func<string, bool> isGeneratedFileName, GeneratedCodeAnalysisFlags? generatedCodeAnalysisFlagsOpt)
{
var analyzers = new DiagnosticAnalyzer[] { new GeneratedCodeAnalyzer(generatedCodeAnalysisFlagsOpt) };
var files = compilation.SyntaxTrees.Select(t => t.FilePath).ToImmutableArray();
var sortedCallbackSymbolNames = new SortedSet<string>();
var sortedCallbackTreePaths = new SortedSet<string>();
var builder = ArrayBuilder<DiagnosticDescription>.GetInstance();
for (int i = 0; i < compilation.SyntaxTrees.Count(); i++)
{
var file = files[i];
var isGeneratedFile = isGeneratedFileName(file);
// Type "GeneratedCode{0}"
var squiggledText = string.Format("GeneratedCode{0}", i);
var diagnosticArgument = squiggledText;
var line = 3;
var column = 7;
var isGeneratedCode = true;
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument);
// Type "Nested{0}"
squiggledText = string.Format("Nested{0}", i);
diagnosticArgument = squiggledText;
line = 5;
column = 19;
isGeneratedCode = true;
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument);
// Type "NonGeneratedCode{0}"
squiggledText = string.Format("NonGeneratedCode{0}", i);
diagnosticArgument = squiggledText;
line = 8;
column = 7;
isGeneratedCode = isGeneratedFile;
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument);
// Type "NestedGeneratedCode{0}"
squiggledText = string.Format("NestedGeneratedCode{0}", i);
diagnosticArgument = squiggledText;
line = 11;
column = 19;
isGeneratedCode = true;
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument);
// File diagnostic
squiggledText = "}"; // last token in file.
diagnosticArgument = file;
line = 12;
column = 1;
isGeneratedCode = isGeneratedFile;
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument);
// Compilation end summary diagnostic (verify callbacks into analyzer)
// Analyzer always called for generated code, unless generated code analysis is explicitly disabled.
if (generatedCodeAnalysisFlagsOpt == null || (generatedCodeAnalysisFlagsOpt & GeneratedCodeAnalysisFlags.Analyze) != 0)
{
sortedCallbackSymbolNames.Add(string.Format("GeneratedCode{0}", i));
sortedCallbackSymbolNames.Add(string.Format("Nested{0}", i));
sortedCallbackSymbolNames.Add(string.Format("NonGeneratedCode{0}", i));
sortedCallbackSymbolNames.Add(string.Format("NestedGeneratedCode{0}", i));
sortedCallbackTreePaths.Add(file);
}
else if (!isGeneratedFile)
{
// Analyzer always called for non-generated code.
sortedCallbackSymbolNames.Add(string.Format("NonGeneratedCode{0}", i));
sortedCallbackTreePaths.Add(file);
}
}
// Compilation end summary diagnostic (verify callbacks into analyzer)
var arg1 = sortedCallbackSymbolNames.Join(",");
var arg2 = sortedCallbackTreePaths.Join(",");
AddExpectedNonLocalDiagnostic(builder, arguments: new[] { arg1, arg2 });
if (compilation.Options.GeneralDiagnosticOption == ReportDiagnostic.Error)
{
for(int i = 0; i < builder.Count; i++)
{
if (((string)builder[i].Code) != GeneratedCodeAnalyzer.Error.Id)
{
builder[i] = builder[i].WithWarningAsError(true);
}
}
}
return builder.ToArrayAndFree();
}
private static void AddExpectedLocalDiagnostics(
ArrayBuilder<DiagnosticDescription> builder,
bool isGeneratedCode,
string squiggledText,
int line,
int column,
GeneratedCodeAnalysisFlags? generatedCodeAnalysisFlagsOpt,
params string[] arguments)
{
// Always report diagnostics in generated code, unless explicitly suppressed or we are not even analyzing generated code.
var reportInGeneratedCode = generatedCodeAnalysisFlagsOpt == null ||
((generatedCodeAnalysisFlagsOpt & GeneratedCodeAnalysisFlags.ReportDiagnostics) != 0 &&
(generatedCodeAnalysisFlagsOpt & GeneratedCodeAnalysisFlags.Analyze) != 0);
if (!isGeneratedCode || reportInGeneratedCode)
{
var diagnostic = Diagnostic(GeneratedCodeAnalyzer.Warning.Id, squiggledText).WithArguments(arguments).WithLocation(line, column);
builder.Add(diagnostic);
diagnostic = Diagnostic(GeneratedCodeAnalyzer.Error.Id, squiggledText).WithArguments(arguments).WithLocation(line, column);
builder.Add(diagnostic);
}
}
private static void AddExpectedNonLocalDiagnostic(ArrayBuilder<DiagnosticDescription> builder, params string[] arguments)
{
AddExpectedDiagnostic(builder, GeneratedCodeAnalyzer.Summary.Id, squiggledText: null, line: 1, column: 1, arguments: arguments);
}
private static void AddExpectedDiagnostic(ArrayBuilder<DiagnosticDescription> builder, string diagnosticId, string squiggledText, int line, int column, params string[] arguments)
{
var diagnostic = Diagnostic(diagnosticId, squiggledText).WithArguments(arguments).WithLocation(line, column);
builder.Add(diagnostic);
}
}
}
......@@ -65,6 +65,7 @@
<Compile Include="CaseInsensitiveComparison.cs" />
<Compile Include="CodeGen\ILEmitStyle.cs" />
<Compile Include="Compilation\ScriptCompilationInfo.cs" />
<Compile Include="DiagnosticAnalyzer\AnalyzerDriver.GeneratedCodeUtilities.cs" />
<Compile Include="DiagnosticAnalyzer\AnalyzerDriver.CompilationData.cs" />
<Compile Include="DiagnosticAnalyzer\SuppressMessageInfo.cs" />
<Compile Include="Diagnostic\SuppressionInfo.cs" />
......
......@@ -524,6 +524,17 @@ public void MarkEventComplete(CompilationEvent compilationEvent, DiagnosticAnaly
GetAnalyzerState(analyzer).MarkEventComplete(compilationEvent);
}
/// <summary>
/// Marks the given event as fully analyzed for the given analyzers.
/// </summary>
public void MarkEventComplete(CompilationEvent compilationEvent, IEnumerable<DiagnosticAnalyzer> analyzers)
{
foreach (var analyzer in analyzers)
{
GetAnalyzerState(analyzer).MarkEventComplete(compilationEvent);
}
}
/// <summary>
/// Attempts to start processing a symbol for the given analyzer's symbol actions.
/// </summary>
......@@ -580,6 +591,17 @@ public void MarkDeclarationComplete(SyntaxReference decl, DiagnosticAnalyzer ana
GetAnalyzerState(analyzer).MarkDeclarationComplete(decl);
}
/// <summary>
/// Marks the given symbol declaration as fully analyzed for the given analyzers.
/// </summary>
public void MarkDeclarationComplete(SyntaxReference decl, IEnumerable<DiagnosticAnalyzer> analyzers)
{
foreach (var analyzer in analyzers)
{
GetAnalyzerState(analyzer).MarkDeclarationComplete(decl);
}
}
/// <summary>
/// Marks all the symbol declarations for the given symbol as fully analyzed for all the given analyzers.
/// </summary>
......@@ -610,5 +632,16 @@ public void MarkSyntaxAnalysisComplete(SyntaxTree tree, DiagnosticAnalyzer analy
{
GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree);
}
/// <summary>
/// Marks the given tree as fully syntactically analyzed for the given analyzers.
/// </summary>
public void MarkSyntaxAnalysisComplete(SyntaxTree tree, IEnumerable<DiagnosticAnalyzer> analyzers)
{
foreach (var analyzer in analyzers)
{
GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
internal abstract partial class AnalyzerDriver : IDisposable
{
private static class GeneratedCodeUtilities
{
private static readonly string[] s_autoGeneratedStrings = new[] { "<autogenerated", "<auto-generated" };
internal static bool IsGeneratedSymbolWithGeneratedCodeAttribute(ISymbol symbol, INamedTypeSymbol generatedCodeAttribute)
{
Debug.Assert(symbol != null);
Debug.Assert(generatedCodeAttribute != null);
// GeneratedCodeAttribute can only be applied once on a symbol.
// For partial symbols with more than one definition, we must treat them as non-generated code symbols.
if (symbol.DeclaringSyntaxReferences.Length > 1)
{
return false;
}
if (symbol.GetAttributes().Any(a => a.AttributeClass == generatedCodeAttribute))
{
return true;
}
return symbol.ContainingSymbol != null && IsGeneratedSymbolWithGeneratedCodeAttribute(symbol.ContainingSymbol, generatedCodeAttribute);
}
internal static bool IsGeneratedCode(SyntaxTree tree, Func<SyntaxTrivia, bool> isComment, CancellationToken cancellationToken)
{
if (IsGeneratedCodeFile(tree.FilePath))
{
return true;
}
if (BeginsWithAutoGeneratedComment(tree, isComment, cancellationToken))
{
return true;
}
return false;
}
private static bool IsGeneratedCodeFile(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
return false;
}
var fileName = PathUtilities.GetFileName(filePath);
if (fileName.StartsWith("TemporaryGeneratedFile_", StringComparison.OrdinalIgnoreCase))
{
return true;
}
var extension = PathUtilities.GetExtension(fileName);
if (string.IsNullOrEmpty(extension))
{
return false;
}
var fileNameWithoutExtension = PathUtilities.GetFileName(filePath, includeExtension: false);
if (fileNameWithoutExtension.EndsWith(".designer", StringComparison.OrdinalIgnoreCase) ||
fileNameWithoutExtension.EndsWith(".generated", StringComparison.OrdinalIgnoreCase) ||
fileNameWithoutExtension.EndsWith(".g", StringComparison.OrdinalIgnoreCase) ||
fileNameWithoutExtension.EndsWith(".g.i", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
private static bool BeginsWithAutoGeneratedComment(SyntaxTree tree, Func<SyntaxTrivia, bool> isComment, CancellationToken cancellationToken)
{
var root = tree.GetRoot(cancellationToken);
if (root.HasLeadingTrivia)
{
var leadingTrivia = root.GetLeadingTrivia();
foreach (var trivia in leadingTrivia)
{
if (!isComment(trivia))
{
continue;
}
var text = trivia.ToString();
// Check to see if the text of the comment contains an auto generated comment.
foreach (var autoGenerated in s_autoGeneratedStrings)
{
if (text.Contains(autoGenerated))
{
return true;
}
}
}
}
return false;
}
}
}
}
......@@ -157,6 +157,16 @@ public async Task<bool> IsConcurrentAnalyzerAsync(DiagnosticAnalyzer analyzer, A
return sessionScope.IsConcurrentAnalyzer(analyzer);
}
/// <summary>
/// Returns <see cref="GeneratedCodeAnalysisFlags"/> for the given analyzer.
/// If an analyzer hasn't configured generated code analysis, returns <see cref="GeneratedCodeAnalysisFlags.Default"/>.
/// </summary>
public async Task<GeneratedCodeAnalysisFlags> GetGeneratedCodeAnalysisFlagsAsync(DiagnosticAnalyzer analyzer, AnalyzerExecutor analyzerExecutor)
{
var sessionScope = await GetSessionAnalysisScopeAsync(analyzer, analyzerExecutor).ConfigureAwait(false);
return sessionScope.GetGeneratedCodeAnalysisFlags(analyzer);
}
/// <summary>
/// Return <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>.
/// </summary>
......
......@@ -174,6 +174,44 @@ public void RegisterOperationAction(Action<OperationAnalysisContext> action, par
/// Hence, end actions are never executed concurrently with non-end actions operating on the same analysis unit.
/// </remarks>
public abstract void EnableConcurrentExecution();
/// <summary>
/// Configure analysis mode of generated code for this analyzer.
/// Non-configured analyzers will default to <see cref="GeneratedCodeAnalysisFlags.Default"/> mode for generated code.
/// It is recommended for the analyzer to always invoke this API with the required <see cref="GeneratedCodeAnalysisFlags"/> setting.
/// </summary>
public abstract void ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags analysisMode);
}
/// <summary>
/// Flags to configure mode of generated code analysis.
/// </summary>
[Flags]
public enum GeneratedCodeAnalysisFlags
{
/// <summary>
/// Disable analyzer action callbacks and diagnostic reporting for generated code.
/// </summary>
None = 0x00,
/// <summary>
/// Enable analyzer action callbacks for generated code.
/// </summary>
Analyze = 0x01,
/// <summary>
/// Enable reporting diagnostics on generated code.
/// </summary>
ReportDiagnostics = 0x10,
/// <summary>
/// Default analysis mode for generated code.
/// </summary>
/// <remarks>
/// This mode will always guarantee that analyzer action callbacks are enabled for generated code, i.e. <see cref="Analyze"/> will be set.
/// However, the default diagnostic reporting mode is liable to change in future.
/// </remarks>
Default = Analyze | ReportDiagnostics,
}
/// <summary>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
namespace Microsoft.CodeAnalysis.Diagnostics
{
/// <summary>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
......@@ -93,6 +94,11 @@ public override void EnableConcurrentExecution()
{
_scope.EnableConcurrentExecution(_analyzer);
}
public override void ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags mode)
{
_scope.ConfigureGeneratedCodeAnalysis(_analyzer, mode);
}
}
/// <summary>
......@@ -232,6 +238,7 @@ internal sealed class HostSessionStartAnalysisScope : HostAnalysisScope
{
private ImmutableArray<CompilationStartAnalyzerAction> _compilationStartActions = ImmutableArray<CompilationStartAnalyzerAction>.Empty;
private ImmutableHashSet<DiagnosticAnalyzer> _concurrentAnalyzers = ImmutableHashSet<DiagnosticAnalyzer>.Empty;
private ConcurrentDictionary<DiagnosticAnalyzer, GeneratedCodeAnalysisFlags> _generatedCodeConfigurationMap = new ConcurrentDictionary<DiagnosticAnalyzer, GeneratedCodeAnalysisFlags>();
public ImmutableArray<CompilationStartAnalyzerAction> CompilationStartActions
{
......@@ -243,6 +250,12 @@ public bool IsConcurrentAnalyzer(DiagnosticAnalyzer analyzer)
return _concurrentAnalyzers.Contains(analyzer);
}
public GeneratedCodeAnalysisFlags GetGeneratedCodeAnalysisFlags(DiagnosticAnalyzer analyzer)
{
GeneratedCodeAnalysisFlags mode;
return _generatedCodeConfigurationMap.TryGetValue(analyzer, out mode) ? mode : GeneratedCodeAnalysisFlags.Default;
}
public void RegisterCompilationStartAction(DiagnosticAnalyzer analyzer, Action<CompilationStartAnalysisContext> action)
{
CompilationStartAnalyzerAction analyzerAction = new CompilationStartAnalyzerAction(action, analyzer);
......@@ -254,6 +267,11 @@ public void EnableConcurrentExecution(DiagnosticAnalyzer analyzer)
{
_concurrentAnalyzers = _concurrentAnalyzers.Add(analyzer);
}
public void ConfigureGeneratedCodeAnalysis(DiagnosticAnalyzer analyzer, GeneratedCodeAnalysisFlags mode)
{
_generatedCodeConfigurationMap.AddOrUpdate(analyzer, addValue: mode, updateValueFactory: (a, c) => mode);
}
}
/// <summary>
......
......@@ -61,9 +61,9 @@ internal static string RemoveExtension(string path)
return FileNameUtilities.ChangeExtension(path, extension: null);
}
internal static string GetFileName(string path)
internal static string GetFileName(string path, bool includeExtension = true)
{
return FileNameUtilities.GetFileName(path);
return FileNameUtilities.GetFileName(path, includeExtension);
}
/// <summary>
......
......@@ -173,10 +173,11 @@ internal static int IndexOfFileName(string path)
/// Get file name from path.
/// </summary>
/// <remarks>Unlike <see cref="System.IO.Path.GetFileName"/> doesn't check for invalid path characters.</remarks>
internal static string GetFileName(string path)
internal static string GetFileName(string path, bool includeExtension = true)
{
int fileNameStart = IndexOfFileName(path);
return (fileNameStart <= 0) ? path : path.Substring(fileNameStart);
var fileName = (fileNameStart <= 0) ? path : path.Substring(fileNameStart);
return includeExtension ? fileName : RemoveExtension(fileName);
}
}
}
......@@ -9,6 +9,11 @@ Microsoft.CodeAnalysis.Compilation.WithScriptCompilationInfo(Microsoft.CodeAnaly
Microsoft.CodeAnalysis.CompilationOptions.Deterministic.get -> bool
Microsoft.CodeAnalysis.CompilationOptions.PublicSign.get -> bool
Microsoft.CodeAnalysis.CompilationOptions.ReportSuppressedDiagnostics.get -> bool
Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags
Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags.Analyze = 1 -> Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags
Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags.Default = Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags.Analyze | Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags.ReportDiagnostics -> Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags
Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags.None = 0 -> Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags
Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags.ReportDiagnostics = 16 -> Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags
Microsoft.CodeAnalysis.CompilationOptions.WithDeterministic(bool deterministic) -> Microsoft.CodeAnalysis.CompilationOptions
Microsoft.CodeAnalysis.CompilationOptions.WithPublicSign(bool publicSign) -> Microsoft.CodeAnalysis.CompilationOptions
Microsoft.CodeAnalysis.CompilationOptions.WithReportSuppressedDiagnostics(bool value) -> Microsoft.CodeAnalysis.CompilationOptions
......@@ -81,6 +86,7 @@ virtual Microsoft.CodeAnalysis.SourceReferenceResolver.ReadText(string resolvedP
Microsoft.CodeAnalysis.SemanticModel.GetOperation(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Semantics.IOperation
abstract Microsoft.CodeAnalysis.SemanticModel.GetOperationCore(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.Semantics.IOperation
abstract Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.EnableConcurrentExecution() -> void
abstract Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.ConfigureGeneratedCodeAnalysis(Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags analysisMode) -> void
Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, params Microsoft.CodeAnalysis.Semantics.OperationKind[] operationKinds) -> void
Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, params Microsoft.CodeAnalysis.Semantics.OperationKind[] operationKinds) -> void
abstract Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Semantics.OperationKind> operationKinds) -> void
......
......@@ -5,7 +5,6 @@
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
......
......@@ -2081,7 +2081,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Friend Overrides Function AnalyzerForLanguage(analyzers As ImmutableArray(Of DiagnosticAnalyzer), analyzerManager As AnalyzerManager) As AnalyzerDriver
Dim getKind As Func(Of SyntaxNode, SyntaxKind) = Function(node As SyntaxNode) node.Kind
Return New AnalyzerDriver(Of SyntaxKind)(analyzers, getKind, analyzerManager)
Dim isComment As Func(Of SyntaxTrivia, Boolean) = Function(trivia As SyntaxTrivia) trivia.Kind() = SyntaxKind.CommentTrivia
Return New AnalyzerDriver(Of SyntaxKind)(analyzers, getKind, analyzerManager, isComment)
End Function
#End Region
......
......@@ -5,6 +5,7 @@ Imports System.Runtime.Serialization
Imports Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Diagnostics.VisualBasic
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Roslyn.Test.Utilities
......@@ -812,5 +813,242 @@ End Class
TestEffectiveSeverity(DiagnosticSeverity.Warning, expectedEffectiveSeverity:=specificOption, specificOptions:=specificOptions, generalOption:=generalOption, isEnabledByDefault:=enabledByDefault)
End Sub
<Fact, WorkItem(6998, "https://github.com/dotnet/roslyn/issues/6998")>
Public Sub TestGeneratedCodeAnalyzer()
Dim source = <![CDATA[
<System.CodeDom.Compiler.GeneratedCodeAttribute("tool", "version")> _
Class GeneratedCode{0}
Private Class Nested{0}
End Class
End Class
Class NonGeneratedCode{0}
<System.CodeDom.Compiler.GeneratedCodeAttribute("tool", "version")> _
Private Class NestedGeneratedCode{0}
End Class
End Class
]]>.Value
Dim generatedFileNames =
{"TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.vb", "Test.designer.vb", "Test.Designer.vb", "Test.generated.vb", "Test.g.vb", "Test.g.i.vb"}
Dim builder = ImmutableArray.CreateBuilder(Of SyntaxTree)()
Dim treeNum As Integer = 0
' Trees with non-generated code file names
Dim tree = VisualBasicSyntaxTree.ParseText(String.Format(source, treeNum), path:="SourceFileRegular.vb")
builder.Add(tree)
treeNum = treeNum + 1
tree = VisualBasicSyntaxTree.ParseText(String.Format(source, treeNum), path:="AssemblyInfo.vb")
builder.Add(tree)
treeNum = treeNum + 1
' Trees with generated code file names
For Each fileName In generatedFileNames
tree = VisualBasicSyntaxTree.ParseText(String.Format(source, treeNum), path:=fileName)
builder.Add(tree)
treeNum = treeNum + 1
Next
' Tree with '<auto-generated>' comment
Dim autoGeneratedPrefixes = {"' <auto-generated> ", "' <autogenerated> "}
For i = 0 To autoGeneratedPrefixes.Length - 1
Dim autoGeneratedPrefix = autoGeneratedPrefixes(i)
tree = VisualBasicSyntaxTree.ParseText(String.Format(autoGeneratedPrefix + source, treeNum), path:=$"SourceFileWithAutoGeneratedComment{i}.vb")
builder.Add(tree)
treeNum = treeNum + 1
Next
' Verify no compiler diagnostics.
Dim trees = builder.ToImmutable()
Dim compilation = CreateCompilationWithMscorlib45(trees, {SystemRef}, TestOptions.ReleaseDll)
compilation.VerifyDiagnostics()
Dim isGeneratedFile As Func(Of String, Boolean) = Function(fileName) fileName.Contains("SourceFileWithAutoGeneratedComment") OrElse generatedFileNames.Contains(fileName)
' (1) Verify default mode of analysis when there is no generated code configuration.
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, generatedCodeAnalysisFlagsOpt:=Nothing)
' (2) Verify ConfigureGeneratedCodeAnalysis with different combinations of GeneratedCodeAnalysisFlags.
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.None)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.Default)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.Analyze)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.ReportDiagnostics)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFile, GeneratedCodeAnalysisFlags.Analyze Or GeneratedCodeAnalysisFlags.ReportDiagnostics)
' (4) Ensure warnaserror doesn't produce noise in generated files.
Dim options = compilation.Options.WithGeneralDiagnosticOption(ReportDiagnostic.Error)
Dim warnAsErrorCompilation = compilation.WithOptions(options)
VerifyGeneratedCodeAnalyzerDiagnostics(warnAsErrorCompilation, isGeneratedFile, generatedCodeAnalysisFlagsOpt:=Nothing)
End Sub
<Fact, WorkItem(6998, "https://github.com/dotnet/roslyn/issues/6998")>
Public Sub TestGeneratedCodeAnalyzerPartialType()
Dim source As String = <![CDATA['
<System.CodeDom.Compiler.GeneratedCodeAttribute("tool", "version")> _
Partial Class PartialType
End Class
Partial Class PartialType
End Class
]]>.Value
Dim tree = VisualBasicSyntaxTree.ParseText(source, path:="SourceFileRegular.vb")
Dim compilation = CreateCompilationWithMscorlib45({tree}, {SystemRef}, TestOptions.ReleaseDll)
compilation.VerifyDiagnostics()
Dim builder = ArrayBuilder(Of DiagnosticDescription).GetInstance()
' Expected symbol diagnostics
Dim squiggledText = "PartialType"
Dim diagnosticArgument = squiggledText
Dim line = 3
Dim column = 15
AddExpectedLocalDiagnostics(builder, False, squiggledText, line, column, GeneratedCodeAnalysisFlags.ReportDiagnostics, diagnosticArgument)
' Expected tree diagnostics
squiggledText = "Class"
diagnosticArgument = tree.FilePath
line = 7
column = 5
AddExpectedLocalDiagnostics(builder, False, squiggledText, line, column, GeneratedCodeAnalysisFlags.ReportDiagnostics, diagnosticArgument)
' Expected compilation diagnostics
AddExpectedNonLocalDiagnostic(builder, "PartialType", compilation.SyntaxTrees(0).FilePath)
Dim expected = builder.ToArrayAndFree()
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, generatedCodeAnalysisFlagsOpt:=Nothing)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.None)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Default)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Analyze)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.ReportDiagnostics)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Analyze Or GeneratedCodeAnalysisFlags.ReportDiagnostics)
End Sub
Private Shared Sub VerifyGeneratedCodeAnalyzerDiagnostics(compilation As Compilation, isGeneratedFileName As Func(Of String, Boolean), generatedCodeAnalysisFlagsOpt As GeneratedCodeAnalysisFlags?)
Dim expected = GetExpectedGeneratedCodeAnalyzerDiagnostics(compilation, isGeneratedFileName, generatedCodeAnalysisFlagsOpt)
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, generatedCodeAnalysisFlagsOpt)
End Sub
Private Shared Sub VerifyGeneratedCodeAnalyzerDiagnostics(compilation As Compilation, expected As DiagnosticDescription(), generatedCodeAnalysisFlagsOpt As GeneratedCodeAnalysisFlags?)
Dim analyzers = New DiagnosticAnalyzer() {New GeneratedCodeAnalyzer(generatedCodeAnalysisFlagsOpt)}
compilation.VerifyAnalyzerDiagnostics(analyzers, Nothing, Nothing, False, expected)
End Sub
Private Shared Function GetExpectedGeneratedCodeAnalyzerDiagnostics(compilation As Compilation, isGeneratedFileName As Func(Of String, Boolean), generatedCodeAnalysisFlagsOpt As GeneratedCodeAnalysisFlags?) As DiagnosticDescription()
Dim analyzers = New DiagnosticAnalyzer() {New GeneratedCodeAnalyzer(generatedCodeAnalysisFlagsOpt)}
Dim files = compilation.SyntaxTrees.Select(Function(t) t.FilePath).ToImmutableArray()
Dim sortedCallbackSymbolNames = New SortedSet(Of String)()
Dim sortedCallbackTreePaths = New SortedSet(Of String)()
Dim builder = ArrayBuilder(Of DiagnosticDescription).GetInstance()
For i As Integer = 0 To compilation.SyntaxTrees.Count() - 1
Dim file = files(i)
Dim isGeneratedFile = isGeneratedFileName(file)
' Type "GeneratedCode{0}"
Dim squiggledText = String.Format("GeneratedCode{0}", i)
Dim diagnosticArgument = squiggledText
Dim line = 3
Dim column = 7
Dim isGeneratedCode = True
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument)
' Type "Nested{0}"
squiggledText = String.Format("Nested{0}", i)
diagnosticArgument = squiggledText
line = 4
column = 16
isGeneratedCode = True
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument)
' Type "NonGeneratedCode{0}"
squiggledText = String.Format("NonGeneratedCode{0}", i)
diagnosticArgument = squiggledText
line = 8
column = 7
isGeneratedCode = isGeneratedFile
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument)
' Type "NestedGeneratedCode{0}"
squiggledText = String.Format("NestedGeneratedCode{0}", i)
diagnosticArgument = squiggledText
line = 10
column = 16
isGeneratedCode = True
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument)
' File diagnostic
squiggledText = "Class" ' last token in file.
diagnosticArgument = file
line = 12
column = 5
isGeneratedCode = isGeneratedFile
AddExpectedLocalDiagnostics(builder, isGeneratedCode, squiggledText, line, column, generatedCodeAnalysisFlagsOpt, diagnosticArgument)
' Compilation end summary diagnostic (verify callbacks into analyzer)
' Analyzer always called for generated code, unless generated code analysis is explicitly disabled.
If generatedCodeAnalysisFlagsOpt Is Nothing OrElse (generatedCodeAnalysisFlagsOpt And GeneratedCodeAnalysisFlags.Analyze) <> 0 Then
sortedCallbackSymbolNames.Add(String.Format("GeneratedCode{0}", i))
sortedCallbackSymbolNames.Add(String.Format("Nested{0}", i))
sortedCallbackSymbolNames.Add(String.Format("NonGeneratedCode{0}", i))
sortedCallbackSymbolNames.Add(String.Format("NestedGeneratedCode{0}", i))
sortedCallbackTreePaths.Add(file)
ElseIf Not isGeneratedFile Then
' Analyzer always called for non-generated code.
sortedCallbackSymbolNames.Add(String.Format("NonGeneratedCode{0}", i))
sortedCallbackTreePaths.Add(file)
End If
Next
' Compilation end summary diagnostic (verify callbacks into analyzer)
Dim arg1 = sortedCallbackSymbolNames.Join(",")
Dim arg2 = sortedCallbackTreePaths.Join(",")
AddExpectedNonLocalDiagnostic(builder, {arg1, arg2})
If compilation.Options.GeneralDiagnosticOption = ReportDiagnostic.Error Then
For i As Integer = 0 To builder.Count - 1
If DirectCast(builder(i).Code, String) <> GeneratedCodeAnalyzer.Error.Id Then
builder(i) = builder(i).WithWarningAsError(True)
End If
Next
End If
Return builder.ToArrayAndFree()
End Function
Private Shared Sub AddExpectedLocalDiagnostics(
builder As ArrayBuilder(Of DiagnosticDescription),
isGeneratedCode As Boolean,
squiggledText As String,
line As Integer,
column As Integer,
generatedCodeAnalysisFlagsOpt As GeneratedCodeAnalysisFlags?,
ParamArray arguments As String())
' Always report diagnostics in generated code, unless explicitly suppressed or we are not even analyzing generated code.
Dim reportInGeneratedCode = generatedCodeAnalysisFlagsOpt Is Nothing OrElse
((generatedCodeAnalysisFlagsOpt And GeneratedCodeAnalysisFlags.ReportDiagnostics) <> 0 AndAlso
(generatedCodeAnalysisFlagsOpt And GeneratedCodeAnalysisFlags.Analyze) <> 0)
If Not isGeneratedCode OrElse reportInGeneratedCode Then
Dim diag = Diagnostic(GeneratedCodeAnalyzer.Warning.Id, squiggledText).WithArguments(arguments).WithLocation(line, column)
builder.Add(diag)
diag = Diagnostic(GeneratedCodeAnalyzer.Error.Id, squiggledText).WithArguments(arguments).WithLocation(line, column)
builder.Add(diag)
End If
End Sub
Private Shared Sub AddExpectedNonLocalDiagnostic(builder As ArrayBuilder(Of DiagnosticDescription), ParamArray arguments As String())
AddExpectedDiagnostic(builder, GeneratedCodeAnalyzer.Summary.Id, Nothing, 1, 1, arguments)
End Sub
Private Shared Sub AddExpectedDiagnostic(builder As ArrayBuilder(Of DiagnosticDescription), diagnosticId As String, squiggledText As String, line As Integer, column As Integer, ParamArray arguments As String())
Dim diag = Diagnostic(diagnosticId, squiggledText).WithArguments(arguments).WithLocation(line, column)
builder.Add(diag)
End Sub
End Class
End Namespace
\ No newline at end of file
......@@ -708,5 +708,100 @@ public override void Initialize(AnalysisContext context)
}
}
}
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public class GeneratedCodeAnalyzer : DiagnosticAnalyzer
{
private readonly GeneratedCodeAnalysisFlags? _generatedCodeAnalysisFlagsOpt;
public static readonly DiagnosticDescriptor Warning = new DiagnosticDescriptor(
"GeneratedCodeAnalyzerWarning",
"Title",
"GeneratedCodeAnalyzerMessage for '{0}'",
"Category",
DiagnosticSeverity.Warning,
true);
public static readonly DiagnosticDescriptor Error = new DiagnosticDescriptor(
"GeneratedCodeAnalyzerError",
"Title",
"GeneratedCodeAnalyzerMessage for '{0}'",
"Category",
DiagnosticSeverity.Error,
true);
public static readonly DiagnosticDescriptor Summary = new DiagnosticDescriptor(
"GeneratedCodeAnalyzerSummary",
"Title2",
"GeneratedCodeAnalyzer received callbacks for: '{0}' types and '{1}' files",
"Category",
DiagnosticSeverity.Warning,
true);
public GeneratedCodeAnalyzer(GeneratedCodeAnalysisFlags? generatedCodeAnalysisFlagsOpt)
{
_generatedCodeAnalysisFlagsOpt = generatedCodeAnalysisFlagsOpt;
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Warning, Error, Summary);
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(this.OnCompilationStart);
if (_generatedCodeAnalysisFlagsOpt.HasValue)
{
// Configure analysis on generated code.
context.ConfigureGeneratedCodeAnalysis(_generatedCodeAnalysisFlagsOpt.Value);
}
}
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
var sortedCallbackSymbolNames = new SortedSet<string>();
var sortedCallbackTreePaths = new SortedSet<string>();
context.RegisterSymbolAction(symbolContext =>
{
sortedCallbackSymbolNames.Add(symbolContext.Symbol.Name);
ReportSymbolDiagnostics(symbolContext.Symbol, symbolContext.ReportDiagnostic);
}, SymbolKind.NamedType);
context.RegisterSyntaxTreeAction(treeContext =>
{
sortedCallbackTreePaths.Add(treeContext.Tree.FilePath);
ReportTreeDiagnostics(treeContext.Tree, treeContext.ReportDiagnostic);
});
context.RegisterCompilationEndAction(endContext =>
{
var arg1 = sortedCallbackSymbolNames.Join(",");
var arg2 = sortedCallbackTreePaths.Join(",");
// Summary diagnostics about received callbacks.
var diagnostic = Diagnostic.Create(Summary, Location.None, arg1, arg2);
endContext.ReportDiagnostic(diagnostic);
});
}
private void ReportSymbolDiagnostics(ISymbol symbol, Action<Diagnostic> addDiagnostic)
{
ReportDiagnosticsCore(addDiagnostic, symbol.Locations[0], symbol.Name);
}
private void ReportTreeDiagnostics(SyntaxTree tree, Action<Diagnostic> addDiagnostic)
{
ReportDiagnosticsCore(addDiagnostic, tree.GetRoot().GetLastToken().GetLocation(), tree.FilePath);
}
private void ReportDiagnosticsCore(Action<Diagnostic> addDiagnostic, Location location, params object[] messageArguments)
{
// warning diagnostic
var diagnostic = Diagnostic.Create(Warning, location, messageArguments);
addDiagnostic(diagnostic);
// error diagnostic
diagnostic = Diagnostic.Create(Error, location, messageArguments);
addDiagnostic(diagnostic);
}
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册