未验证 提交 eb489b72 编写于 作者: M Manish Vasani 提交者: GitHub

Merge pull request #29852 from mavasani/DeadCodeAnalysis_FollowUp

Enable DeadCodeAnalysis rules and address design/review feedback.
......@@ -49,11 +49,11 @@ Friend Module ParserTestUtilities
End Function
Public Function ParseAndVerify(code As XCData, ParamArray expectedDiagnostics() As DiagnosticDescription) As SyntaxTree
Return ParseAndVerify(TestBase.NormalizeNewLines(code), VisualBasicParseOptions.Default, expectedDiagnostics, errorCodesOnly:=False)
Return ParseAndVerify(TestHelpers.NormalizeNewLines(code), VisualBasicParseOptions.Default, expectedDiagnostics, errorCodesOnly:=False)
End Function
Public Function ParseAndVerify(code As XCData, options As VisualBasicParseOptions, ParamArray expectedDiagnostics() As DiagnosticDescription) As SyntaxTree
Return ParseAndVerify(TestBase.NormalizeNewLines(code), options, expectedDiagnostics, errorCodesOnly:=False)
Return ParseAndVerify(TestHelpers.NormalizeNewLines(code), options, expectedDiagnostics, errorCodesOnly:=False)
End Function
Public Function ParseAndVerify(source As String, ParamArray expectedDiagnostics() As DiagnosticDescription) As SyntaxTree
......
......@@ -1849,7 +1849,7 @@ End Structure
Optional latestReferences As Boolean = False,
Optional addXmlReferences As Boolean = False,
Optional diagnostics() As DiagnosticDescription = Nothing)
TestExpressionTrees(sourceFile, TestBase.NormalizeNewLines(result), checked, optimize, latestReferences, addXmlReferences, diagnostics)
TestExpressionTrees(sourceFile, TestHelpers.NormalizeNewLines(result), checked, optimize, latestReferences, addXmlReferences, diagnostics)
End Sub
Private Class ExpressionTreeTest
......
......@@ -43,7 +43,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
var symbol = context.SemanticModel.GetSymbolInfo(node).Symbol;
if (symbol != null && symbol.Kind == SymbolKind.Field)
{
var diagnostic = CodeAnalysis.Diagnostic.Create(Descriptor, node.GetLocation());
var diagnostic = Diagnostic.Create(Descriptor, node.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
......
......@@ -47,7 +47,7 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
var location = _reportDiagnosticsWithoutLocation ? Location.None : classDecl.Identifier.GetLocation();
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Decsciptor, location));
context.ReportDiagnostic(Diagnostic.Create(Decsciptor, location));
}
}
......
......@@ -47,8 +47,8 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
var location = classDecl.Identifier.GetLocation();
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Decsciptor1, location));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Decsciptor2, location));
context.ReportDiagnostic(Diagnostic.Create(Decsciptor1, location));
context.ReportDiagnostic(Diagnostic.Create(Decsciptor2, location));
}
}
......
......@@ -468,7 +468,7 @@ public override void Initialize(AnalysisContext context)
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Decsciptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Decsciptor, classDecl.Identifier.GetLocation()));
}
}
......@@ -530,7 +530,7 @@ public override void Initialize(AnalysisContext context)
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()));
}
}
......@@ -595,7 +595,7 @@ public override void Initialize(AnalysisContext context)
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()));
}
}
......@@ -647,7 +647,7 @@ public override void Initialize(AnalysisContext context)
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Decsciptor, classDecl.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Decsciptor, classDecl.GetLocation()));
}
}
......@@ -760,39 +760,39 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
case SyntaxKind.ClassDeclaration:
var classDecl = (ClassDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, classDecl.Identifier.GetLocation()));
break;
case SyntaxKind.NamespaceDeclaration:
var ns = (NamespaceDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, ns.Name.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, ns.Name.GetLocation()));
break;
case SyntaxKind.MethodDeclaration:
var method = (MethodDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, method.Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, method.Identifier.GetLocation()));
break;
case SyntaxKind.PropertyDeclaration:
var property = (PropertyDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, property.Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, property.Identifier.GetLocation()));
break;
case SyntaxKind.FieldDeclaration:
var field = (FieldDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, field.Declaration.Variables.First().Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, field.Declaration.Variables.First().Identifier.GetLocation()));
break;
case SyntaxKind.EventDeclaration:
var e = (EventDeclarationSyntax)context.Node;
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, e.Identifier.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, e.Identifier.GetLocation()));
break;
case SyntaxKind.EnumDeclaration:
// Report diagnostic on each descendant comment trivia
foreach (var trivia in context.Node.DescendantTrivia().Where(t => t.Kind() == SyntaxKind.SingleLineCommentTrivia || t.Kind() == SyntaxKind.MultiLineCommentTrivia))
{
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, trivia.GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, trivia.GetLocation()));
}
break;
}
......@@ -1571,7 +1571,7 @@ public override void Initialize(AnalysisContext context)
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, Location.None));
context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.None));
}
}
......
......@@ -5,17 +5,16 @@
using Microsoft.CodeAnalysis.CSharp.RemoveUnusedMembers;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.RemoveUnusedMembers;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
using static Roslyn.Test.Utilities.TestHelpers;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnusedMembers
{
public class RemoveUnusedMembersTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new CSharpRemoveUnusedMembersDiagnosticAnalyzer(forceEnableRules: true),
new CSharpRemoveUnusedMembersCodeFixProvider());
=> (new CSharpRemoveUnusedMembersDiagnosticAnalyzer(), new CSharpRemoveUnusedMembersCodeFixProvider());
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[InlineData("public")]
......@@ -32,6 +31,37 @@ public async Task NonPrivateField(string accessibility)
}}");
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[InlineData("public")]
[InlineData("internal")]
[InlineData("protected")]
[InlineData("protected internal")]
[InlineData("private protected")]
public async Task NonPrivateFieldWithConstantInitializer(string accessibility)
{
await TestMissingInRegularAndScriptAsync(
$@"class MyClass
{{
{accessibility} int [|_goo|] = 0;
}}");
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[InlineData("public")]
[InlineData("internal")]
[InlineData("protected")]
[InlineData("protected internal")]
[InlineData("private protected")]
public async Task NonPrivateFieldWithNonConstantInitializer(string accessibility)
{
await TestMissingInRegularAndScriptAsync(
$@"class MyClass
{{
{accessibility} int [|_goo|] = _goo2;
private static readonly int _goo2 = 0;
}}");
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[InlineData("public")]
[InlineData("internal")]
......@@ -120,6 +150,76 @@ public async Task MethodIsUnused()
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task GenericMethodIsUnused()
{
await TestInRegularAndScriptAsync(
@"class MyClass
{
private int [|M|]<T>() => 0;
}",
@"class MyClass
{
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task MethodInGenericTypeIsUnused()
{
await TestInRegularAndScriptAsync(
@"class MyClass<T>
{
private int [|M|]() => 0;
}",
@"class MyClass<T>
{
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task InstanceConstructorIsUnused_NoArguments()
{
// We only flag constructors with arguments.
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
private [|MyClass()|] { }
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task InstanceConstructorIsUnused_WithArguments()
{
await TestInRegularAndScriptAsync(
@"class MyClass
{
private [|MyClass(int i)|] { }
}",
@"class MyClass
{
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task StaticConstructorIsNotFlagged()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
static [|MyClass()|] { }
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task DestructorIsNotFlagged()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
~[|MyClass()|] { }
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task PropertyIsUnused()
{
......@@ -571,6 +671,83 @@ public void M2()
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task GenericMethodIsInvoked_ExplicitTypeArguments()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
private int [|M1|]<T>() => 0;
private int M2() => M1<int>();
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task GenericMethodIsInvoked_ImplicitTypeArguments()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
private T [|M1|]<T>(T t) => t;
private int M2() => M1(0);
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task MethodInGenericTypeIsInvoked_NoTypeArguments()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass<T>
{
private int [|M1|]() => 0;
private int M2() => M1();
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task MethodInGenericTypeIsInvoked_NonConstructedType()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass<T>
{
private int [|M1|]() => 0;
private int M2(MyClass<T> m) => m.M1();
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task MethodInGenericTypeIsInvoked_ConstructedType()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass<T>
{
private int [|M1|]() => 0;
private int M2(MyClass<int> m) => m.M1();
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task InstanceConstructorIsUsed_NoArguments()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
private [|MyClass()|] { }
public static readonly MyClass Instance = new MyClass();
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task InstanceConstructorIsUsed_WithArguments()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
private [|MyClass(int i)|] { }
public static readonly MyClass Instance = new MyClass(0);
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task PropertyIsRead()
{
......
......@@ -26,7 +26,7 @@
namespace Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions
{
[UseExportProvider]
public abstract class AbstractCodeActionOrUserDiagnosticTest : TestBase
public abstract class AbstractCodeActionOrUserDiagnosticTest
{
public struct TestParameters
{
......
......@@ -629,7 +629,7 @@ End Class]]>
Private Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -703,7 +703,7 @@ End Class]]>
Private Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -747,7 +747,7 @@ End Class]]>
Public Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -811,7 +811,7 @@ End Class]]>
Public Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -931,32 +931,32 @@ End Class]]>
Select Case context.Node.Kind()
Case SyntaxKind.ClassStatement
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
Exit Select
Case SyntaxKind.NamespaceStatement
Dim ns = DirectCast(context.Node, NamespaceStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, ns.Name.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, ns.Name.GetLocation()))
Exit Select
Case SyntaxKind.SubStatement, SyntaxKind.FunctionStatement
Dim method = DirectCast(context.Node, MethodStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, method.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, method.Identifier.GetLocation()))
Exit Select
Case SyntaxKind.PropertyStatement
Dim p = DirectCast(context.Node, PropertyStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, p.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, p.Identifier.GetLocation()))
Exit Select
Case SyntaxKind.FieldDeclaration
Dim f = DirectCast(context.Node, FieldDeclarationSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, f.Declarators.First().Names.First.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, f.Declarators.First().Names.First.GetLocation()))
Exit Select
Case SyntaxKind.EventStatement
Dim e = DirectCast(context.Node, EventStatementSyntax)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, e.Identifier.GetLocation()))
context.ReportDiagnostic(Diagnostic.Create(_descriptor, e.Identifier.GetLocation()))
Exit Select
End Select
End Sub
......
......@@ -3,16 +3,17 @@
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics
Imports Microsoft.CodeAnalysis.RemoveUnusedMembers
Imports Microsoft.CodeAnalysis.VisualBasic.RemoveUnusedMembers
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnusedMembers
Public Class RemoveUnusedMembersTests
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider)
Return (New VisualBasicRemoveUnusedMembersDiagnosticAnalyzer(forceEnableRules:=True),
New VisualBasicRemoveUnusedMembersCodeFixProvider())
Return (New VisualBasicRemoveUnusedMembersDiagnosticAnalyzer(), New VisualBasicRemoveUnusedMembersCodeFixProvider())
End Function
Private Shared Function Diagnostic(id As String) As DiagnosticDescription
Return TestHelpers.Diagnostic(id)
End Function
<Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
......@@ -27,6 +28,31 @@ $"Class C
End Class")
End Function
<Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
<InlineData("Public")>
<InlineData("Friend")>
<InlineData("Protected")>
<InlineData("Protected Friend")>
Public Async Function NonPrivateFieldWithConstantInitializer(accessibility As String) As Task
Await TestMissingInRegularAndScriptAsync(
$"Class C
{accessibility} [|_goo|] As Integer = 0
End Class")
End Function
<Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
<InlineData("Public")>
<InlineData("Friend")>
<InlineData("Protected")>
<InlineData("Protected Friend")>
Public Async Function NonPrivateFieldWithNonConstantInitializer(accessibility As String) As Task
Await TestMissingInRegularAndScriptAsync(
$"Class C
{accessibility} [|_goo|] As Integer = _goo2
Private Shared ReadOnly _goo2 As Integer = 0
End Class")
End Function
<Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
<InlineData("Public")>
<InlineData("Friend")>
......@@ -103,6 +129,58 @@ End Class",
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function GenericMethodIsUnused() As Task
Await TestInRegularAndScriptAsync(
"Class C
Private Sub [|M|](Of T)()
End Sub
End Class",
"Class C
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function MethodInGenericTypeIsUnused() As Task
Await TestInRegularAndScriptAsync(
"Class C(Of T)
Private Sub [|M|]()
End Sub
End Class",
"Class C(Of T)
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function InstanceConstructorIsUnused_NoArguments() As Task
' We only flag constructors with arguments.
Await TestMissingInRegularAndScriptAsync(
"Class C
Private Sub [|New()|]
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function InstanceConstructorIsUnused_WithArguments() As Task
Await TestInRegularAndScriptAsync(
"Class C
Private Sub [|New(i As Integer)|]
End Sub
End Class",
"Class C
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function StaticConstructorIsNotFlagged() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Shared Sub [|New()|]
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function PropertyIsUnused() As Task
Await TestInRegularAndScriptAsync(
......@@ -379,6 +457,115 @@ End Class")
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function GenericMethodIsInvoked_ExplicitTypeArguments() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private Sub [|M1|](Of T)()
End Sub
Private Sub M2()
M1(Of Integer)()
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function GenericMethodIsInvoked_ImplicitTypeArguments() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private Sub [|M1|](Of T)(t1 As T)
End Sub
Private Sub M2()
M1(0)
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function MethodInGenericTypeIsInvoked_NoTypeArguments() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C(Of T)
Private Sub [|M1|]()
End Sub
Private Sub M2()
M1()
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function MethodInGenericTypeIsInvoked_NonConstructedType() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C(Of T)
Private Sub [|M1|]()
End Sub
Private Sub M2(m As C(Of T))
m.M1()
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function MethodInGenericTypeIsInvoked_ConstructedType() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C(Of T)
Private Sub [|M1|]()
End Sub
Private Sub M2(m As C(Of Integer))
m.M1()
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function InstanceConstructorIsUsed_NoArguments() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private Sub [|New|]()
End Sub
Public Shared ReadOnly Instance As C = New C()
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function InstanceConstructorIsUsed_NoArguments_AsNew() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private Sub [|New|]()
End Sub
Public Shared ReadOnly Instance As New C()
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function InstanceConstructorIsUsed_WithArguments() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private Sub [|New|](i As Integer)
End Sub
Public Shared ReadOnly Instance As C = New C(0)
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function InstanceConstructorIsUsed_WithArguments_AsNew() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private Sub [|New|](i As Integer)
End Sub
Public Shared ReadOnly Instance As New C(0)
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function PropertyIsRead() As Task
Await TestMissingInRegularAndScriptAsync(
......
......@@ -10,15 +10,5 @@ namespace Microsoft.CodeAnalysis.CSharp.RemoveUnusedMembers
internal class CSharpRemoveUnusedMembersDiagnosticAnalyzer
: AbstractRemoveUnusedMembersDiagnosticAnalyzer<DocumentationCommentTriviaSyntax, IdentifierNameSyntax>
{
public CSharpRemoveUnusedMembersDiagnosticAnalyzer()
: base(forceEnableRules: false)
{
}
// For testing purposes only.
internal CSharpRemoveUnusedMembersDiagnosticAnalyzer(bool forceEnableRules)
: base(forceEnableRules)
{
}
}
}
// 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.Diagnostics;
......@@ -19,41 +18,50 @@ internal abstract class AbstractRemoveUnusedMembersDiagnosticAnalyzer<TDocumenta
where TDocumentationCommentTriviaSyntax: SyntaxNode
where TIdentifierNameSyntax : SyntaxNode
{
protected AbstractRemoveUnusedMembersDiagnosticAnalyzer(bool forceEnableRules)
: base(CreateDescriptors(forceEnableRules))
{
}
// IDE0051: "Remove unused members" (Symbol is declared but never referenced)
private static readonly DiagnosticDescriptor s_removeUnusedMembersRule;
private static readonly DiagnosticDescriptor s_removeUnusedMembersWithFadingRule;
private static ImmutableArray<DiagnosticDescriptor> CreateDescriptors(bool forceEnableRules)
{
// TODO: Enable these rules by default once we have designed the Tools|Option location and UI for such code quality rules.
// https://github.com/dotnet/roslyn/issues/29519
// IDE0052: "Remove unread members" (Value is written and/or symbol is referenced, but the assigned value is never read)
private static readonly DiagnosticDescriptor s_removeUnreadMembersRule;
private static readonly DiagnosticDescriptor s_removeUnreadMembersWithFadingRule;
// IDE0051: "Remove unused members" (Symbol is declared but never referenced)
static AbstractRemoveUnusedMembersDiagnosticAnalyzer()
{
var removeUnusedMembersTitle = new LocalizableResourceString(nameof(FeaturesResources.Remove_unused_private_members), FeaturesResources.ResourceManager, typeof(FeaturesResources));
var removeUnusedMembersMessage = new LocalizableResourceString(nameof(FeaturesResources.Type_0_has_an_unused_private_member_1_which_can_be_removed), FeaturesResources.ResourceManager, typeof(FeaturesResources));
var removeUnusedMembersRule = CreateDescriptor(
IDEDiagnosticIds.RemoveUnusedMembersDiagnosticId, removeUnusedMembersTitle, removeUnusedMembersMessage, configurable: true, enabledByDefault: forceEnableRules);
var removeUnusedMembersRuleWithFadingRule = CreateUnnecessaryDescriptor(
IDEDiagnosticIds.RemoveUnusedMembersDiagnosticId, removeUnusedMembersTitle, removeUnusedMembersMessage, configurable: true, enabledByDefault: forceEnableRules);
s_removeUnusedMembersRule = CreateDescriptor(IDEDiagnosticIds.RemoveUnusedMembersDiagnosticId,
removeUnusedMembersTitle,
removeUnusedMembersMessage,
configurable: true,
enabledByDefault: true);
s_removeUnusedMembersWithFadingRule = CreateUnnecessaryDescriptor(IDEDiagnosticIds.RemoveUnusedMembersDiagnosticId,
removeUnusedMembersTitle,
removeUnusedMembersMessage,
configurable: true,
enabledByDefault: true);
// IDE0052: "Remove unread members" (Value is written and/or symbol is referenced, but the assigned value is never read)
var removeUnreadMembersTitle = new LocalizableResourceString(nameof(FeaturesResources.Remove_unread_private_members), FeaturesResources.ResourceManager, typeof(FeaturesResources));
var removeUnreadMembersMessage = new LocalizableResourceString(nameof(FeaturesResources.Type_0_has_a_private_member_1_that_can_be_removed_as_the_value_assigned_to_it_is_never_read), FeaturesResources.ResourceManager, typeof(FeaturesResources));
var removeUnreadMembersRule = CreateDescriptor(
IDEDiagnosticIds.RemoveUnreadMembersDiagnosticId, removeUnreadMembersTitle, removeUnreadMembersMessage, configurable: true, enabledByDefault: forceEnableRules);
var removeUnreadMembersRuleUnnecessaryWithFadingRule = CreateUnnecessaryDescriptor(
IDEDiagnosticIds.RemoveUnreadMembersDiagnosticId, removeUnreadMembersTitle, removeUnreadMembersMessage, configurable: true, enabledByDefault: forceEnableRules);
return ImmutableArray.Create(removeUnusedMembersRule, removeUnusedMembersRuleWithFadingRule,
removeUnreadMembersRule, removeUnreadMembersRuleUnnecessaryWithFadingRule);
s_removeUnreadMembersRule = CreateDescriptor(IDEDiagnosticIds.RemoveUnreadMembersDiagnosticId,
removeUnreadMembersTitle,
removeUnreadMembersMessage,
configurable: true,
enabledByDefault: true);
s_removeUnreadMembersWithFadingRule = CreateUnnecessaryDescriptor(IDEDiagnosticIds.RemoveUnreadMembersDiagnosticId,
removeUnreadMembersTitle,
removeUnreadMembersMessage,
configurable: true,
enabledByDefault: true);
}
// See CreateDescriptors method above for the indices.
// We should be able to cleanup the implementation to avoid hard coded indices
// once https://github.com/dotnet/roslyn/issues/29519 is implemented.
private DiagnosticDescriptor RemoveUnusedMemberRule => SupportedDiagnostics[1];
private DiagnosticDescriptor RemoveUnreadMemberRule => SupportedDiagnostics[3];
protected AbstractRemoveUnusedMembersDiagnosticAnalyzer()
: base (ImmutableArray.Create(s_removeUnusedMembersRule,
s_removeUnusedMembersWithFadingRule,
s_removeUnreadMembersRule,
s_removeUnreadMembersWithFadingRule))
{
}
public override bool OpenFileOnly(Workspace workspace) => false;
......@@ -70,20 +78,17 @@ protected override void InitializeWorker(AnalysisContext context)
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.RegisterCompilationStartAction(compilationStartContext
=> CompilationAnalyzer.CreateAndRegisterActions(compilationStartContext, RemoveUnusedMemberRule, RemoveUnreadMemberRule));
=> CompilationAnalyzer.CreateAndRegisterActions(compilationStartContext));
}
private sealed class CompilationAnalyzer
{
private readonly DiagnosticDescriptor _removeUnusedMembersRule, _removeUnreadMembersRule;
private readonly object _gate;
private readonly Dictionary<ISymbol, ValueUsageInfo> _symbolValueUsageStateMap;
private readonly INamedTypeSymbol _taskType, _genericTaskType;
private CompilationAnalyzer(Compilation compilation, DiagnosticDescriptor removeUnusedMembersRule, DiagnosticDescriptor removeUnreadMembersRule)
private CompilationAnalyzer(Compilation compilation)
{
_removeUnusedMembersRule = removeUnusedMembersRule;
_removeUnreadMembersRule = removeUnreadMembersRule;
_gate = new object();
// State map for candidate member symbols, with the value indicating how each symbol is used in executable code.
......@@ -93,9 +98,9 @@ private CompilationAnalyzer(Compilation compilation, DiagnosticDescriptor remove
_genericTaskType = compilation.TaskOfTType();
}
public static void CreateAndRegisterActions(CompilationStartAnalysisContext compilationStartContext, DiagnosticDescriptor removeUnusedMembersRule, DiagnosticDescriptor removeUnreadMembersRule)
public static void CreateAndRegisterActions(CompilationStartAnalysisContext compilationStartContext)
{
var compilationAnalyzer = new CompilationAnalyzer(compilationStartContext.Compilation, removeUnusedMembersRule, removeUnreadMembersRule);
var compilationAnalyzer = new CompilationAnalyzer(compilationStartContext.Compilation);
compilationAnalyzer.RegisterActions(compilationStartContext);
}
......@@ -103,12 +108,12 @@ private void RegisterActions(CompilationStartAnalysisContext compilationStartCon
{
// We register following actions in the compilation:
// 1. A symbol action for member symbols to ensure the member's unused state is initialized to true for every private member symbol.
// 2. Operation actions for member references and invocations to detect member usages, i.e. read or read reference taken.
// 2. Operation actions for member references, invocations and object creations to detect member usages, i.e. read or read reference taken.
// 3. Operation action for field initializers to detect non-constant initialization.
// 4. Operation action for invalid operations to bail out on erroneous code.
// 5. A symbol start/end action for named types to report diagnostics for candidate members that have no usage in executable code.
//
// Note that we need to register separately for OperationKind.Invocation due to https://github.com/dotnet/roslyn/issues/26206
// Note that we need to register separately for OperationKind.Invocation and OperationKind.ObjectCreation due to https://github.com/dotnet/roslyn/issues/26206
compilationStartContext.RegisterSymbolAction(AnalyzeSymbolDeclaration, SymbolKind.Method, SymbolKind.Field, SymbolKind.Property, SymbolKind.Event);
......@@ -118,6 +123,7 @@ private void RegisterActions(CompilationStartAnalysisContext compilationStartCon
symbolStartContext.RegisterOperationAction(AnalyzeMemberReferenceOperation, OperationKind.FieldReference, OperationKind.MethodReference, OperationKind.PropertyReference, OperationKind.EventReference);
symbolStartContext.RegisterOperationAction(AnalyzeFieldInitializer, OperationKind.FieldInitializer);
symbolStartContext.RegisterOperationAction(AnalyzeInvocationOperation, OperationKind.Invocation);
symbolStartContext.RegisterOperationAction(AnalyzeObjectCreationOperation, OperationKind.ObjectCreation);
symbolStartContext.RegisterOperationAction(_ => hasInvalidOperation = true, OperationKind.Invalid);
symbolStartContext.RegisterSymbolEndAction(symbolEndContext => OnSymbolEnd(symbolEndContext, hasInvalidOperation));
}, SymbolKind.NamedType);
......@@ -125,7 +131,8 @@ private void RegisterActions(CompilationStartAnalysisContext compilationStartCon
private void AnalyzeSymbolDeclaration(SymbolAnalysisContext symbolContext)
{
if (IsCandidateSymbol(symbolContext.Symbol))
var symbol = symbolContext.Symbol.OriginalDefinition;
if (IsCandidateSymbol(symbol))
{
lock (_gate)
{
......@@ -134,9 +141,9 @@ private void AnalyzeSymbolDeclaration(SymbolAnalysisContext symbolContext)
// Note that we might receive a symbol reference (AnalyzeMemberOperation) callback before
// this symbol declaration callback, so even though we cannot receive duplicate callbacks for a symbol,
// an entry might already be present of the declared symbol here.
if (!_symbolValueUsageStateMap.ContainsKey(symbolContext.Symbol))
if (!_symbolValueUsageStateMap.ContainsKey(symbol))
{
_symbolValueUsageStateMap.Add(symbolContext.Symbol, ValueUsageInfo.None);
_symbolValueUsageStateMap.Add(symbol, ValueUsageInfo.None);
}
}
}
......@@ -155,7 +162,10 @@ private void AnalyzeFieldInitializer(OperationAnalysisContext operationContext)
{
foreach (var field in initializer.InitializedFields)
{
OnSymbolUsage(field, ValueUsageInfo.Write);
if (IsCandidateSymbol(field))
{
OnSymbolUsage(field, ValueUsageInfo.Write);
}
}
}
}
......@@ -193,7 +203,8 @@ private bool TryRemove(ISymbol memberSymbol, out ValueUsageInfo valueUsageInfo)
private void AnalyzeMemberReferenceOperation(OperationAnalysisContext operationContext)
{
var memberReference = (IMemberReferenceOperation)operationContext.Operation;
if (IsCandidateSymbol(memberReference.Member))
var memberSymbol = memberReference.Member.OriginalDefinition;
if (IsCandidateSymbol(memberSymbol))
{
// Get the value usage info.
var valueUsageInfo = memberReference.GetValueUsageInfo();
......@@ -225,18 +236,29 @@ private void AnalyzeMemberReferenceOperation(OperationAnalysisContext operationC
}
}
OnSymbolUsage(memberReference.Member, valueUsageInfo);
OnSymbolUsage(memberSymbol, valueUsageInfo);
}
}
private void AnalyzeInvocationOperation(OperationAnalysisContext operationContext)
{
var invocation = (IInvocationOperation)operationContext.Operation;
if (IsCandidateSymbol(invocation.TargetMethod))
var targetMethod = ((IInvocationOperation)operationContext.Operation).TargetMethod.OriginalDefinition;
if (IsCandidateSymbol(targetMethod))
{
// A method invocation is considered as a read reference to the symbol
// to ensure that we consider the method as "used".
OnSymbolUsage(invocation.TargetMethod, ValueUsageInfo.Read);
OnSymbolUsage(targetMethod, ValueUsageInfo.Read);
}
}
private void AnalyzeObjectCreationOperation(OperationAnalysisContext operationContext)
{
var constructor = ((IObjectCreationOperation)operationContext.Operation).Constructor.OriginalDefinition;
if (IsCandidateSymbol(constructor))
{
// An object creation is considered as a read reference to the constructor
// to ensure that we consider the constructor as "used".
OnSymbolUsage(constructor, ValueUsageInfo.Read);
}
}
......@@ -283,8 +305,8 @@ private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasInvalid
// Report IDE0051 or IDE0052 based on whether the underlying member has any Write/WritableRef/NonReadWriteRef references or not.
var rule = !valueUsageInfo.ContainsWriteOrWritableRef() && !valueUsageInfo.ContainsNonReadWriteRef() && !symbolsReferencedInDocComments.Contains(member)
? _removeUnusedMembersRule
: _removeUnreadMembersRule;
? s_removeUnusedMembersWithFadingRule
: s_removeUnreadMembersWithFadingRule;
var effectiveSeverity = rule.GetEffectiveSeverity(symbolEndContext.Compilation.Options);
// Most of the members should have a single location, except for partial methods.
......@@ -346,6 +368,8 @@ PooledHashSet<ISymbol> GetCandidateSymbolsReferencedInDocComments(INamedTypeSymb
private bool IsCandidateSymbol(ISymbol memberSymbol)
{
Debug.Assert(memberSymbol == memberSymbol.OriginalDefinition);
if (memberSymbol.DeclaredAccessibility == Accessibility.Private &&
!memberSymbol.IsImplicitlyDeclared)
{
......@@ -358,14 +382,28 @@ private bool IsCandidateSymbol(ISymbol memberSymbol)
// 2. Abstract/Virtual/Override methods
// 3. Extern methods
// 4. Interface implementation methods
// 5. Constructors with no parameters.
// 6. Static constructors.
// 7. Destructors.
var methodSymbol = (IMethodSymbol)memberSymbol;
return methodSymbol.AssociatedSymbol == null &&
!IsEntryPoint(methodSymbol) &&
!methodSymbol.IsAbstract &&
!methodSymbol.IsVirtual &&
!methodSymbol.IsOverride &&
!methodSymbol.IsExtern &&
methodSymbol.ExplicitInterfaceImplementations.IsEmpty;
switch (methodSymbol.MethodKind)
{
case MethodKind.Constructor:
return methodSymbol.Parameters.Length > 0;
case MethodKind.StaticConstructor:
case MethodKind.Destructor:
return false;
default:
return methodSymbol.AssociatedSymbol == null &&
!IsEntryPoint(methodSymbol) &&
!methodSymbol.IsAbstract &&
!methodSymbol.IsVirtual &&
!methodSymbol.IsOverride &&
!methodSymbol.IsExtern &&
methodSymbol.ExplicitInterfaceImplementations.IsEmpty;
}
case SymbolKind.Field:
return ((IFieldSymbol)memberSymbol).AssociatedSymbol == null;
......
......@@ -9,14 +9,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnusedMembers
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend NotInheritable Class VisualBasicRemoveUnusedMembersDiagnosticAnalyzer
Inherits AbstractRemoveUnusedMembersDiagnosticAnalyzer(Of DocumentationCommentTriviaSyntax, IdentifierNameSyntax)
Public Sub New()
MyBase.New(forceEnableRules:=False)
End Sub
' For testing purposes only.
Friend Sub New(forceEnableRules As Boolean)
MyBase.New(forceEnableRules)
End Sub
End Class
End Namespace
......@@ -330,17 +330,13 @@ private static MetadataReference GetOrCreateMetadataReference(ref MetadataRefere
Func<SyntaxNode, bool> syntaxNodePredicate = null,
bool argumentOrderDoesNotMatter = false)
{
Debug.Assert(code is ErrorCode || code is ERRID || code is int || code is string);
return new DiagnosticDescription(
code as string ?? (object)(int)code,
false,
return TestHelpers.Diagnostic(
code,
squiggledText,
arguments,
startLocation,
syntaxNodePredicate,
argumentOrderDoesNotMatter,
code.GetType());
argumentOrderDoesNotMatter);
}
internal static DiagnosticDescription Diagnostic(
......@@ -351,25 +347,15 @@ private static MetadataReference GetOrCreateMetadataReference(ref MetadataRefere
Func<SyntaxNode, bool> syntaxNodePredicate = null,
bool argumentOrderDoesNotMatter = false)
{
return Diagnostic(
return TestHelpers.Diagnostic(
code,
NormalizeNewLines(squiggledText),
squiggledText,
arguments,
startLocation,
syntaxNodePredicate,
argumentOrderDoesNotMatter);
}
public static string NormalizeNewLines(XCData data)
{
if (ExecutionConditionUtil.IsWindows)
{
return data.Value.Replace("\n", "\r\n");
}
return data.Value;
}
#endregion
}
}
......@@ -7,7 +7,10 @@
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
namespace Roslyn.Test.Utilities
{
......@@ -76,5 +79,55 @@ public static string AsXmlCommentText(string text)
return result;
}
internal static DiagnosticDescription Diagnostic(
object code,
string squiggledText = null,
object[] arguments = null,
LinePosition? startLocation = null,
Func<SyntaxNode, bool> syntaxNodePredicate = null,
bool argumentOrderDoesNotMatter = false)
{
Debug.Assert(code is Microsoft.CodeAnalysis.CSharp.ErrorCode ||
code is Microsoft.CodeAnalysis.VisualBasic.ERRID ||
code is int ||
code is string);
return new DiagnosticDescription(
code as string ?? (object)(int)code,
false,
squiggledText,
arguments,
startLocation,
syntaxNodePredicate,
argumentOrderDoesNotMatter,
code.GetType());
}
internal static DiagnosticDescription Diagnostic(
object code,
XCData squiggledText,
object[] arguments = null,
LinePosition? startLocation = null,
Func<SyntaxNode, bool> syntaxNodePredicate = null,
bool argumentOrderDoesNotMatter = false)
{
return Diagnostic(
code,
NormalizeNewLines(squiggledText),
arguments,
startLocation,
syntaxNodePredicate,
argumentOrderDoesNotMatter);
}
public static string NormalizeNewLines(XCData data)
{
if (ExecutionConditionUtil.IsWindows)
{
return data.Value.Replace("\n", "\r\n");
}
return data.Value;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册