提交 6c7e4ea3 编写于 作者: H heejaechang

Add an analyzer that can detect unused declarations.

this is compilation end analyzer. this is supposed to squiggle name and fade out whole code block when it detects unused declarations. but for now, it only reports the issue in error list and not in editor due to engine limitation.

we will use this analyzer to drive making compilation end analyzer experience better. so that we can get things like squiggle, fading out or code fix work with compilation end analyzer. (changeset 1409499)
上级 9747a890
...@@ -75,6 +75,7 @@ ...@@ -75,6 +75,7 @@
<Compile Include="ApiDesign\CancellationTokenMustBeLastCodeFixProvider.cs" /> <Compile Include="ApiDesign\CancellationTokenMustBeLastCodeFixProvider.cs" />
<Compile Include="Documentation\CSharpDoNotUseVerbatimCrefsAnalyzer.cs" /> <Compile Include="Documentation\CSharpDoNotUseVerbatimCrefsAnalyzer.cs" />
<Compile Include="Reliability\CSharpDoNotCreateTasksWithoutTaskSchedulerAnalyzer.cs" /> <Compile Include="Reliability\CSharpDoNotCreateTasksWithoutTaskSchedulerAnalyzer.cs" />
<Compile Include="Maintainability\CSharpUnusedDeclarationsAnalyzer.cs" />
<Compile Include="Reliability\CSharpSymbolDeclaredEventAnalyzer.cs" /> <Compile Include="Reliability\CSharpSymbolDeclaredEventAnalyzer.cs" />
<Compile Include="Reliability\CSharpConsumePreserveSigAnalyzer.cs" /> <Compile Include="Reliability\CSharpConsumePreserveSigAnalyzer.cs" />
<Compile Include="Performance\CSharpDiagnosticDescriptorAccessAnalyzer.cs" /> <Compile Include="Performance\CSharpDiagnosticDescriptorAccessAnalyzer.cs" />
...@@ -92,4 +93,4 @@ ...@@ -92,4 +93,4 @@
<Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" /> <Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup> </ImportGroup>
</Project> </Project>
\ No newline at end of file
// 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.Collections.Generic;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Roslyn.Diagnostics.Analyzers.CSharp
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class CSharpUnusedDeclarationsAnalyzer : UnusedDeclarationsAnalyzer<SyntaxKind>
{
protected override SyntaxKind IdentifierSyntaxKind
{
get { return SyntaxKind.IdentifierName; }
}
protected override SyntaxKind LocalDeclarationStatementSyntaxKind
{
get { return SyntaxKind.LocalDeclarationStatement; }
}
protected override IEnumerable<SyntaxNode> GetLocalDeclarationNodes(SyntaxNode node, CancellationToken cancellationToken)
{
var locals = node as LocalDeclarationStatementSyntax;
if (locals == null)
{
yield break;
}
var variables = (locals.Declaration == null) ? (SeparatedSyntaxList<VariableDeclaratorSyntax>?)null : locals.Declaration.Variables;
if (variables == null)
{
yield break;
}
foreach (var variable in variables)
{
yield return variable;
}
}
}
}
// 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Roslyn.Diagnostics.Analyzers
{
internal abstract partial class UnusedDeclarationsAnalyzer<TLanguageKindEnum> : DiagnosticAnalyzer where TLanguageKindEnum : struct
{
internal const string Category = "Maintainability";
private static readonly LocalizableString s_title = new LocalizableResourceString(nameof(RoslynDiagnosticsResources.UnusedDeclarationsTitle), RoslynDiagnosticsResources.ResourceManager, typeof(RoslynDiagnosticsResources));
private static readonly LocalizableString s_messageFormat = new LocalizableResourceString(nameof(RoslynDiagnosticsResources.UnusedDeclarationsMessage), RoslynDiagnosticsResources.ResourceManager, typeof(RoslynDiagnosticsResources));
internal static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor(RoslynDiagnosticIds.DeadCodeRuleId, s_title, s_messageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true);
internal static readonly DiagnosticDescriptor s_triggerRule = new TriggerDiagnosticDescriptor(RoslynDiagnosticIds.DeadCodeTriggerRuleId, WellKnownDiagnosticTags.Unnecessary, WellKnownDiagnosticTags.Telemetry);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get { return ImmutableArray.Create(s_rule, s_triggerRule); }
}
protected abstract TLanguageKindEnum IdentifierSyntaxKind { get; }
protected abstract TLanguageKindEnum LocalDeclarationStatementSyntaxKind { get; }
protected abstract IEnumerable<SyntaxNode> GetLocalDeclarationNodes(SyntaxNode node, CancellationToken cancellationToken);
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(c =>
{
var tracker = new UnusedDeclarationsTracker(this);
c.RegisterCompilationEndAction(tracker.OnCompilationEnd);
c.RegisterSyntaxNodeAction<TLanguageKindEnum>(tracker.OnIdentifier, IdentifierSyntaxKind);
c.RegisterSyntaxNodeAction<TLanguageKindEnum>(tracker.OnLocalDeclaration, LocalDeclarationStatementSyntaxKind);
c.RegisterSymbolAction(
tracker.OnSymbol,
SymbolKind.NamedType,
SymbolKind.Method,
SymbolKind.Property,
SymbolKind.Event,
SymbolKind.Field);
});
}
private class UnusedDeclarationsTracker
{
private readonly ConcurrentDictionary<ISymbol, bool> _used = new ConcurrentDictionary<ISymbol, bool>(concurrencyLevel: 2, capacity: 100);
private readonly UnusedDeclarationsAnalyzer<TLanguageKindEnum> _owner;
public UnusedDeclarationsTracker(UnusedDeclarationsAnalyzer<TLanguageKindEnum> owner)
{
_owner = owner;
}
public void OnIdentifier(SyntaxNodeAnalysisContext context)
{
context.CancellationToken.ThrowIfCancellationRequested();
var info = context.SemanticModel.GetSymbolInfo(context.Node, context.CancellationToken);
var hasLocations = info.Symbol?.OriginalDefinition?.Locations.Length > 0;
var inSource = info.Symbol?.OriginalDefinition?.Locations[0].IsInSource == true;
if (!hasLocations || !inSource || AccessibleFromOutside(info.Symbol.OriginalDefinition))
{
return;
}
_used.AddOrUpdate(info.Symbol.OriginalDefinition, true, (k, v) => true);
}
public void OnLocalDeclaration(SyntaxNodeAnalysisContext context)
{
foreach (var node in _owner.GetLocalDeclarationNodes(context.Node, context.CancellationToken))
{
var local = context.SemanticModel.GetDeclaredSymbol(node, context.CancellationToken);
if (local == null)
{
continue;
}
_used.TryAdd(local, false);
}
}
public void OnSymbol(SymbolAnalysisContext context)
{
context.CancellationToken.ThrowIfCancellationRequested();
var symbol = context.Symbol;
if (!AccessibleFromOutside(symbol))
{
_used.TryAdd(symbol, false);
}
var type = symbol as INamedTypeSymbol;
if (type != null)
{
AddSymbolDeclarations(type.TypeParameters);
}
var method = symbol as IMethodSymbol;
if (method != null)
{
AddParameters(method.DeclaredAccessibility, method.TypeParameters);
AddParameters(method.DeclaredAccessibility, method.Parameters);
}
var property = symbol as IPropertySymbol;
if (property != null)
{
AddParameters(property.DeclaredAccessibility, property.Parameters);
if (!AccessibleFromOutside(property.GetMethod))
{
_used.TryAdd(property.GetMethod, false);
}
if (!AccessibleFromOutside(property.SetMethod))
{
_used.TryAdd(property.SetMethod, false);
AddParameters(property.SetMethod.DeclaredAccessibility, property.SetMethod.Parameters);
}
}
}
private void AddParameters(Accessibility accessibility, IEnumerable<ISymbol> parameters)
{
// only add parameters if accessibility is explicitly set to private.
if (accessibility != Accessibility.Private)
{
return;
}
AddSymbolDeclarations(parameters);
}
private void AddSymbolDeclarations(IEnumerable<ISymbol> symbols)
{
if (symbols == null)
{
return;
}
foreach (var symbol in symbols)
{
_used.TryAdd(symbol, false);
}
}
public void OnCompilationEnd(CompilationEndAnalysisContext context)
{
foreach (var kv in _used.Where(kv => !kv.Value && (kv.Key.Locations.FirstOrDefault()?.IsInSource == true)))
{
context.CancellationToken.ThrowIfCancellationRequested();
var symbol = kv.Key;
// report visible error only if symbol is not local symbol
if (!(symbol is ILocalSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(s_rule, symbol.Locations[0], symbol.Locations.Skip(1), symbol.Name));
}
// where code fix works
foreach (var reference in symbol.DeclaringSyntaxReferences)
{
context.CancellationToken.ThrowIfCancellationRequested();
context.ReportDiagnostic(Diagnostic.Create(s_triggerRule, Location.Create(reference.SyntaxTree, reference.Span)));
}
}
}
private static bool AccessibleFromOutside(ISymbol symbol)
{
if (symbol == null ||
symbol.Kind == SymbolKind.Namespace)
{
return true;
}
if (symbol.DeclaredAccessibility == Accessibility.Private ||
symbol.DeclaredAccessibility == Accessibility.NotApplicable)
{
return false;
}
if (symbol.ContainingSymbol == null)
{
return true;
}
return AccessibleFromOutside(symbol.ContainingSymbol.OriginalDefinition);
}
}
}
}
// 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.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
namespace Roslyn.Diagnostics.Analyzers
{
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = nameof(UnusedDeclarationsCodeFixProvider)), Shared]
internal class UnusedDeclarationsCodeFixProvider : CodeFixProvider
{
private static readonly Task _done = Task.Run(() => { });
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(RoslynDiagnosticIds.DeadCodeTriggerRuleId); }
}
public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.CancellationToken.ThrowIfCancellationRequested();
var document = context.Document;
context.RegisterCodeFix(new MyCodeAction(RoslynDiagnosticsResources.UnusedDeclarationsCodeFixTitle, async c =>
{
var text = await document.GetTextAsync(c).ConfigureAwait(false);
return document.WithText(text.Replace(context.Span, string.Empty));
}, RoslynDiagnosticIds.DeadCodeTriggerRuleId), context.Diagnostics);
return _done;
}
private class MyCodeAction : DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> changedDocument, string key) :
base(title, changedDocument, key)
{
}
}
}
}
...@@ -74,6 +74,8 @@ ...@@ -74,6 +74,8 @@
<Compile Include="DocumentChangedAction.cs" /> <Compile Include="DocumentChangedAction.cs" />
<Compile Include="ISymbolExtensions.cs" /> <Compile Include="ISymbolExtensions.cs" />
<Compile Include="Performance\CodeActionCreateAnalyzer.cs" /> <Compile Include="Performance\CodeActionCreateAnalyzer.cs" />
<Compile Include="Maintainability\UnusedDeclarationsAnalyzer.cs" />
<Compile Include="Maintainability\UnusedDeclarationsCodeFixer.cs" />
<Compile Include="Performance\EmptyArrayDiagnosticAnalyzer.cs" /> <Compile Include="Performance\EmptyArrayDiagnosticAnalyzer.cs" />
<Compile Include="Performance\EquatableAnalyzer.cs" /> <Compile Include="Performance\EquatableAnalyzer.cs" />
<Compile Include="Performance\SpecializedEnumerableCreationAnalyzer.cs" /> <Compile Include="Performance\SpecializedEnumerableCreationAnalyzer.cs" />
...@@ -108,4 +110,4 @@ ...@@ -108,4 +110,4 @@
<Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" /> <Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup> </ImportGroup>
</Project> </Project>
\ No newline at end of file
...@@ -24,5 +24,7 @@ internal static class RoslynDiagnosticIds ...@@ -24,5 +24,7 @@ internal static class RoslynDiagnosticIds
public const string RemoveDeletedApiRuleId = "RS0017"; public const string RemoveDeletedApiRuleId = "RS0017";
public const string DoNotCreateTasksWithoutTaskSchedulerRuleId = "RS0018"; public const string DoNotCreateTasksWithoutTaskSchedulerRuleId = "RS0018";
public const string SymbolDeclaredEventRuleId = "RS0019"; public const string SymbolDeclaredEventRuleId = "RS0019";
public const string DeadCodeRuleId = "RS0020";
public const string DeadCodeTriggerRuleId = "RS0021";
} }
} }
...@@ -375,6 +375,33 @@ internal class RoslynDiagnosticsResources { ...@@ -375,6 +375,33 @@ internal class RoslynDiagnosticsResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Remove unused code.
/// </summary>
internal static string UnusedDeclarationsCodeFixTitle {
get {
return ResourceManager.GetString("UnusedDeclarationsCodeFixTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &apos;{0}&apos; is not used in solution..
/// </summary>
internal static string UnusedDeclarationsMessage {
get {
return ResourceManager.GetString("UnusedDeclarationsMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to unused code.
/// </summary>
internal static string UnusedDeclarationsTitle {
get {
return ResourceManager.GetString("UnusedDeclarationsTitle", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Avoid zero-length array allocations.. /// Looks up a localized string similar to Avoid zero-length array allocations..
/// </summary> /// </summary>
......
...@@ -237,6 +237,18 @@ ...@@ -237,6 +237,18 @@
<data name="RemoveDeletedApiDescription" xml:space="preserve"> <data name="RemoveDeletedApiDescription" xml:space="preserve">
<value>When removing a public type or member the corresponding entry in PublicAPI.txt should also be removed. This draws attention to API changes in the code reviews and source control history, and helps prevent breaking changes.</value> <value>When removing a public type or member the corresponding entry in PublicAPI.txt should also be removed. This draws attention to API changes in the code reviews and source control history, and helps prevent breaking changes.</value>
</data> </data>
<data name="UnusedDeclarationsCodeFixTitle" xml:space="preserve">
<value>Remove unused code</value>
<comment>The title of the fix</comment>
</data>
<data name="UnusedDeclarationsMessage" xml:space="preserve">
<value>'{0}' is not used in solution.</value>
<comment>The format-able message the diagnostic displays.</comment>
</data>
<data name="UnusedDeclarationsTitle" xml:space="preserve">
<value>unused code</value>
<comment>The title of the diagnostic.</comment>
</data>
<data name="DoNotCreateTasksWithoutTaskSchedulerDescription" xml:space="preserve"> <data name="DoNotCreateTasksWithoutTaskSchedulerDescription" xml:space="preserve">
<value>Do not create tasks unless you are using one of the overloads that takes a TaskScheduler. The default is to schedule on TaskScheduler.Current, which would lead to deadlocks. Either use TaskScheduler.Default to schedule on the thread pool, or explicitly pass TaskScheduler.Current to make your intentions clear.</value> <value>Do not create tasks unless you are using one of the overloads that takes a TaskScheduler. The default is to schedule on TaskScheduler.Current, which would lead to deadlocks. Either use TaskScheduler.Default to schedule on the thread pool, or explicitly pass TaskScheduler.Current to make your intentions clear.</value>
</data> </data>
......
...@@ -95,6 +95,7 @@ ...@@ -95,6 +95,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Documentation\BasicDoNotUseVerbatimCrefsAnalyzer.vb" /> <Compile Include="Documentation\BasicDoNotUseVerbatimCrefsAnalyzer.vb" />
<Compile Include="Maintainability\BasicUnusedDeclarationsAnalyzer.vb" />
<Compile Include="Performance\BasicDiagnosticDescriptorAccessAnalyzer.vb" /> <Compile Include="Performance\BasicDiagnosticDescriptorAccessAnalyzer.vb" />
<Compile Include="Performance\BasicCodeActionCreateAnalyzer.vb" /> <Compile Include="Performance\BasicCodeActionCreateAnalyzer.vb" />
<Compile Include="Performance\BasicEmptyArrayCodeFixProvider.vb" /> <Compile Include="Performance\BasicEmptyArrayCodeFixProvider.vb" />
...@@ -115,4 +116,4 @@ ...@@ -115,4 +116,4 @@
<Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" /> <Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup> </ImportGroup>
</Project> </Project>
\ No newline at end of file
' 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 System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Roslyn.Diagnostics.Analyzers.VisualBasic
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend Class VisualBasicUnusedDeclarationsAnalyzer
Inherits UnusedDeclarationsAnalyzer(Of SyntaxKind)
Protected Overrides ReadOnly Property IdentifierSyntaxKind As SyntaxKind
Get
Return SyntaxKind.IdentifierName
End Get
End Property
Protected Overrides ReadOnly Property LocalDeclarationStatementSyntaxKind As SyntaxKind
Get
Return SyntaxKind.LocalDeclarationStatement
End Get
End Property
Protected Overrides Iterator Function GetLocalDeclarationNodes(node As SyntaxNode, cancellationToken As CancellationToken) As IEnumerable(Of SyntaxNode)
Dim locals = TryCast(node, LocalDeclarationStatementSyntax)
If locals Is Nothing Then
Return
End If
For Each variable In locals.Declarators
Yield variable
Next
End Function
End Class
End Namespace
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册