提交 20070331 编写于 作者: M manishv

Add a CodeAnalysis diagnostic for detecting memory leaks in implementation of...

Add a CodeAnalysis diagnostic for detecting memory leaks in implementation of analyzers (DoNotStorePerCompilationDataOntoFields): Instance of a diagnostic analyzer might outlive the lifetime of compilation (when executed in the IDE). Hence, storing per-compilation data, such as symbols, into the fields of a diagnostic analyzer might cause stale compilations to stay alive and cause memory leaks. Instead, one should store this data on a separate type instantiatied in a compilation start action, registered using AnaylsisContext.RegisterCompilationStartActionName API. An instance of this type will be created per-compilation and it won't outlive compilation's lifetime, avoiding memory leaks.

Analyzer implementation: Analyzer tracks all field declarations within analyzer types (marked with DiagnosticAnalyzerAttribute) and flags if it's variable type declaration has any type syntax descendant that binds to a type implementing ISymbol or has Compilation in its type chain.

I have also added a few positive and negative tests for the diagnostic. (changeset 1394660)
上级 244160b0
......@@ -73,6 +73,7 @@
<ItemGroup>
<Compile Include="MetaAnalyzers\CSharpDiagnosticDescriptorCreationAnalyzer.cs" />
<Compile Include="MetaAnalyzers\CSharpRegisterActionAnalyzer.cs" />
<Compile Include="MetaAnalyzers\CSharpDiagnosticAnalyzerFieldsAnalyzer.cs" />
<Compile Include="MetaAnalyzers\CSharpReportDiagnosticAnalyzer.cs" />
<Compile Include="MetaAnalyzers\Fixers\CSharpApplyDiagnosticAnalyzerAttributeFix.cs" />
</ItemGroup>
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp.Analyzers.MetaAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CSharpDiagnosticAnalyzerFieldsAnalyzer : DiagnosticAnalyzerFieldsAnalyzer<ClassDeclarationSyntax, FieldDeclarationSyntax, TypeSyntax, VariableDeclarationSyntax>
{
}
}
......@@ -64,10 +64,11 @@
<Compile Include="Helpers\AttributeHelpers.cs" />
<Compile Include="Helpers\DocumentChangedAction.cs" />
<Compile Include="Helpers\ITypeSymbolExtensions.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerFieldsAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerAttributeAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerCorrectnessAnalyzer.CompilationAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerCorrectnessAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerCorrectnessAnalyzer.InvocationCompilationAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticAnalyzerCorrectnessAnalyzer.SyntaxNodeWithinAnalyzerTypeCompilationAnalyzer.cs" />
<Compile Include="MetaAnalyzers\DiagnosticDescriptorCreationAnalyzer.cs" />
<Compile Include="MetaAnalyzers\Fixers\ApplyDiagnosticAnalyzerAttributeFix.cs" />
<Compile Include="MetaAnalyzers\RegisterActionAnalyzer.cs" />
......
......@@ -115,6 +115,33 @@ internal class CodeAnalysisDiagnosticsResources {
}
}
/// <summary>
/// Looks up a localized string similar to Instance of a diagnostic analyzer might outlive the lifetime of compilation. Hence, storing per-compilation data, such as symbols, into the fields of a diagnostic analyzer might cause stale compilations to stay alive and cause memory leaks. Instead, you should store this data on a separate type instantiatied in a compilation start action, registered using &apos;{0}.{1}&apos; API. An instance of this type will be created per-compilation and it won&apos;t outlive compilation&apos;s lifetime, hence avoiding memory leaks..
/// </summary>
internal static string DoNotStorePerCompilationDataOntoFieldsDescription {
get {
return ResourceManager.GetString("DoNotStorePerCompilationDataOntoFieldsDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Avoid storing per-compilation data of type &apos;{0}&apos; into the fields of a diagnostic analyzer..
/// </summary>
internal static string DoNotStorePerCompilationDataOntoFieldsMessage {
get {
return ResourceManager.GetString("DoNotStorePerCompilationDataOntoFieldsMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Avoid storing per-compilation data into the fields of a diagnostic analyzer..
/// </summary>
internal static string DoNotStorePerCompilationDataOntoFieldsTitle {
get {
return ResourceManager.GetString("DoNotStorePerCompilationDataOntoFieldsTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ReportDiagnostic should only be invoked with supported DiagnosticDescriptors that are returned from DiagnosticAnalyzer.SupportedDiagnostics property. Otherwise, the reported diagnostic will be filtered out by the analysis engine..
/// </summary>
......
......@@ -186,4 +186,13 @@
<data name="UseLocalizableStringsInDescriptorDescription" xml:space="preserve">
<value>If your diagnostic analyzer and it's reported diagnostics need to be localizable, then the supported DiagnosticDescriptors used for constructing the diagnostics must also be localizable. If so, then localizable argument(s) must be provided for parameter 'title' (and optionally 'description') to the diagnostic descriptor constructor to ensure that the descriptor is localizable.</value>
</data>
<data name="DoNotStorePerCompilationDataOntoFieldsMessage" xml:space="preserve">
<value>Avoid storing per-compilation data of type '{0}' into the fields of a diagnostic analyzer.</value>
</data>
<data name="DoNotStorePerCompilationDataOntoFieldsTitle" xml:space="preserve">
<value>Avoid storing per-compilation data into the fields of a diagnostic analyzer.</value>
</data>
<data name="DoNotStorePerCompilationDataOntoFieldsDescription" xml:space="preserve">
<value>Instance of a diagnostic analyzer might outlive the lifetime of compilation. Hence, storing per-compilation data, such as symbols, into the fields of a diagnostic analyzer might cause stale compilations to stay alive and cause memory leaks. Instead, you should store this data on a separate type instantiatied in a compilation start action, registered using '{0}.{1}' API. An instance of this type will be created per-compilation and it won't outlive compilation's lifetime, hence avoiding memory leaks.</value>
</data>
</root>
\ No newline at end of file
......@@ -4,5 +4,6 @@ internal static class DiagnosticCategory
{
public const string AnalyzerCorrectness = "AnalyzerCorrectness";
public const string AnalyzerLocalization = "AnalyzerLocalization";
public const string AnalyzerPerformance = "AnalyzerPerformance";
}
}
......@@ -9,5 +9,6 @@ internal static class DiagnosticIds
public const string InvalidReportDiagnosticRuleId = "RS1005";
public const string InvalidSyntaxKindTypeArgumentRuleId = "RS1006";
public const string UseLocalizableStringsInDescriptorRuleId = "RS1007";
public const string DoNotStorePerCompilationDataOntoFieldsRuleId = "RS1008";
}
}
......@@ -37,6 +37,19 @@ internal void AnalyzeSymbol(SymbolAnalysisContext symbolContext)
}
protected abstract void AnalyzeDiagnosticAnalyzer(SymbolAnalysisContext symbolContext);
protected bool HasDiagnosticAnalyzerAttribute(INamedTypeSymbol namedType)
{
foreach (var attribute in AttributeHelpers.GetApplicableAttributes(namedType))
{
if (AttributeHelpers.DerivesFrom(attribute.AttributeClass, DiagnosticAnalyzerAttribute))
{
return true;
}
}
return false;
}
}
}
}
......@@ -10,11 +10,11 @@ namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers
{
public abstract partial class DiagnosticAnalyzerCorrectnessAnalyzer : DiagnosticAnalyzer
{
protected abstract class InvocationCompilationAnalyzer<TClassDeclarationSyntax, TInvocationExpressionSyntax> : CompilationAnalyzer
protected abstract class SyntaxNodeWithinAnalyzerTypeCompilationAnalyzer<TClassDeclarationSyntax, TSyntaxNodeOfInterest> : CompilationAnalyzer
where TClassDeclarationSyntax : SyntaxNode
where TInvocationExpressionSyntax : SyntaxNode
where TSyntaxNodeOfInterest : SyntaxNode
{
protected InvocationCompilationAnalyzer(INamedTypeSymbol diagnosticAnalyzer, INamedTypeSymbol diagnosticAnalyzerAttribute)
protected SyntaxNodeWithinAnalyzerTypeCompilationAnalyzer(INamedTypeSymbol diagnosticAnalyzer, INamedTypeSymbol diagnosticAnalyzerAttribute)
: base(diagnosticAnalyzer, diagnosticAnalyzerAttribute)
{
}
......@@ -40,23 +40,19 @@ protected override void AnalyzeDiagnosticAnalyzer(SymbolAnalysisContext symbolCo
var classDecls = GetClassDeclarationNodes(namedType, symbolContext.CancellationToken);
foreach (var classDecl in classDecls)
{
var invocations = classDecl.DescendantNodes().OfType<TInvocationExpressionSyntax>();
if (invocations.Any())
var syntaxNodes = classDecl.DescendantNodes(n => !(n is TClassDeclarationSyntax) || ReferenceEquals(n, classDecl)).OfType<TSyntaxNodeOfInterest>();
if (syntaxNodes.Any())
{
var semanticModel = symbolContext.Compilation.GetSemanticModel(classDecl.SyntaxTree);
foreach (var invocation in invocations)
foreach (var syntaxNode in syntaxNodes)
{
var symbol = semanticModel.GetSymbolInfo(invocation, symbolContext.CancellationToken).Symbol;
if (symbol != null)
{
AnalyzeInvocation(symbolContext, invocation, symbol, semanticModel);
}
AnalyzeNode(symbolContext, syntaxNode, semanticModel);
}
}
}
}
protected abstract void AnalyzeInvocation(SymbolAnalysisContext symbolContext, TInvocationExpressionSyntax invocation, ISymbol invocationSymbol, SemanticModel semanticModel);
protected abstract void AnalyzeNode(SymbolAnalysisContext symbolContext, TSyntaxNodeOfInterest syntaxNode, SemanticModel semanticModel);
}
}
}
......@@ -27,6 +27,7 @@ public abstract partial class DiagnosticAnalyzerCorrectnessAnalyzer : Diagnostic
internal static readonly string RegisterSymbolActionName = nameof(AnalysisContext.RegisterSymbolAction);
internal static readonly string RegisterCodeBlockStartActionName = nameof(AnalysisContext.RegisterCodeBlockStartAction);
internal static readonly string RegisterCodeBlockEndActionName = nameof(AnalysisContext.RegisterCodeBlockEndAction);
internal static readonly string RegisterCompilationStartActionName = nameof(AnalysisContext.RegisterCompilationStartAction);
internal static readonly string RegisterCompilationEndActionName = nameof(CompilationStartAnalysisContext.RegisterCompilationEndAction);
internal static readonly string ReportDiagnosticName = nameof(CompilationEndAnalysisContext.ReportDiagnostic);
internal static readonly string SupportedDiagnosticsName = nameof(DiagnosticAnalyzer.SupportedDiagnostics);
......
// Copyright (c) Microsoft Open Technologies, Inc. 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.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers
{
public abstract class DiagnosticAnalyzerFieldsAnalyzer<TClassDeclarationSyntax, TFieldDeclarationSyntax, TTypeSyntax, TVariableTypeDeclarationSyntax> : DiagnosticAnalyzerCorrectnessAnalyzer
where TClassDeclarationSyntax : SyntaxNode
where TFieldDeclarationSyntax : SyntaxNode
where TTypeSyntax : SyntaxNode
where TVariableTypeDeclarationSyntax : SyntaxNode
{
private static readonly LocalizableString localizableTitle = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.DoNotStorePerCompilationDataOntoFieldsTitle), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private static readonly LocalizableString localizableMessage = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.DoNotStorePerCompilationDataOntoFieldsMessage), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources));
private static readonly LocalizableString localizableDescription = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.DoNotStorePerCompilationDataOntoFieldsDescription), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources), nameof(AnalysisContext), DiagnosticAnalyzerCorrectnessAnalyzer.RegisterCompilationStartActionName);
private static readonly string compilationTypeFullName = typeof(Compilation).FullName;
private static readonly string symbolTypeFullName = typeof(ISymbol).FullName;
public static DiagnosticDescriptor DoNotStorePerCompilationDataOntoFieldsRule = new DiagnosticDescriptor(
DiagnosticIds.DoNotStorePerCompilationDataOntoFieldsRuleId,
localizableTitle,
localizableMessage,
DiagnosticCategory.AnalyzerPerformance,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: localizableDescription,
customTags: WellKnownDiagnosticTags.Telemetry);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
return ImmutableArray.Create(DoNotStorePerCompilationDataOntoFieldsRule);
}
}
protected override CompilationAnalyzer GetCompilationAnalyzer(Compilation compilation, INamedTypeSymbol diagnosticAnalyzer, INamedTypeSymbol diagnosticAnalyzerAttribute)
{
var compilationType = compilation.GetTypeByMetadataName(compilationTypeFullName);
if (compilationType == null)
{
return null;
}
var symbolType = compilation.GetTypeByMetadataName(symbolTypeFullName);
if (symbolType == null)
{
return null;
}
return new FieldsAnalyzer(compilationType, symbolType, diagnosticAnalyzer, diagnosticAnalyzerAttribute);
}
private sealed class FieldsAnalyzer : SyntaxNodeWithinAnalyzerTypeCompilationAnalyzer<TClassDeclarationSyntax, TFieldDeclarationSyntax>
{
private readonly INamedTypeSymbol compilationType;
private readonly INamedTypeSymbol symbolType;
public FieldsAnalyzer(INamedTypeSymbol compilationType, INamedTypeSymbol symbolType, INamedTypeSymbol diagnosticAnalyzer, INamedTypeSymbol diagnosticAnalyzerAttribute)
: base(diagnosticAnalyzer, diagnosticAnalyzerAttribute)
{
this.compilationType = compilationType;
this.symbolType = symbolType;
}
protected override void AnalyzeDiagnosticAnalyzer(SymbolAnalysisContext symbolContext)
{
var namedType = (INamedTypeSymbol)symbolContext.Symbol;
if (!HasDiagnosticAnalyzerAttribute(namedType))
{
// We are interested only in DiagnosticAnalyzer types with DiagnosticAnalyzerAttribute.
return;
}
base.AnalyzeDiagnosticAnalyzer(symbolContext);
}
protected override void AnalyzeNode(SymbolAnalysisContext symbolContext, TFieldDeclarationSyntax syntaxNode, SemanticModel semanticModel)
{
// Get all the type syntax nodes within the topmost type declaration nodes for field declarations.
var variableTypeDeclarations = syntaxNode.DescendantNodesAndSelf().OfType<TVariableTypeDeclarationSyntax>();
var topMostTypeNodes = variableTypeDeclarations.SelectMany(typeDecl => typeDecl.ChildNodes().OfType<TTypeSyntax>());
var typeNodes = topMostTypeNodes.SelectMany(t => t.DescendantNodesAndSelf().OfType<TTypeSyntax>());
foreach (var typeNode in typeNodes)
{
var type = semanticModel.GetTypeInfo(typeNode, symbolContext.CancellationToken).Type;
if (type != null)
{
foreach (var innerType in type.GetBaseTypesAndThis())
{
if (innerType.Equals(compilationType))
{
ReportDiagnostic(type, typeNode, symbolContext);
return;
}
}
foreach (var iface in type.AllInterfaces)
{
if (iface.Equals(symbolType))
{
ReportDiagnostic(type, typeNode, symbolContext);
return;
}
}
}
}
}
private static void ReportDiagnostic(ITypeSymbol type, TTypeSyntax typeSyntax, SymbolAnalysisContext context)
{
var diagnostic = Diagnostic.Create(DoNotStorePerCompilationDataOntoFieldsRule, typeSyntax.GetLocation(), type.ToDisplayString());
context.ReportDiagnostic(diagnostic);
}
}
}
}
......@@ -94,7 +94,7 @@ protected override CompilationAnalyzer GetCompilationAnalyzer(Compilation compil
protected abstract RegisterActionCompilationAnalyzer GetAnalyzer(Compilation compilation, INamedTypeSymbol analysisContext, INamedTypeSymbol compilationStartAnalysisContext, INamedTypeSymbol codeBlockStartAnalysisContext, INamedTypeSymbol symbolKind, INamedTypeSymbol diagnosticAnalyzer, INamedTypeSymbol diagnosticAnalyzerAttribute);
protected abstract class RegisterActionCompilationAnalyzer : InvocationCompilationAnalyzer<TClassDeclarationSyntax, TInvocationExpressionSyntax>
protected abstract class RegisterActionCompilationAnalyzer : SyntaxNodeWithinAnalyzerTypeCompilationAnalyzer<TClassDeclarationSyntax, TInvocationExpressionSyntax>
{
private readonly INamedTypeSymbol analysisContext;
private readonly INamedTypeSymbol compilationStartAnalysisContext;
......@@ -145,9 +145,10 @@ private static bool IsRegisterAction(string expectedName, IMethodSymbol method,
return false;
}
protected override void AnalyzeInvocation(SymbolAnalysisContext symbolContext, TInvocationExpressionSyntax invocation, ISymbol symbol, SemanticModel semanticModel)
protected override void AnalyzeNode(SymbolAnalysisContext symbolContext, TInvocationExpressionSyntax invocation, SemanticModel semanticModel)
{
if (symbol.Kind != SymbolKind.Method || !symbol.Name.StartsWith("Register"))
var symbol = semanticModel.GetSymbolInfo(invocation, symbolContext.CancellationToken).Symbol;
if (symbol == null || symbol.Kind != SymbolKind.Method || !symbol.Name.StartsWith("Register"))
{
return;
}
......
......@@ -97,7 +97,7 @@ protected override CompilationAnalyzer GetCompilationAnalyzer(Compilation compil
protected abstract ReportDiagnosticCompilationAnalyzer GetAnalyzer(ImmutableHashSet<INamedTypeSymbol> contextTypes, INamedTypeSymbol diagnosticType, INamedTypeSymbol diagnosticDescriptorType, INamedTypeSymbol diagnosticAnalyzer, INamedTypeSymbol diagnosticAnalyzerAttribute);
protected abstract class ReportDiagnosticCompilationAnalyzer : InvocationCompilationAnalyzer<TClassDeclarationSyntax, TInvocationExpressionSyntax>
protected abstract class ReportDiagnosticCompilationAnalyzer : SyntaxNodeWithinAnalyzerTypeCompilationAnalyzer<TClassDeclarationSyntax, TInvocationExpressionSyntax>
{
private readonly ImmutableHashSet<INamedTypeSymbol> contextTypes;
private readonly INamedTypeSymbol diagnosticType;
......@@ -181,9 +181,11 @@ private ImmutableArray<IFieldSymbol> GetReferencedDescriptorFields(SyntaxNode sy
return builder.ToImmutable();
}
protected override void AnalyzeInvocation(SymbolAnalysisContext symbolContext, TInvocationExpressionSyntax invocation, ISymbol symbol, SemanticModel semanticModel)
protected override void AnalyzeNode(SymbolAnalysisContext symbolContext, TInvocationExpressionSyntax invocation, SemanticModel semanticModel)
{
if (symbol.Kind != SymbolKind.Method ||
var symbol = semanticModel.GetSymbolInfo(invocation, symbolContext.CancellationToken).Symbol;
if (symbol == null ||
symbol.Kind != SymbolKind.Method ||
!symbol.Name.Equals(ReportDiagnosticName, StringComparison.OrdinalIgnoreCase) ||
!contextTypes.Contains(symbol.ContainingType))
{
......
......@@ -102,6 +102,7 @@
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Compile Include="MetaAnalyzers\DoNotStorePerCompilationDataOntoFieldsRuleTests.cs" />
<Compile Include="MetaAnalyzers\InvalidReportDiagnosticRuleTests.cs" />
<Compile Include="MetaAnalyzers\InvalidSyntaxKindTypeArgumentRuleTests.cs" />
<Compile Include="MetaAnalyzers\UnsupportedSymbolKindArgumentRuleTests.cs" />
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.Analyzers;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Analyzers.MetaAnalyzers;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Analyzers.MetaAnalyzers;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests.Analyzers.MetaAnalyzers
{
public class DoNotStorePerCompilationDataOntoFieldsRuleTests : CodeFixTestBase
{
[Fact]
public void CSharp_VerifyDiagnostic()
{
var source = @"
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using MyNamedType = Microsoft.CodeAnalysis.INamedTypeSymbol;
class MyCompilation : Compilation
{
}
[DiagnosticAnalyzer(LanguageNames.CSharp)]
class MyAnalyzer : DiagnosticAnalyzer
{
private static readonly ITypeSymbol x1;
public static readonly CSharpCompilation x2;
private readonly List<MyNamedType> x3;
private static Dictionary<MyCompilation, MyNamedType> x4;
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
throw new NotImplementedException();
}
}
public override void Initialize(AnalysisContext context)
{
}
}";
var expected = new[]
{
GetCSharpExpectedDiagnostic(17, 29, violatingTypeName: typeof(ITypeSymbol).FullName),
GetCSharpExpectedDiagnostic(18, 28, violatingTypeName: typeof(CSharpCompilation).FullName),
GetCSharpExpectedDiagnostic(19, 27, violatingTypeName: typeof(INamedTypeSymbol).FullName),
GetCSharpExpectedDiagnostic(20, 31, violatingTypeName: "MyCompilation")
};
VerifyCSharp(source, expected);
}
[Fact]
public void VisualBasic_VerifyDiagnostic()
{
var source = @"
Imports System
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic
Imports MyNamedType = Microsoft.CodeAnalysis.INamedTypeSymbol
Class MyCompilation
Inherits Compilation
End Class
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Class MyAnalyzer
Inherits DiagnosticAnalyzer
Private Shared ReadOnly x1 As ITypeSymbol
Public Shared ReadOnly x2 As VisualBasicCompilation
Private ReadOnly x3 As List(Of MyNamedType)
Private Shared x4 As Dictionary(Of MyCompilation, MyNamedType)
Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor)
Get
Throw New NotImplementedException
End Get
End Property
Public Overrides Sub Initialize(context As AnalysisContext)
End Sub
End Class
";
var expected = new[]
{
GetBasicExpectedDiagnostic(17, 35, violatingTypeName: typeof(ITypeSymbol).FullName),
GetBasicExpectedDiagnostic(18, 34, violatingTypeName: typeof(VisualBasicCompilation).FullName),
GetBasicExpectedDiagnostic(19, 36, violatingTypeName: typeof(INamedTypeSymbol).FullName),
GetBasicExpectedDiagnostic(20, 40, violatingTypeName: "MyCompilation")
};
VerifyBasic(source, expected);
}
[Fact]
public void CSharp_NoDiagnosticCases()
{
var source = @"
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using MyNamedType = Microsoft.CodeAnalysis.INamedTypeSymbol;
class MyCompilation : Compilation
{
}
[DiagnosticAnalyzer(LanguageNames.CSharp)]
class MyAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor x1;
private readonly List<LocalizableResourceString> x2;
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
throw new NotImplementedException();
}
}
public override void Initialize(AnalysisContext context)
{
var analyzer = new NestedCompilationAnalyzer();
context.RegisterCompilationStartAction(analyzer.StartCompilation);
}
private class NestedCompilationAnalyzer
{
// Ok to store per-compilation data here.
private readonly Dictionary<MyCompilation, MyNamedType> x;
internal void StartCompilation(CompilationStartAnalysisContext context)
{
}
}
}
class MyAnalyzerWithoutAttribute : DiagnosticAnalyzer
{
// Ok to store per-compilation data here.
private static ITypeSymbol x;
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
throw new NotImplementedException();
}
}
public override void Initialize(AnalysisContext context)
{
throw new NotImplementedException();
}
}";
VerifyCSharp(source);
}
[Fact]
public void VisualBasic_NoDiagnosticCases()
{
var source = @"
Imports System
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic
Imports MyNamedType = Microsoft.CodeAnalysis.INamedTypeSymbol
Class MyCompilation
Inherits Compilation
End Class
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Class MyAnalyzer
Inherits DiagnosticAnalyzer
Private Shared ReadOnly x1 As DiagnosticDescriptor
Private ReadOnly x2 As List(Of LocalizableResourceString)
Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor)
Get
Throw New NotImplementedException
End Get
End Property
Public Overrides Sub Initialize(context As AnalysisContext)
Dim compilationAnalyzer = New NestedCompilationAnalyzer
context.RegisterCompilationStartAction(AddressOf compilationAnalyzer.StartCompilation)
End Sub
Class NestedCompilationAnalyzer
' Ok to store per-compilation data here.
Private ReadOnly x As Dictionary(Of MyCompilation, MyNamedType)
Friend Sub StartCompilation(context As CompilationStartAnalysisContext)
End Sub
End Class
End Class
Class MyAnalyzerWithoutAttribute
Inherits DiagnosticAnalyzer
' Ok to store per-compilation data here.
Private Shared x As ITypeSymbol
Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor)
Get
Throw New NotImplementedException()
End Get
End Property
Public Overrides Sub Initialize(context As AnalysisContext)
Throw New NotImplementedException()
End Sub
End Class
";
VerifyBasic(source);
}
protected override CodeFixProvider GetCSharpCodeFixProvider()
{
return null;
}
protected override CodeFixProvider GetBasicCodeFixProvider()
{
return null;
}
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
{
return new CSharpDiagnosticAnalyzerFieldsAnalyzer();
}
protected override DiagnosticAnalyzer GetBasicDiagnosticAnalyzer()
{
return new BasicDiagnosticAnalyzerFieldsAnalyzer();
}
private static DiagnosticResult GetCSharpExpectedDiagnostic(int line, int column, string violatingTypeName)
{
return GetExpectedDiagnostic(LanguageNames.CSharp, line, column, violatingTypeName);
}
private static DiagnosticResult GetBasicExpectedDiagnostic(int line, int column, string violatingTypeName)
{
return GetExpectedDiagnostic(LanguageNames.VisualBasic, line, column, violatingTypeName);
}
private static DiagnosticResult GetExpectedDiagnostic(string language, int line, int column, string violatingTypeName)
{
var fileName = language == LanguageNames.CSharp ? "Test0.cs" : "Test0.vb";
return new DiagnosticResult
{
Id = DiagnosticIds.DoNotStorePerCompilationDataOntoFieldsRuleId,
Message = string.Format(CodeAnalysisDiagnosticsResources.DoNotStorePerCompilationDataOntoFieldsMessage, violatingTypeName),
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation(fileName, line, column)
}
};
}
}
}
......@@ -72,6 +72,7 @@
<ItemGroup>
<Compile Include="MetaAnalyzers\BasicDiagnosticDescriptorCreationAnalyzer.vb" />
<Compile Include="MetaAnalyzers\BasicRegisterActionAnalyzer.vb" />
<Compile Include="MetaAnalyzers\BasicDiagnosticAnalyzerFieldsAnalyzer.vb" />
<Compile Include="MetaAnalyzers\BasicReportDiagnosticAnalyzer.vb" />
<Compile Include="MetaAnalyzers\Fixers\BasicApplyDiagnosticAnalyzerAttributeFix.vb" />
</ItemGroup>
......
' Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Analyzers.MetaAnalyzers
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Public Class BasicDiagnosticAnalyzerFieldsAnalyzer
Inherits DiagnosticAnalyzerFieldsAnalyzer(Of ClassBlockSyntax, FieldDeclarationSyntax, TypeSyntax, SimpleAsClauseSyntax)
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册