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