Skip to content

Commit

Permalink
Ensure that we suppress compilation diagnostics reported in generated… (
Browse files Browse the repository at this point in the history
#11225)

* Ensure that we suppress compilation diagnostics reported in generated code when the analyzer is configured to analyze generated code, but not report diagnostics on generated code.

Fixes #11217

* Address PR feedback to improve performance of AnalyzerDriver.IsInGeneratedCode

* Address PR feedback and modify test
  • Loading branch information
mavasani committed May 11, 2016
1 parent 555f4fa commit 410c697
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,25 @@ partial class PartialType
VerifyGeneratedCodeAnalyzerDiagnostics(compilation, expected, GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
}

[Fact, WorkItem(11217, "https://github.com/dotnet/roslyn/issues/11217")]
public void TestGeneratedCodeAnalyzerNoReportDiagnostics()
{
string source1 = @"
class TypeInUserFile { }
";
string source2 = @"
class TypeInGeneratedFile { }
";
var tree1 = CSharpSyntaxTree.ParseText(source1, path: "SourceFileRegular.cs");
var tree2 = CSharpSyntaxTree.ParseText(source2, path: "SourceFileRegular.Designer.cs");
var compilation = CreateCompilationWithMscorlib45(new[] { tree1, tree2 }, new MetadataReference[] { SystemRef });
compilation.VerifyDiagnostics();

var analyzers = new DiagnosticAnalyzer[] { new GeneratedCodeAnalyzer2() };
compilation.VerifyAnalyzerDiagnostics(analyzers,
expected: Diagnostic("GeneratedCodeAnalyzer2Warning", "TypeInUserFile").WithArguments("TypeInUserFile", "2").WithLocation(2, 7));
}

internal class OwningSymbolTestAnalyzer : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor ExpressionDescriptor = new DiagnosticDescriptor(
Expand Down
95 changes: 84 additions & 11 deletions src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ internal abstract partial class AnalyzerDriver : IDisposable
/// </summary>
private Dictionary<SyntaxTree, bool> _lazyGeneratedCodeFilesMap;

/// <summary>
/// Lazily populated dictionary from tree to declared symbols with GeneratedCodeAttribute.
/// </summary>
private Dictionary<SyntaxTree, ImmutableHashSet<ISymbol>> _lazyGeneratedCodeSymbolsMap;

/// <summary>
/// Symbol for <see cref="System.CodeDom.Compiler.GeneratedCodeAttribute"/>.
/// </summary>
Expand Down Expand Up @@ -149,6 +154,7 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn
_doNotAnalyzeGeneratedCode = ShouldSkipAnalysisOnGeneratedCode(unsuppressedAnalyzers);
_treatAllCodeAsNonGeneratedCode = ShouldTreatAllCodeAsNonGeneratedCode(unsuppressedAnalyzers, _generatedCodeAnalysisFlagsMap);
_lazyGeneratedCodeFilesMap = _treatAllCodeAsNonGeneratedCode ? null : new Dictionary<SyntaxTree, bool>();
_lazyGeneratedCodeSymbolsMap = _treatAllCodeAsNonGeneratedCode ? null : new Dictionary<SyntaxTree, ImmutableHashSet<ISymbol>>();
_generatedCodeAttribute = analyzerExecutor.Compilation?.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute");
_symbolActionsByKind = MakeSymbolActionsByKind();
Expand Down Expand Up @@ -596,26 +602,32 @@ private bool IsInGeneratedCode(Location location, Compilation compilation, Cance
return false;
}

// Check if this is a generated code file by its extension.
if (IsGeneratedCode(location.SourceTree))
{
return true;
}

if (_generatedCodeAttribute != null)
// Check if the file has generated code definitions (i.e. symbols with GeneratedCodeAttribute).
if (_generatedCodeAttribute != null && _lazyGeneratedCodeSymbolsMap != null)
{
var model = compilation.GetSemanticModel(location.SourceTree);
for (var node = location.SourceTree.GetRoot(cancellationToken).FindNode(location.SourceSpan, getInnermostNodeForTie: true);
node != null;
node = node.Parent)
var generatedCodeSymbolsInTree = GetOrComputeGeneratedCodeSymbolsInTree(location.SourceTree, compilation, cancellationToken);
if (generatedCodeSymbolsInTree.Count > 0)
{
var declaredSymbols = model.GetDeclaredSymbolsForNode(node, cancellationToken);
Debug.Assert(declaredSymbols != null);

foreach (var symbol in declaredSymbols)
var model = compilation.GetSemanticModel(location.SourceTree);
for (var node = location.SourceTree.GetRoot(cancellationToken).FindNode(location.SourceSpan, getInnermostNodeForTie: true);
node != null;
node = node.Parent)
{
if (GeneratedCodeUtilities.IsGeneratedSymbolWithGeneratedCodeAttribute(symbol, _generatedCodeAttribute))
var declaredSymbols = model.GetDeclaredSymbolsForNode(node, cancellationToken);
Debug.Assert(declaredSymbols != null);

foreach (var symbol in declaredSymbols)
{
return true;
if (generatedCodeSymbolsInTree.Contains(symbol))
{
return true;
}
}
}
}
Expand All @@ -624,6 +636,67 @@ private bool IsInGeneratedCode(Location location, Compilation compilation, Cance
return false;
}

private ImmutableHashSet<ISymbol> GetOrComputeGeneratedCodeSymbolsInTree(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken)
{
Debug.Assert(_lazyGeneratedCodeSymbolsMap != null);

ImmutableHashSet<ISymbol> generatedCodeSymbols;
lock (_lazyGeneratedCodeSymbolsMap)
{
if (_lazyGeneratedCodeSymbolsMap.TryGetValue(tree, out generatedCodeSymbols))
{
return generatedCodeSymbols;
}
}

generatedCodeSymbols = ComputeGeneratedCodeSymbolsInTree(tree, compilation, cancellationToken);

lock (_lazyGeneratedCodeSymbolsMap)
{
ImmutableHashSet<ISymbol> existingGeneratedCodeSymbols;
if (!_lazyGeneratedCodeSymbolsMap.TryGetValue(tree, out existingGeneratedCodeSymbols))
{
_lazyGeneratedCodeSymbolsMap.Add(tree, generatedCodeSymbols);
}
else
{
Debug.Assert(existingGeneratedCodeSymbols.SetEquals(generatedCodeSymbols));
}
}

return generatedCodeSymbols;
}

private ImmutableHashSet<ISymbol> ComputeGeneratedCodeSymbolsInTree(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken)
{
// PERF: Bail out early if file doesn't have "GeneratedCode" text.
var text = tree.GetText(cancellationToken).ToString();
if (!text.Contains("GeneratedCode"))
{
return ImmutableHashSet<ISymbol>.Empty;
}

var model = compilation.GetSemanticModel(tree);
var root = tree.GetRoot(cancellationToken);
var span = root.FullSpan;
var builder = new List<DeclarationInfo>();
model.ComputeDeclarationsInSpan(span, getSymbol: true, builder: builder, cancellationToken: cancellationToken);

ImmutableHashSet<ISymbol>.Builder generatedSymbolsBuilderOpt = null;
foreach (var declarationInfo in builder)
{
var symbol = declarationInfo.DeclaredSymbol;
if (symbol != null &&
GeneratedCodeUtilities.IsGeneratedSymbolWithGeneratedCodeAttribute(symbol, _generatedCodeAttribute))
{
generatedSymbolsBuilderOpt = generatedSymbolsBuilderOpt ?? ImmutableHashSet.CreateBuilder<ISymbol>();
generatedSymbolsBuilderOpt.Add(symbol);
}
}

return generatedSymbolsBuilderOpt != null ? generatedSymbolsBuilderOpt.ToImmutable() : ImmutableHashSet<ISymbol>.Empty;
}

/// <summary>
/// Return a task that completes when the driver is initialized.
/// </summary>
Expand Down
Loading

0 comments on commit 410c697

Please sign in to comment.