提交 d43e0e62 编写于 作者: M manishv

Add a Roslyn diagnostic for C# and VB code analysis projects to verify that...

Add a Roslyn diagnostic for C# and VB code analysis projects to verify that all of the expected source symbol types have some code path that generates a SymbolDeclaredEvent. This will hopefully avoid bugs such as 1111667 (Cannot develop VB analyzer when field does not have an initializer) in future. (changeset 1407683)
上级 4f6e43fa
......@@ -3,4 +3,7 @@
<Rules AnalyzerId="Roslyn.Diagnostics.Analyzers.CSharp" RuleNamespace="Roslyn.Diagnostics.Analyzers.CSharp">
<Rule Id="RS0013" Action="Warning" />
</Rules>
<Rules AnalyzerId="Roslyn.Diagnostics.Analyzers.CSharp" RuleNamespace="Roslyn.Diagnostics.Analyzers.CSharp">
<Rule Id="RS0018" Action="Error" />
</Rules>
</RuleSet>
\ No newline at end of file
......@@ -6,4 +6,7 @@
<Rules AnalyzerId="Roslyn.Diagnostics.Analyzers.VisualBasic" RuleNamespace="Roslyn.Diagnostics.Analyzers.VisualBasic">
<Rule Id="RS0013" Action="Warning" />
</Rules>
<Rules AnalyzerId="Roslyn.Diagnostics.Analyzers.VisualBasic" RuleNamespace="Roslyn.Diagnostics.Analyzers.VisualBasic">
<Rule Id="RS0018" Action="Error" />
</Rules>
</RuleSet>
\ No newline at end of file
......@@ -75,6 +75,7 @@
<Compile Include="ApiDesign\CancellationTokenMustBeLastCodeFixProvider.cs" />
<Compile Include="Documentation\CSharpDoNotUseVerbatimCrefsAnalyzer.cs" />
<Compile Include="Reliability\CSharpDoNotCreateTasksWithoutTaskSchedulerAnalyzer.cs" />
<Compile Include="Reliability\CSharpSymbolDeclaredEventAnalyzer.cs" />
<Compile Include="Reliability\CSharpConsumePreserveSigAnalyzer.cs" />
<Compile Include="Performance\CSharpDiagnosticDescriptorAccessAnalyzer.cs" />
<Compile Include="Performance\CSharpCodeActionCreateAnalyzer.cs" />
......@@ -92,4 +93,4 @@
<Import Project="..\..\..\..\packages\StyleCop.MSBuild.4.7.48.2\build\StyleCop.MSBuild.Targets" Condition="Exists('..\..\..\..\packages\StyleCop.MSBuild.4.7.48.2\build\StyleCop.MSBuild.Targets')" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
\ No newline at end of file
</Project>
// 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.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Roslyn.Diagnostics.Analyzers.CSharp
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CSharpSymbolDeclaredEventAnalyzer : SymbolDeclaredEventAnalyzer<SyntaxKind>
{
private static HashSet<string> symbolTypesWithExpectedSymbolDeclaredEvent = new HashSet<string>(
new[] { "SourceNamespaceSymbol", "SourceNamedTypeSymbol", "SourceEventSymbol", "SourceFieldSymbol", "SourceMethodSymbol", "SourcePropertySymbol" });
protected override CompilationAnalyzer GetCompilationAnalyzer(Compilation compilation, INamedTypeSymbol symbolType)
{
var compilationType = compilation.GetTypeByMetadataName(typeof(CSharpCompilation).FullName);
if (compilationType == null)
{
return null;
}
return new CSharpCompilationAnalyzer(symbolType, compilationType);
}
protected override SyntaxKind InvocationExpressionSyntaxKind
{
get { return SyntaxKind.InvocationExpression; }
}
private sealed class CSharpCompilationAnalyzer : CompilationAnalyzer
{
public CSharpCompilationAnalyzer(INamedTypeSymbol symbolType, INamedTypeSymbol compilationType)
: base(symbolType, compilationType)
{ }
protected override HashSet<string> SymbolTypesWithExpectedSymbolDeclaredEvent
{
get
{
return symbolTypesWithExpectedSymbolDeclaredEvent;
}
}
protected override SyntaxNode GetFirstArgumentOfInvocation(SyntaxNode node)
{
var invocation = (InvocationExpressionSyntax)node;
if (invocation.ArgumentList != null)
{
var argument = invocation.ArgumentList.Arguments.FirstOrDefault();
if (argument != null)
{
return argument.Expression;
}
}
return null;
}
}
}
}
// 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.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Roslyn.Diagnostics.Analyzers
{
public abstract class SymbolDeclaredEventAnalyzer<TSyntaxKind> : DiagnosticAnalyzer
where TSyntaxKind : struct
{
private static LocalizableString localizableTitle = new LocalizableResourceString(nameof(RoslynDiagnosticsResources.SymbolDeclaredEventRuleTitle), RoslynDiagnosticsResources.ResourceManager, typeof(RoslynDiagnosticsResources));
private static LocalizableString localizableMessage = new LocalizableResourceString(nameof(RoslynDiagnosticsResources.SymbolDeclaredEventRuleMessage), RoslynDiagnosticsResources.ResourceManager, typeof(RoslynDiagnosticsResources));
private static LocalizableString localizableDescription = new LocalizableResourceString(nameof(RoslynDiagnosticsResources.SymbolDeclaredEventRuleDescription), RoslynDiagnosticsResources.ResourceManager, typeof(RoslynDiagnosticsResources));
private static readonly string fullNameOfSymbol = typeof(ISymbol).FullName;
internal static readonly DiagnosticDescriptor SymbolDeclaredEventRule = new DiagnosticDescriptor(
RoslynDiagnosticIds.SymbolDeclaredEventRuleId,
localizableTitle,
localizableMessage,
"Reliability",
DiagnosticSeverity.Error,
isEnabledByDefault: false,
description: localizableDescription,
customTags: WellKnownDiagnosticTags.Telemetry);
public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
return ImmutableArray.Create(SymbolDeclaredEventRule);
}
}
public sealed override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(compilationContext =>
{
var symbolType = compilationContext.Compilation.GetTypeByMetadataName(fullNameOfSymbol);
if (symbolType != null)
{
var compilationAnalyzer = GetCompilationAnalyzer(compilationContext.Compilation, symbolType);
if (compilationAnalyzer != null)
{
compilationContext.RegisterSyntaxNodeAction(compilationAnalyzer.AnalyzeNode, InvocationExpressionSyntaxKind);
compilationContext.RegisterSymbolAction(compilationAnalyzer.AnalyzeNamedType, SymbolKind.NamedType);
compilationContext.RegisterCompilationEndAction(compilationAnalyzer.AnalyzeCompilationEnd);
}
}
});
}
protected abstract TSyntaxKind InvocationExpressionSyntaxKind { get; }
protected abstract CompilationAnalyzer GetCompilationAnalyzer(Compilation compilation, INamedTypeSymbol symbolType);
protected abstract class CompilationAnalyzer
{
private readonly INamedTypeSymbol symbolType;
private readonly INamedTypeSymbol compilationType;
private readonly HashSet<INamedTypeSymbol> sourceSymbolsToCheck = new HashSet<INamedTypeSymbol>();
private readonly HashSet<INamedTypeSymbol> typesWithSymbolDeclaredEventInvoked = new HashSet<INamedTypeSymbol>();
private const string SymbolDeclaredEventName = "SymbolDeclaredEvent";
protected CompilationAnalyzer(INamedTypeSymbol symbolType, INamedTypeSymbol compilationType)
{
this.symbolType = symbolType;
this.compilationType = compilationType;
// If the below assert fire then probably the definition of "SymbolDeclaredEvent" has changed and we need to fix this analyzer.
var symbolDeclaredEvent = compilationType.GetMembers(SymbolDeclaredEventName).Single();
Contract.ThrowIfFalse(symbolDeclaredEvent.GetParameters().Count() == 1);
}
protected abstract SyntaxNode GetFirstArgumentOfInvocation(SyntaxNode invocation);
protected abstract HashSet<string> SymbolTypesWithExpectedSymbolDeclaredEvent { get; }
internal void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var invocationSymbol = context.SemanticModel.GetSymbolInfo(context.Node, context.CancellationToken).Symbol;
if (invocationSymbol != null &&
invocationSymbol.Kind == SymbolKind.Method)
{
AnalyzeMethodInvocation((IMethodSymbol)invocationSymbol, context);
}
}
internal virtual void AnalyzeMethodInvocation(IMethodSymbol invocationSymbol, SyntaxNodeAnalysisContext context)
{
if (invocationSymbol.Name.Equals(SymbolDeclaredEventName) &&
compilationType.Equals(invocationSymbol.ContainingType))
{
var argument = GetFirstArgumentOfInvocation(context.Node);
AnalyzeSymbolDeclaredEventInvocation(argument, context);
}
}
protected bool AnalyzeSymbolDeclaredEventInvocation(SyntaxNode argument, SyntaxNodeAnalysisContext context)
{
if (argument != null)
{
var argumentType = context.SemanticModel.GetTypeInfo(argument, context.CancellationToken).Type;
return AnalyzeSymbolDeclaredEventInvocation(argumentType);
}
return false;
}
private bool AnalyzeSymbolDeclaredEventInvocation(ISymbol type)
{
if (type != null &&
type.Kind == SymbolKind.NamedType &&
!type.Name.Equals("Symbol"))
{
var namedType = (INamedTypeSymbol)type;
if (namedType.AllInterfaces.Contains(symbolType))
{
typesWithSymbolDeclaredEventInvoked.Add(namedType);
return true;
}
}
return false;
}
internal void AnalyzeNamedType(SymbolAnalysisContext context)
{
var namedType = (INamedTypeSymbol)context.Symbol;
if (!namedType.IsAbstract &&
namedType.Name.StartsWith("Source") &&
!namedType.Name.Contains("Backing") &&
namedType.AllInterfaces.Contains(symbolType) &&
namedType.GetBaseTypesAndThis().Any(b => SymbolTypesWithExpectedSymbolDeclaredEvent.Contains(b.Name, StringComparer.Ordinal)))
{
sourceSymbolsToCheck.Add(namedType);
}
}
internal void AnalyzeCompilationEnd(CompilationEndAnalysisContext context)
{
foreach (var sourceSymbol in sourceSymbolsToCheck)
{
var found = false;
foreach (var type in sourceSymbol.GetBaseTypesAndThis())
{
if (typesWithSymbolDeclaredEventInvoked.Contains(type))
{
found = true;
break;
}
}
if (!found)
{
var diagnostic = Diagnostic.Create(SymbolDeclaredEventRule, sourceSymbol.Locations[0], sourceSymbol.Name, compilationType.Name, SymbolDeclaredEventName);
context.ReportDiagnostic(diagnostic);
}
}
}
}
}
}
......@@ -85,6 +85,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Reliability\AttributeHelpers.cs" />
<Compile Include="Reliability\DoNotCreateTasksWithoutTaskSchedulerAnalyzer.cs" />
<Compile Include="Reliability\SymbolDeclaredEventAnalyzer.cs" />
<Compile Include="Reliability\DirectlyAwaitingTaskAnalyzer.cs" />
<Compile Include="Reliability\DirectlyAwaitingTaskAnalyzerRule.cs" />
<Compile Include="Reliability\DirectlyAwaitingTaskFix.cs" />
......@@ -113,4 +114,4 @@
<Import Project="..\..\..\..\packages\StyleCop.MSBuild.4.7.48.2\build\StyleCop.MSBuild.Targets" Condition="Exists('..\..\..\..\packages\StyleCop.MSBuild.4.7.48.2\build\StyleCop.MSBuild.Targets')" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
\ No newline at end of file
</Project>
......@@ -23,5 +23,6 @@ internal static class RoslynDiagnosticIds
public const string DeclarePublicApiRuleId = "RS0016";
public const string RemoveDeletedApiRuleId = "RS0017";
public const string DoNotCreateTasksWithoutTaskSchedulerRuleId = "RS0018";
public const string SymbolDeclaredEventRuleId = "RS0019";
}
}
......@@ -348,6 +348,33 @@ internal class RoslynDiagnosticsResources {
}
}
/// <summary>
/// Looks up a localized string similar to Compilation event queue is required to generate symbol declared events for all declared source symbols. Hence, every source symbol type or one of it&apos;s base types must generate a symbol declared event..
/// </summary>
internal static string SymbolDeclaredEventRuleDescription {
get {
return ResourceManager.GetString("SymbolDeclaredEventRuleDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Symbol &apos;{0}&apos; seems to be a source symbol, but neither the symbol nor any of it&apos;s base types invoke method &apos;{1}.{2}&apos; to register a symbol declared event..
/// </summary>
internal static string SymbolDeclaredEventRuleMessage {
get {
return ResourceManager.GetString("SymbolDeclaredEventRuleMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SymbolDeclaredEvent must be generated for source symbols.
/// </summary>
internal static string SymbolDeclaredEventRuleTitle {
get {
return ResourceManager.GetString("SymbolDeclaredEventRuleTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Avoid zero-length array allocations..
/// </summary>
......
......@@ -246,4 +246,13 @@
<data name="DoNotCreateTasksWithoutTaskSchedulerMessage" xml:space="preserve">
<value>Do not call {0} without passing a TaskScheduler</value>
</data>
<data name="SymbolDeclaredEventRuleTitle" xml:space="preserve">
<value>SymbolDeclaredEvent must be generated for source symbols</value>
</data>
<data name="SymbolDeclaredEventRuleMessage" xml:space="preserve">
<value>Symbol '{0}' seems to be a source symbol, but neither the symbol nor any of it's base types invoke method '{1}.{2}' to register a symbol declared event.</value>
</data>
<data name="SymbolDeclaredEventRuleDescription" xml:space="preserve">
<value>Compilation event queue is required to generate symbol declared events for all declared source symbols. Hence, every source symbol type or one of it's base types must generate a symbol declared event.</value>
</data>
</root>
\ No newline at end of file
......@@ -101,6 +101,7 @@
<Compile Include="Performance\BasicEmptyArrayDiagnosticAnalyzer.vb" />
<Compile Include="Performance\BasicSpecializedEnumerableCreationAnalyzer.vb" />
<Compile Include="Reliability\BasicDoNotCreateTasksWithoutTaskSchedulerAnalyzer.vb" />
<Compile Include="Reliability\BasicSymbolDeclaredEventAnalyzer.vb" />
<Compile Include="Reliability\BasicConsumePreserveSigAnalyzer.vb" />
<Compile Include="Reliability\BasicDirectlyAwaitingTaskAnalyzer.vb" />
<Compile Include="Reliability\BasicDirectlyAwaitingTaskFix.vb" />
......@@ -114,4 +115,4 @@
<Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
\ No newline at end of file
</Project>
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Roslyn.Diagnostics.Analyzers.VisualBasic
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Public Class BasicSymbolDeclaredEventAnalyzer
Inherits SymbolDeclaredEventAnalyzer(Of SyntaxKind)
Private Shared ReadOnly sourceModuleTypeFullName As String = "Microsoft.CodeAnalysis.VisualBasic.Symbols.SourceModuleSymbol"
Protected Overrides Function GetCompilationAnalyzer(compilation As Compilation, symbolType As INamedTypeSymbol) As CompilationAnalyzer
Dim compilationType = compilation.GetTypeByMetadataName(GetType(VisualBasicCompilation).FullName)
If compilationType Is Nothing Then
Return Nothing
End If
Dim sourceModuleType = compilation.GetTypeByMetadataName(sourceModuleTypeFullName)
If sourceModuleType Is Nothing Then
Return Nothing
End If
Return New BasicCompilationAnalyzer(symbolType, compilationType, sourceModuleType)
End Function
Protected Overrides ReadOnly Property InvocationExpressionSyntaxKind As SyntaxKind
Get
Return SyntaxKind.InvocationExpression
End Get
End Property
Private NotInheritable Class BasicCompilationAnalyzer
Inherits CompilationAnalyzer
Private ReadOnly sourceModuleType As INamedTypeSymbol
Private Shared ReadOnly _symbolTypesWithExpectedSymbolDeclaredEvent As HashSet(Of String) =
New HashSet(Of String) From {"SourceNamespaceSymbol", "SourceNamedTypeSymbol", "SourceEventSymbol", "SourceFieldSymbol", "SourceMethodSymbol", "SourcePropertySymbol"}
Private Const AtomicSetFlagAndRaiseSymbolDeclaredEventName As String = "AtomicSetFlagAndRaiseSymbolDeclaredEvent"
Public Sub New(symbolType As INamedTypeSymbol, compilationType As INamedTypeSymbol, sourceModuleSymbol As INamedTypeSymbol)
MyBase.New(symbolType, compilationType)
Me.sourceModuleType = sourceModuleSymbol
End Sub
Protected Overrides ReadOnly Property SymbolTypesWithExpectedSymbolDeclaredEvent As HashSet(Of String)
Get
Return _symbolTypesWithExpectedSymbolDeclaredEvent
End Get
End Property
Protected Overrides Function GetFirstArgumentOfInvocation(node As SyntaxNode) As SyntaxNode
Dim invocation = DirectCast(node, InvocationExpressionSyntax)
If invocation.ArgumentList IsNot Nothing Then
Dim argument = invocation.ArgumentList.Arguments.FirstOrDefault()
If argument IsNot Nothing Then
Return argument.GetExpression
End If
End If
Return Nothing
End Function
Friend Overrides Sub AnalyzeMethodInvocation(invocationSymbol As IMethodSymbol, context As SyntaxNodeAnalysisContext)
If invocationSymbol.Name.Equals(AtomicSetFlagAndRaiseSymbolDeclaredEventName, StringComparison.OrdinalIgnoreCase) AndAlso
sourceModuleType.Equals(invocationSymbol.ContainingType) Then
Dim argumentOpt As SyntaxNode = Nothing
Dim invocationExp = DirectCast(context.Node, InvocationExpressionSyntax)
If invocationExp.ArgumentList IsNot Nothing Then
For Each argument In invocationExp.ArgumentList.Arguments
If AnalyzeSymbolDeclaredEventInvocation(argument.GetExpression, context) Then
Exit For
End If
Next
End If
End If
MyBase.AnalyzeMethodInvocation(invocationSymbol, context)
End Sub
End Class
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册