提交 a6db5441 编写于 作者: M Manish Vasani

Address PR Feedback:

1. Break the analyzer into two separate diagnostics for flagging unused and unread members. Latter rule has no code fix.
2. Remove all UI option elements for the added diagnostics and turn the rules off by default for now.
3. Bail out for invalid operations and syntax errors.
4. Handle doc comments and nameof - report unread member diagostics instead of unused member diagnostics if referenced in non-read/write context.
上级 c1c54cc9
......@@ -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 = Diagnostic.Create(Descriptor, node.GetLocation());
var diagnostic = CodeAnalysis.Diagnostic.Create(Descriptor, node.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
......
......@@ -9,7 +9,6 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.Suppression
......@@ -48,7 +47,7 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
var location = _reportDiagnosticsWithoutLocation ? Location.None : classDecl.Identifier.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(Decsciptor, location));
context.ReportDiagnostic(CodeAnalysis.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(Diagnostic.Create(Decsciptor1, location));
context.ReportDiagnostic(Diagnostic.Create(Decsciptor2, location));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Decsciptor1, location));
context.ReportDiagnostic(CodeAnalysis.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(Diagnostic.Create(Decsciptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.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(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.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(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.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(Diagnostic.Create(Decsciptor, classDecl.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Decsciptor, classDecl.GetLocation()));
}
}
......@@ -760,39 +760,39 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
case SyntaxKind.ClassDeclaration:
var classDecl = (ClassDeclarationSyntax)context.Node;
context.ReportDiagnostic(Diagnostic.Create(Descriptor, classDecl.Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, classDecl.Identifier.GetLocation()));
break;
case SyntaxKind.NamespaceDeclaration:
var ns = (NamespaceDeclarationSyntax)context.Node;
context.ReportDiagnostic(Diagnostic.Create(Descriptor, ns.Name.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, ns.Name.GetLocation()));
break;
case SyntaxKind.MethodDeclaration:
var method = (MethodDeclarationSyntax)context.Node;
context.ReportDiagnostic(Diagnostic.Create(Descriptor, method.Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, method.Identifier.GetLocation()));
break;
case SyntaxKind.PropertyDeclaration:
var property = (PropertyDeclarationSyntax)context.Node;
context.ReportDiagnostic(Diagnostic.Create(Descriptor, property.Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, property.Identifier.GetLocation()));
break;
case SyntaxKind.FieldDeclaration:
var field = (FieldDeclarationSyntax)context.Node;
context.ReportDiagnostic(Diagnostic.Create(Descriptor, field.Declaration.Variables.First().Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, field.Declaration.Variables.First().Identifier.GetLocation()));
break;
case SyntaxKind.EventDeclaration:
var e = (EventDeclarationSyntax)context.Node;
context.ReportDiagnostic(Diagnostic.Create(Descriptor, e.Identifier.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.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(Diagnostic.Create(Descriptor, trivia.GetLocation()));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, trivia.GetLocation()));
}
break;
}
......@@ -1571,7 +1571,7 @@ public override void Initialize(AnalysisContext context)
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.None));
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor, Location.None));
}
}
......
......@@ -5,6 +5,7 @@
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;
......@@ -13,7 +14,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnusedMembers
public class RemoveUnusedMembersTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new CSharpRemoveUnusedMembersDiagnosticAnalyzer(), new CSharpRemoveUnusedMembersCodeFixProvider());
=> (new CSharpRemoveUnusedMembersDiagnosticAnalyzer(forceEnableRules: true),
new CSharpRemoveUnusedMembersCodeFixProvider());
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[InlineData("public")]
......@@ -584,19 +586,19 @@ public void RaiseEvent(EventArgs e)
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldInNameOf()
{
// https://github.com/dotnet/roslyn/issues/29519 tracks flagging and fixing this appropriately.
await TestMissingInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
private string _goo2 = nameof(_goo);
}");
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldInDocComment()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"
/// <summary>
/// <see cref=""C._goo""/>
......@@ -605,19 +607,45 @@ class C
{
private static int [|_goo|];
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(7, 24));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldInDocComment_02()
{
await TestDiagnosticsAsync(
@"
/// <summary>
/// <see cref=""C._goo""/>
/// </summary>
class C
{
}");
/// <summary>
/// <see cref=""_goo""/>
/// </summary>
private static int [|_goo|];
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(7, 24));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldInDocComment_03()
{
await TestDiagnosticsAsync(
@"
class C
{
/// <summary>
/// <see cref=""_goo""/>
/// </summary>
public void M() { }
private static int [|_goo|];
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(9, 24));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsOnlyWritten()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
......@@ -626,19 +654,13 @@ public void M()
_goo = 0;
}
}",
@"class MyClass
{
public void M()
{
_goo = 0;
}
}");
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task PropertyIsOnlyWritten()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|P|] { get; set; }
......@@ -647,19 +669,13 @@ public void M()
P = 0;
}
}",
@"class MyClass
{
public void M()
{
P = 0;
}
}");
expected: Diagnostic("IDE0052", "P").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task IndexerIsOnlyWritten()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|this|][int x] { get { return 0; } set { } }
......@@ -668,13 +684,7 @@ public void M(int x, int y)
this[x] = y;
}
}",
@"class MyClass
{
public void M(int x, int y)
{
this[x] = y;
}
}");
expected: Diagnostic("IDE0052", "this").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
......@@ -695,7 +705,7 @@ public void M()
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsOnlyWritten_Deconstruction()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
......@@ -705,37 +715,26 @@ public void M()
(_goo, x) = (0, 0);
}
}",
@"class MyClass
{
public void M()
{
int x;
(_goo, x) = (0, 0);
}
}");
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsOnlyWritten_ObjectInitializer()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"
class MyClass
{
private int [|_goo|];
public MyClass M() => new MyClass() { _goo = 0 };
}",
@"
class MyClass
{
public MyClass M() => new MyClass() { _goo = 0 };
}");
expected: Diagnostic("IDE0052", "_goo").WithLocation(4, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsOnlyWritten_InProperty()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
......@@ -745,14 +744,7 @@ int Goo
set { _goo = value; }
}
}",
@"class MyClass
{
int Goo
{
get { return 0; }
set { _goo = value; }
}
}");
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
......@@ -816,7 +808,7 @@ int Goo
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsIncremented()
public async Task FieldIsIncrementedAndValueUsed()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
......@@ -827,7 +819,42 @@ public async Task FieldIsIncremented()
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task PropertyIsIncremented()
public async Task FieldIsIncrementedAndValueUsed_02()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
private int [|_goo|];
public int M1() { return ++_goo; }
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsIncrementedAndValueDropped()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public void M1() => ++_goo;
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsIncrementedAndValueDropped_02()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public void M1() { ++_goo; }
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task PropertyIsIncrementedAndValueUsed()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
......@@ -838,7 +865,19 @@ public async Task PropertyIsIncremented()
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task IndexerIsIncremented()
public async Task PropertyIsIncrementedAndValueDropped()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|P|] { get; set; }
public void M1() { ++P; }
}",
expected: Diagnostic("IDE0052", "P").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task IndexerIsIncrementedAndValueUsed()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
......@@ -849,7 +888,19 @@ public async Task IndexerIsIncremented()
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsTargetOfCompoundAssignment()
public async Task IndexerIsIncrementedAndValueDropped()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|this|][int x] { get { return 0; } set { } }
public void M1(int x) => ++this[x];
}",
expected: Diagnostic("IDE0052", "this").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsTargetOfCompoundAssignmentAndValueUsed()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
......@@ -860,7 +911,42 @@ public async Task FieldIsTargetOfCompoundAssignment()
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task PropertyIsTargetOfCompoundAssignment()
public async Task FieldIsTargetOfCompoundAssignmentAndValueUsed_02()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
{
private int [|_goo|];
public int M1(int x) { return _goo += x; }
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsTargetOfCompoundAssignmentAndValueDropped()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public void M1(int x) => _goo += x;
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsTargetOfCompoundAssignmentAndValueDropped_02()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public void M1(int x) { _goo += x; }
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task PropertyIsTargetOfCompoundAssignmentAndValueUsed()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
......@@ -871,7 +957,19 @@ public async Task PropertyIsTargetOfCompoundAssignment()
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task IndexerIsTargetOfCompoundAssignment()
public async Task PropertyIsTargetOfCompoundAssignmentAndValueDropped()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|P|] { get; set; }
public void M1(int x) { P += x; }
}",
expected: Diagnostic("IDE0052", "P").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task IndexerIsTargetOfCompoundAssignmentAndValueUsed()
{
await TestMissingInRegularAndScriptAsync(
@"class MyClass
......@@ -882,35 +980,40 @@ public async Task IndexerIsTargetOfCompoundAssignment()
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsTargetOfAssignmentAndParenthesized()
public async Task IndexerIsTargetOfCompoundAssignmentAndValueDropped()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public void M1(int x) => (_goo) = x;
private int [|this|][int x] { get { return 0; } set { } }
public void M1(int x, int y) => this[x] += y;
}",
expected: Diagnostic("IDE0052", "this").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsTargetOfAssignmentAndParenthesized()
{
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public void M1(int x) => (_goo) = x;
}");
}",
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsTargetOfAssignmentAndHasImplicitConversion()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public static implicit operator int(MyClass c) => 0;
public void M1(MyClass c) => _goo = c;
}",
@"class MyClass
{
public static implicit operator int(MyClass c) => 0;
public void M1(MyClass c) => _goo = c;
}");
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
......@@ -952,18 +1055,14 @@ public async Task FieldIsRefArg()
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsOutArg()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public int M1() => M2(out _goo);
public int M2(out int i) => { i = 0; return i; }
public int M2(out int i) { i = 0; return i; }
}",
@"class MyClass
{
public int M1() => M2(out _goo);
public int M2(out int i) => { i = 0; return i; }
}");
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
......@@ -1186,7 +1285,7 @@ public async Task FieldIsRead_PartialClass_DifferentFile()
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsOnlyWritten_PartialClass_DifferentFile()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
......@@ -1202,20 +1301,7 @@ public async Task FieldIsOnlyWritten_PartialClass_DifferentFile()
</Document>
</Project>
</Workspace>",
@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>partial class MyClass
{
}
</Document>
<Document>partial class MyClass
{
public void M() { _goo = 0; }
}
</Document>
</Project>
</Workspace>");
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
......@@ -1232,16 +1318,13 @@ public async Task FieldIsRead_InParens()
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsWritten_InParens()
{
await TestInRegularAndScriptAsync(
await TestDiagnosticsAsync(
@"class MyClass
{
private int [|_goo|];
public int M() { (_goo) = 1; }
}",
@"class MyClass
{
public int M() { (_goo) = 1; }
}");
expected: Diagnostic("IDE0052", "_goo").WithLocation(3, 17));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
......@@ -1313,6 +1396,56 @@ public async Task FieldUsedInGeneratedCode()
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsUnusedInType_SyntaxError()
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
private int [|i|];
public int M() { return = ; }
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsUnusedInType_SemanticError()
{
await TestMissingInRegularAndScriptAsync(
@"class C
{
private int [|i|];
// 'ii' is undefined.
public int M() => ii;
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FieldIsUnusedInType_SemanticErrorInDifferentType()
{
await TestInRegularAndScriptAsync(
@"class C
{
private int [|i|];
}
class C2
{
// 'ii' is undefined.
public int M() => ii;
}",
@"class C
{
}
class C2
{
// 'ii' is undefined.
public int M() => ii;
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FixAllFields_Document()
{
......@@ -1417,10 +1550,10 @@ public async Task FixAllMembers_Project()
partial class MyClass
{
private int {|FixAllInProject:_f1|}, f2 = 0, f3;
private int {|FixAllInProject:f1|}, f2 = 0, f3;
private void M1() { }
private int P1 => 0;
private int this[x] { get { return 0; } set { } }
private int this[int x] { get { return 0; } set { } }
private event EventHandler e1, e2 = null;
}
......
......@@ -26,7 +26,7 @@
namespace Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions
{
[UseExportProvider]
public abstract class AbstractCodeActionOrUserDiagnosticTest
public abstract class AbstractCodeActionOrUserDiagnosticTest : TestBase
{
public struct TestParameters
{
......
......@@ -18,6 +18,7 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Microsoft.CodeAnalysis.UnitTests.Diagnostics;
......@@ -35,6 +36,16 @@ public abstract partial class AbstractUserDiagnosticTest : AbstractCodeActionOrU
internal abstract Task<IEnumerable<Diagnostic>> GetDiagnosticsAsync(
TestWorkspace workspace, TestParameters parameters);
protected async Task TestDiagnosticsAsync(
string initialMarkup, TestParameters parameters = default, params DiagnosticDescription[] expected)
{
using (var workspace = CreateWorkspaceFromOptions(initialMarkup, parameters))
{
var diagnostics = (await GetDiagnosticsAsync(workspace, parameters).ConfigureAwait(false)).ToImmutableArrayOrEmpty();
DiagnosticExtensions.Verify(diagnostics, expected);
}
}
protected override async Task<(ImmutableArray<CodeAction>, CodeAction actionToInvoke)> GetCodeActionsWorkerAsync(
TestWorkspace workspace, TestParameters parameters)
{
......
......@@ -2,7 +2,6 @@
Option Strict Off
Imports System.Collections.Immutable
Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis.CodeFixes.Suppression
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
......@@ -630,7 +629,7 @@ End Class]]>
Private Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -704,7 +703,7 @@ End Class]]>
Private Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -748,7 +747,7 @@ End Class]]>
Public Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -812,7 +811,7 @@ End Class]]>
Public Sub AnalyzeNode(context As SyntaxNodeAnalysisContext)
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
End Sub
End Class
......@@ -932,32 +931,32 @@ End Class]]>
Select Case context.Node.Kind()
Case SyntaxKind.ClassStatement
Dim classDecl = DirectCast(context.Node, ClassStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, classDecl.Identifier.GetLocation()))
Exit Select
Case SyntaxKind.NamespaceStatement
Dim ns = DirectCast(context.Node, NamespaceStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, ns.Name.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, ns.Name.GetLocation()))
Exit Select
Case SyntaxKind.SubStatement, SyntaxKind.FunctionStatement
Dim method = DirectCast(context.Node, MethodStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, method.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, method.Identifier.GetLocation()))
Exit Select
Case SyntaxKind.PropertyStatement
Dim p = DirectCast(context.Node, PropertyStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, p.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, p.Identifier.GetLocation()))
Exit Select
Case SyntaxKind.FieldDeclaration
Dim f = DirectCast(context.Node, FieldDeclarationSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, f.Declarators.First().Names.First.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, f.Declarators.First().Names.First.GetLocation()))
Exit Select
Case SyntaxKind.EventStatement
Dim e = DirectCast(context.Node, EventStatementSyntax)
context.ReportDiagnostic(Diagnostic.Create(_descriptor, e.Identifier.GetLocation()))
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(_descriptor, e.Identifier.GetLocation()))
Exit Select
End Select
End Sub
......
......@@ -3,6 +3,7 @@
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
......@@ -10,7 +11,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnusedMember
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider)
Return (New VisualBasicRemoveUnusedMembersDiagnosticAnalyzer(),
Return (New VisualBasicRemoveUnusedMembersDiagnosticAnalyzer(forceEnableRules:=True),
New VisualBasicRemoveUnusedMembersCodeFixProvider())
End Function
......@@ -447,67 +448,83 @@ End Class")
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldInNameOf() As Task
' https://github.com/dotnet/roslyn/issues/29519 tracks flagging and fixing this appropriately.
Await TestMissingInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Private [|_goo|] As Integer
Private _goo2 As String = NameOf(_goo)
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(2, 13))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldInDocComment() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"
''' <summary>
''' <see cref=""C._goo""/>
''' </summary>
Class C
Private Shared [|_goo|] As Integer
End Class",
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(6, 20))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldInDocComment_02() As Task
Await TestDiagnosticsAsync(
"
''' <summary>
''' <see cref=""C._goo""/>
''' </summary>
Class C
End Class")
''' <summary>
''' <see cref=""_goo""/>
''' </summary>
Private Shared [|_goo|] As Integer
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(6, 20))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsOnlyWritten() As Task
Await TestInRegularAndScriptAsync(
"Class C
Private [|_goo|] As Integer
Public Async Function FieldInDocComment_03() As Task
Await TestDiagnosticsAsync(
"
Class C
''' <summary>
''' <see cref=""_goo""/>
''' </summary>
Public Sub M()
_goo = 0
End Sub
End Class",
Private Shared [|_goo|] As Integer
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(9, 20))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsOnlyWritten() As Task
Await TestDiagnosticsAsync(
"Class C
Private [|_goo|] As Integer
Public Sub M()
_goo = 0
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(2, 13))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function PropertyIsOnlyWritten() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Private Property [|P|] As Integer
Public Sub M()
P = 0
End Sub
End Class",
"Class C
Public Sub M()
P = 0
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "P").WithLocation(2, 22))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function IndexerIsOnlyWritten() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Private Property [|P|](x As Integer) As Integer
Get
......@@ -519,17 +536,13 @@ End Class")
Public Sub M(x As Integer)
P(x) = 0
End Sub
End Class",
"Class C
Public Sub M(x As Integer)
P(x) = 0
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "P").WithLocation(2, 22))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function EventIsOnlyWritten() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Imports System
Class C
......@@ -545,36 +558,25 @@ Class C
' BC32022: 'Private Event E As EventHandler' is an event, and cannot be called directly. Use a 'RaiseEvent' statement to raise an event.
E = Nothing
End Sub
End Class",
"Imports System
Class C
Public Sub M()
' BC32022: 'Private Event E As EventHandler' is an event, and cannot be called directly. Use a 'RaiseEvent' statement to raise an event.
E = Nothing
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "E").WithLocation(4, 26))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsOnlyWritten_ObjectInitializer() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Private [|_goo|] As Integer
Public Sub M()
Dim x = New C() With { ._goo = 0 }
End Sub
End Class",
"Class C
Public Sub M()
Dim x = New C() With { ._goo = 0 }
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(2, 13))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsOnlyWritten_InProperty() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Private [|_goo|] As Integer
Public Property P As Integer
......@@ -585,17 +587,8 @@ End Class")
_goo = value
End Set
End Property
End Class",
"Class C
Public Property P As Integer
Get
Return 0
End Get
Set
_goo = value
End Set
End Property
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(2, 13))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
......@@ -643,39 +636,31 @@ End Class")
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsTargetOfCompoundAssignment() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Dim [|_goo|] As Integer
Public Sub M()
_goo += 1
End Sub
End Class",
"Class C
Public Sub M()
_goo += 1
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(2, 9))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function PropertyIsTargetOfCompoundAssignment() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Private ReadOnly Property [|P|] As Integer
Public Sub M()
P += 1
End Sub
End Class",
"Class C
Public Sub M()
P += 1
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "P").WithLocation(2, 31))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function IndexerIsTargetOfCompoundAssignment() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"Class C
Private Property [|P|](i As Integer) As Integer
Get
......@@ -688,12 +673,8 @@ End Class")
Public Sub M(x As Integer)
P(x) += 1
End Sub
End Class",
"Class C
Public Sub M(x As Integer)
P(x) += 1
End Sub
End Class")
End Class", parameters:=Nothing,
Diagnostic("IDE0052", "P").WithLocation(2, 22))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
......@@ -935,7 +916,7 @@ End Class
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsOnlyWritten_PartialClass_DifferentFile() As Task
Await TestInRegularAndScriptAsync(
Await TestDiagnosticsAsync(
"<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>
......@@ -951,22 +932,8 @@ Partial Class C
End Class
</Document>
</Project>
</Workspace>",
"<Workspace>
<Project Language=""Visual Basic"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>
Partial Class C
End Class
</Document>
<Document>
Partial Class C
Public Sub M()
_goo = 0
End Sub
End Class
</Document>
</Project>
</Workspace>")
</Workspace>", parameters:=Nothing,
Diagnostic("IDE0052", "_goo").WithLocation(3, 13))
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
......@@ -982,16 +949,59 @@ End Class")
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsWritten_InParens() As Task
Await TestInRegularAndScriptAsync(
Await TestMissingInRegularAndScriptAsync(
"Class C
Private [|_goo|] As Integer
Public Sub M()
' Below is a syntax error, _goo is parsed as skipped trivia
(_goo) = 0
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsUnusedInType_SyntaxError() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private [|_goo|] As Integer
Public Sub M()
Return =
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsUnusedInType_SemanticError() As Task
Await TestMissingInRegularAndScriptAsync(
"Class C
Private [|_goo|] As Integer
Public Sub M()
' _goo2 is undefined
Return _goo2
End Sub
End Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function FieldIsUnusedInType_SemanticErrorInDifferentType() As Task
Await TestInRegularAndScriptAsync(
"Class C
Private [|_goo|] As Integer
End Class
Class C2
Public Sub M()
' _goo2 is undefined
Return _goo2
End Sub
End Class",
"Class C
End Class
Class C2
Public Sub M()
(_goo) = 0
' _goo2 is undefined
Return _goo2
End Sub
End Class")
End Function
......@@ -1047,9 +1057,9 @@ End Class")
Await TestInRegularAndScriptAsync(
"Class C
Private {|FixAllInDocument:_goo|}, _goo2 As Integer, _goo3 = """", _goo4, _goo5 As Char
Private _goo6, _goo7 As Integer, _goo8 = 'c'
Private _goo9, _goo10 As New String
Private _goo11, _goo12 As New String
Private _goo6, _goo7 As Integer, _goo8 = 0
Private _goo9, _goo10 As New String("""")
Private _goo11, _goo12 As New String("""")
Private _goo13 = 0
Public Sub M()
......@@ -1059,7 +1069,7 @@ End Class")
End Class",
"Class C
Private _goo4 As Char
Private _goo11 As New String
Private _goo11 As New String("""")
Public Sub M()
Dim x = _goo4
......
// 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 Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.RemoveUnusedMembers;
namespace Microsoft.CodeAnalysis.CSharp.RemoveUnusedMembers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class CSharpRemoveUnusedMembersDiagnosticAnalyzer : AbstractRemoveUnusedMembersDiagnosticAnalyzer
internal class CSharpRemoveUnusedMembersDiagnosticAnalyzer
: AbstractRemoveUnusedMembersDiagnosticAnalyzer<DocumentationCommentTriviaSyntax, IdentifierNameSyntax>
{
public CSharpRemoveUnusedMembersDiagnosticAnalyzer()
: base(forceEnableRules: false)
{
}
internal CSharpRemoveUnusedMembersDiagnosticAnalyzer(bool forceEnableRules)
: base(forceEnableRules)
{
}
}
}
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
......@@ -57,15 +58,23 @@ internal abstract class AbstractCodeStyleDiagnosticAnalyzer : DiagnosticAnalyzer
Descriptor, UnnecessaryWithoutSuggestionDescriptor, UnnecessaryWithSuggestionDescriptor);
}
protected AbstractCodeStyleDiagnosticAnalyzer(ImmutableArray<DiagnosticDescriptor> diagnosticDescriptors)
{
Debug.Assert(diagnosticDescriptors.Length > 0);
SupportedDiagnostics = diagnosticDescriptors;
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
protected DiagnosticDescriptor CreateUnnecessaryDescriptor()
=> CreateUnnecessaryDescriptor(DescriptorId);
protected DiagnosticDescriptor CreateUnnecessaryDescriptor(string descriptorId)
=> CreateDescriptorWithId(
descriptorId, _localizableTitle, _localizableMessageFormat,
DiagnosticCustomTags.Unnecessary);
=> CreateUnnecessaryDescriptor(descriptorId, _localizableTitle, _localizableMessageFormat, _configurable);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
protected static DiagnosticDescriptor CreateUnnecessaryDescriptor(
string id, LocalizableString title, LocalizableString messageFormat, bool configurable, bool enabledByDefault = true)
=> CreateDescriptor(id, title, messageFormat, configurable, enabledByDefault, DiagnosticCustomTags.Unnecessary);
protected DiagnosticDescriptor CreateDescriptor(params string[] customTags)
=> CreateDescriptorWithId(DescriptorId, _localizableTitle, _localizableMessageFormat, customTags);
......@@ -76,8 +85,13 @@ protected DiagnosticDescriptor CreateDescriptorWithTitle(LocalizableString title
protected DiagnosticDescriptor CreateDescriptorWithId(
string id, LocalizableString title, LocalizableString messageFormat,
params string[] customTags)
=> CreateDescriptor(id, title, messageFormat, _configurable, customTags: customTags);
protected static DiagnosticDescriptor CreateDescriptor(
string id, LocalizableString title, LocalizableString messageFormat,
bool configurable, bool enabledByDefault = true, params string[] customTags)
{
if (!_configurable)
if (!configurable)
{
customTags = customTags.Concat(WellKnownDiagnosticTags.NotConfigurable).ToArray();
}
......@@ -86,7 +100,7 @@ protected DiagnosticDescriptor CreateDescriptorWithTitle(LocalizableString title
id, title, messageFormat,
DiagnosticCategory.Style,
DiagnosticSeverity.Hidden,
isEnabledByDefault: true,
isEnabledByDefault: enabledByDefault,
customTags: customTags);
}
......
......@@ -80,6 +80,7 @@ internal static class IDEDiagnosticIds
public const string ConvertAnonymousTypeToTupleDiagnosticId = "IDE0050";
public const string RemoveUnusedMembersDiagnosticId = "IDE0051";
public const string RemoveUnreadMembersDiagnosticId = "IDE0052";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
......
......@@ -2787,6 +2787,24 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Remove unread private members.
/// </summary>
internal static string Remove_Unread_Private_Members {
get {
return ResourceManager.GetString("Remove_Unread_Private_Members", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; has a private member &apos;{1}&apos; that can be removed as the value assigned to it is never read..
/// </summary>
internal static string Remove_Unread_Private_Members_Message {
get {
return ResourceManager.GetString("Remove_Unread_Private_Members_Message", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove unused member.
/// </summary>
......@@ -2806,7 +2824,7 @@ internal class FeaturesResources {
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; has a private member &apos;{1}&apos; which can be removed as it is either never used or the value assigned to it is never used..
/// Looks up a localized string similar to Type &apos;{0}&apos; has an unused private member &apos;{1}&apos; which can be removed..
/// </summary>
internal static string Remove_Unused_Private_Members_Message {
get {
......
......@@ -1410,9 +1410,15 @@ This version used in: {2}</value>
<value>Remove unused member</value>
</data>
<data name="Remove_Unused_Private_Members_Message" xml:space="preserve">
<value>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</value>
<value>Type '{0}' has an unused private member '{1}' which can be removed.</value>
</data>
<data name="Remove_Unused_Private_Members" xml:space="preserve">
<value>Remove unused private members</value>
</data>
<data name="Remove_Unread_Private_Members" xml:space="preserve">
<value>Remove unread private members</value>
</data>
<data name="Remove_Unread_Private_Members_Message" xml:space="preserve">
<value>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</value>
</data>
</root>
\ No newline at end of file
......@@ -214,7 +214,7 @@ private static bool IsInAnonymousFunctionOrLocalFunction(IOperation operation)
private static CodeStyleOption<bool> GetCodeStyleOption(IFieldSymbol field, AnalyzerOptions options, CancellationToken cancellationToken)
{
var optionSet = options.GetDocumentOptionSetAsync(field.Locations[0].SourceTree, cancellationToken).GetAwaiter().GetResult();
return optionSet?.GetOption(CodeStyleOptions.RemoveUnusedMembers, field.Language);
return optionSet?.GetOption(CodeStyleOptions.PreferReadonly, field.Language);
}
private static bool IsMutableValueType(ITypeSymbol type)
......
// 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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
......@@ -10,24 +9,46 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.RemoveUnusedMembers
{
internal abstract class AbstractRemoveUnusedMembersDiagnosticAnalyzer
internal abstract class AbstractRemoveUnusedMembersDiagnosticAnalyzer<TDocumentationCommentTriviaSyntax, TIdentifierNameSyntax>
: AbstractCodeStyleDiagnosticAnalyzer
where TDocumentationCommentTriviaSyntax: SyntaxNode
where TIdentifierNameSyntax : SyntaxNode
{
private readonly bool _treatCompoundAssignmentAsWriteOnlyOperation;
protected AbstractRemoveUnusedMembersDiagnosticAnalyzer(bool forceEnableRules)
: base(CreateDescriptors(forceEnableRules))
{
}
public AbstractRemoveUnusedMembersDiagnosticAnalyzer(bool treatCompoundAssignmentAsWriteOnlyOperation = false)
: base(
IDEDiagnosticIds.RemoveUnusedMembersDiagnosticId,
new LocalizableResourceString(nameof(FeaturesResources.Remove_Unused_Private_Members), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
new LocalizableResourceString(nameof(FeaturesResources.Remove_Unused_Private_Members_Message), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
private static ImmutableArray<DiagnosticDescriptor> CreateDescriptors(bool forceEnableRules)
{
_treatCompoundAssignmentAsWriteOnlyOperation = treatCompoundAssignmentAsWriteOnlyOperation;
// TODO: Enable these rules by default once we have designed the Tools|Option location and UI for such code quality rules.
// IDE0051: "Remove unused members" (Symbol is declared but never referenced)
var removeUnusedMembersTitle = new LocalizableResourceString(nameof(FeaturesResources.Remove_Unused_Private_Members), FeaturesResources.ResourceManager, typeof(FeaturesResources));
var removeUnusedMembersMessage = new LocalizableResourceString(nameof(FeaturesResources.Remove_Unused_Private_Members_Message), 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);
// 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.Remove_Unread_Private_Members_Message), 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);
}
private DiagnosticDescriptor RemoveUnusedMemberRule => SupportedDiagnostics[1];
private DiagnosticDescriptor RemoveUnreadMemberRule => SupportedDiagnostics[3];
public override bool OpenFileOnly(Workspace workspace) => false;
// We need to analyze the whole document even for edits within a method body.
......@@ -40,38 +61,40 @@ protected override void InitializeWorker(AnalysisContext context)
context.RegisterCompilationStartAction(compilationStartContext =>
{
var compilationAnalyzer = new CompilationAnalyzer(compilationStartContext.Compilation, UnnecessaryWithSuggestionDescriptor, _treatCompoundAssignmentAsWriteOnlyOperation);
var compilationAnalyzer = new CompilationAnalyzer(compilationStartContext.Compilation, RemoveUnusedMemberRule, RemoveUnreadMemberRule);
// 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.
// 3. A symbol start/end action for named types to report diagnostics for candidate members that have no usage in executable code.
// 3. Operation action for invalid operations to bail out on erroneous code.
// 4. 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
compilationStartContext.RegisterSymbolAction(compilationAnalyzer.AnalyzeSymbolDeclaration, SymbolKind.Method, SymbolKind.Field, SymbolKind.Property, SymbolKind.Event);
compilationStartContext.RegisterSymbolStartAction(symbolStartContext =>
{
var hasInvalidOperation = false;
symbolStartContext.RegisterOperationAction(compilationAnalyzer.AnalyzeMemberReferenceOperation, OperationKind.FieldReference, OperationKind.MethodReference, OperationKind.PropertyReference, OperationKind.EventReference);
symbolStartContext.RegisterOperationAction(compilationAnalyzer.AnalyzeInvocationOperation, OperationKind.Invocation);
symbolStartContext.RegisterSymbolEndAction(compilationAnalyzer.OnSymbolEnd);
symbolStartContext.RegisterOperationAction(_ => hasInvalidOperation = true, OperationKind.Invalid);
symbolStartContext.RegisterSymbolEndAction(symbolEndContext => compilationAnalyzer.OnSymbolEnd(symbolEndContext, hasInvalidOperation));
}, SymbolKind.NamedType);
});
}
private sealed class CompilationAnalyzer
{
private readonly DiagnosticDescriptor _rule;
private readonly bool _treatCompoundAssignmentAsWriteOnlyOperation;
private readonly DiagnosticDescriptor _removeUnusedMembersRule, _removeUnreadMembersRule;
private readonly object _gate;
private readonly Dictionary<ISymbol, ValueUsageInfo> _symbolValueUsageStateMap;
private readonly Lazy<INamedTypeSymbol> _lazyTaskType, _lazyGenericTaskType;
public CompilationAnalyzer(Compilation compilation, DiagnosticDescriptor rule, bool treatCompoundAssignmentAsWriteOnlyOperation)
public CompilationAnalyzer(Compilation compilation, DiagnosticDescriptor removeUnusedMembersRule, DiagnosticDescriptor removeUnreadMembersRule)
{
_rule = rule;
_treatCompoundAssignmentAsWriteOnlyOperation = treatCompoundAssignmentAsWriteOnlyOperation;
_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.
......@@ -137,20 +160,18 @@ public void AnalyzeMemberReferenceOperation(OperationAnalysisContext operationCo
// Get the value usage info.
var valueUsageInfo = memberReference.GetValueUsageInfo();
// Usages which are neither value read nor value write (e.g. in nameof, typeof, sizeof)
// are treated as a read to avoid flagging them.
// https://github.com/dotnet/roslyn/issues/29519 covers improving this behavior.
if (valueUsageInfo == ValueUsageInfo.None)
if (valueUsageInfo == ValueUsageInfo.ReadWrite)
{
valueUsageInfo = ValueUsageInfo.Read;
}
Debug.Assert(memberReference.Parent is ICompoundAssignmentOperation compoundAssignment &&
compoundAssignment.Target == memberReference ||
memberReference.Parent is IIncrementOrDecrementOperation);
// Is this a compound assignment that must be treated as a write-only usage?
if (_treatCompoundAssignmentAsWriteOnlyOperation &&
memberReference.Parent is ICompoundAssignmentOperation compoundAssignment &&
compoundAssignment.Target == memberReference)
{
valueUsageInfo = ValueUsageInfo.Write;
// Compound assignment or increment whose value is being dropped (parent has null type)
// is treated as a Write as the value was never actually 'read' in a way that is observable.
if (memberReference.Parent.Parent?.Type == null)
{
valueUsageInfo = ValueUsageInfo.Write;
}
}
OnSymbolUsage(memberReference.Member, valueUsageInfo);
......@@ -166,9 +187,15 @@ public void AnalyzeInvocationOperation(OperationAnalysisContext operationContext
}
}
public void OnSymbolEnd(SymbolAnalysisContext symbolEndContext)
public void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasInvalidOperation)
{
if (hasInvalidOperation)
{
return;
}
// Report diagnostics for unused candidate members.
ImmutableHashSet<ISymbol> symbolsReferencedInDocComments = null;
var members = ((INamedTypeSymbol)symbolEndContext.Symbol).GetMembers();
foreach (var member in members)
{
......@@ -179,20 +206,68 @@ public void OnSymbolEnd(SymbolAnalysisContext symbolEndContext)
Debug.Assert(IsCandidateSymbol(member));
Debug.Assert(!member.IsImplicitlyDeclared);
var option = TryGetCodeStyleOption(member, symbolEndContext);
if (option != null && option.Value)
if (symbolsReferencedInDocComments == null)
{
// Bail out if there are syntax errors in any of the declarations of the containing type.
if (HasSyntaxErrors())
{
return;
}
symbolsReferencedInDocComments = GetCandidateSymbolsReferencedInDocComments();
}
// 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;
var effectiveSeverity = rule.GetEffectiveSeverity(symbolEndContext.Compilation.Options);
var diagnostic = DiagnosticHelper.Create(
rule,
member.Locations[0],
effectiveSeverity,
additionalLocations: member.Locations,
properties: null,
member.ContainingType.Name,
member.Name);
symbolEndContext.ReportDiagnostic(diagnostic);
}
}
bool HasSyntaxErrors()
{
foreach (var tree in symbolEndContext.Symbol.Locations.Select(l => l.SourceTree))
{
if (tree.GetDiagnostics(symbolEndContext.CancellationToken).Any(d => d.Severity == DiagnosticSeverity.Error))
{
var diagnostic = DiagnosticHelper.Create(
_rule,
member.Locations[0],
option.Notification.Severity,
additionalLocations: member.Locations,
properties: null,
member.ContainingType.Name,
member.Name);
symbolEndContext.ReportDiagnostic(diagnostic);
return true;
}
}
return false;
}
ImmutableHashSet<ISymbol> GetCandidateSymbolsReferencedInDocComments()
{
var builder = ImmutableHashSet.CreateBuilder<ISymbol>();
foreach (var root in symbolEndContext.Symbol.Locations.Select(l => l.SourceTree.GetRoot(symbolEndContext.CancellationToken)))
{
SemanticModel lazyModel = null;
foreach (var node in root.DescendantNodes(descendIntoTrivia: true)
.OfType<TDocumentationCommentTriviaSyntax>()
.SelectMany(n => n.DescendantNodes().OfType<TIdentifierNameSyntax>()))
{
lazyModel = lazyModel ?? symbolEndContext.Compilation.GetSemanticModel(root.SyntaxTree);
var symbol = lazyModel.GetSymbolInfo(node, symbolEndContext.CancellationToken).Symbol;
if (symbol != null && IsCandidateSymbol(symbol))
{
builder.Add(symbol);
}
}
}
return builder.ToImmutable();
}
}
......@@ -217,7 +292,7 @@ private bool IsCandidateSymbol(ISymbol memberSymbol)
}
}
return false;
return false;
// Local functions.
bool IsEntryPoint(IMethodSymbol methodSymbol)
......@@ -243,12 +318,6 @@ bool IsEntryPoint(IMethodSymbol methodSymbol)
return false;
}
}
private static CodeStyleOption<bool> TryGetCodeStyleOption(ISymbol memberSymbol, SymbolAnalysisContext symbolEndContext)
{
var optionSet = symbolEndContext.Options.GetDocumentOptionSetAsync(memberSymbol.Locations[0].SourceTree, symbolEndContext.CancellationToken).GetAwaiter().GetResult();
return optionSet?.GetOption(CodeStyleOptions.RemoveUnusedMembers, memberSymbol.Language);
}
}
}
}
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -67,14 +67,24 @@
<target state="new">Related method signatures found in metadata will not be updated.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members">
<source>Remove unread private members</source>
<target state="new">Remove unread private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unread_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</source>
<target state="new">Type '{0}' has a private member '{1}' that can be removed as the value assigned to it is never read.</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members">
<source>Remove unused private members</source>
<target state="new">Remove unused private members</target>
<note />
</trans-unit>
<trans-unit id="Remove_Unused_Private_Members_Message">
<source>Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</source>
<target state="new">Type '{0}' has a private member '{1}' which can be removed as it is either never used or the value assigned to it is never used.</target>
<source>Type '{0}' has an unused private member '{1}' which can be removed.</source>
<target state="new">Type '{0}' has an unused private member '{1}' which can be removed.</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_member">
......
......@@ -2,17 +2,20 @@
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.RemoveUnusedMembers
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnusedMembers
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend NotInheritable Class VisualBasicRemoveUnusedMembersDiagnosticAnalyzer
Inherits AbstractRemoveUnusedMembersDiagnosticAnalyzer
Inherits AbstractRemoveUnusedMembersDiagnosticAnalyzer(Of DocumentationCommentTriviaSyntax, IdentifierNameSyntax)
Public Sub New()
' Compound assigment is a statement in VB that does not return a value.
' So, we treat it as a write-only usage.
MyBase.New(treatCompoundAssignmentAsWriteOnlyOperation:=True)
MyBase.New(forceEnableRules:=False)
End Sub
Public Sub New(forceEnableRules As Boolean)
MyBase.New(forceEnableRules)
End Sub
End Class
End Namespace
......@@ -725,12 +725,6 @@ public string Style_PreferReadonly
set { SetXmlOption(CodeStyleOptions.PreferReadonly, value); }
}
public string Style_RemoveUnusedMembers
{
get { return GetXmlOption(CodeStyleOptions.RemoveUnusedMembers); }
set { SetXmlOption(CodeStyleOptions.RemoveUnusedMembers, value); }
}
public int Wrapping_IgnoreSpacesAroundBinaryOperators
{
get
......
......@@ -1109,7 +1109,7 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) :
var predefinedTypesGroupTitle = CSharpVSResources.predefined_type_preferences_colon;
var varGroupTitle = CSharpVSResources.var_preferences_colon;
var nullCheckingGroupTitle = CSharpVSResources.null_checking_colon;
var memberGroupTitle = ServicesVSResources.Member_preferences_colon;
var fieldGroupTitle = ServicesVSResources.Field_preferences_colon;
var codeBlockPreferencesGroupTitle = ServicesVSResources.Code_block_preferences_colon;
var expressionPreferencesGroupTitle = ServicesVSResources.Expression_preferences_colon;
var variablePreferencesGroupTitle = ServicesVSResources.Variable_preferences_colon;
......@@ -1177,9 +1177,8 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) :
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferNullPropagation, ServicesVSResources.Prefer_null_propagation, s_preferNullPropagation, s_preferNullPropagation, this, optionSet, nullCheckingGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferIsNullCheckOverReferenceEqualityMethod, CSharpVSResources.Prefer_is_null_for_reference_equality_checks, s_preferIsNullOverReferenceEquals, s_preferIsNullOverReferenceEquals, this, optionSet, nullCheckingGroupTitle));
// Member preferences (field/method/property/event).
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferReadonly, ServicesVSResources.Prefer_readonly_fields, s_preferReadonly, s_preferReadonly, this, optionSet, memberGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.RemoveUnusedMembers, ServicesVSResources.Remove_unused_members, s_removeUnusedMembers, s_removeUnusedMembers, this, optionSet, memberGroupTitle));
// Field preferences.
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferReadonly, ServicesVSResources.Prefer_readonly, s_preferReadonly, s_preferReadonly, this, optionSet, fieldGroupTitle));
}
private void AddParenthesesOptions(OptionSet optionSet)
......
......@@ -952,6 +952,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Field preferences:.
/// </summary>
internal static string Field_preferences_colon {
get {
return ResourceManager.GetString("Field_preferences_colon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to File already exists.
/// </summary>
......@@ -1316,15 +1325,6 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Member preferences:.
/// </summary>
internal static string Member_preferences_colon {
get {
return ResourceManager.GetString("Member_preferences_colon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Miscellaneous Files.
/// </summary>
......@@ -1875,6 +1875,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Prefer_readonly.
/// </summary>
internal static string Prefer_readonly {
get {
return ResourceManager.GetString("Prefer_readonly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prefer readonly fields.
/// </summary>
......@@ -2115,15 +2124,6 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Remove unused members.
/// </summary>
internal static string Remove_unused_members {
get {
return ResourceManager.GetString("Remove_unused_members", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reorder.
/// </summary>
......
......@@ -1048,10 +1048,10 @@ I agree to all of the foregoing:</value>
<data name="We_notice_you_suspended_0_Reset_keymappings_to_continue_to_navigate_and_refactor" xml:space="preserve">
<value>We notice you suspended '{0}'. Reset keymappings to continue to navigate and refactor.</value>
</data>
<data name="Remove_unused_members" xml:space="preserve">
<value>Remove unused members</value>
<data name="Prefer_readonly" xml:space="preserve">
<value>Prefer_readonly</value>
</data>
<data name="Member_preferences_colon" xml:space="preserve">
<value>Member preferences:</value>
<data name="Field_preferences_colon" xml:space="preserve">
<value>Field preferences:</value>
</data>
</root>
\ No newline at end of file
......@@ -17,6 +17,11 @@
<target state="translated">Element není platný.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">Das Element ist ungültig.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">El elemento no es válido.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">L'élément n'est pas valide.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">L'elemento non è valido.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">要素が有効ではありません。</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">요소가 잘못되었습니다.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">Element jest nieprawidłowy.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">O elemento é inválido.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">Элемент недопустим.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">Öğe geçerli değil.</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">元素无效。</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -17,6 +17,11 @@
<target state="translated">元素無效。</target>
<note />
</trans-unit>
<trans-unit id="Field_preferences_colon">
<source>Field preferences:</source>
<target state="new">Field preferences:</target>
<note />
</trans-unit>
<trans-unit id="In_other_operators">
<source>In other operators</source>
<target state="new">In other operators</target>
......@@ -27,11 +32,6 @@
<target state="new">Keep all parentheses in:</target>
<note />
</trans-unit>
<trans-unit id="Member_preferences_colon">
<source>Member preferences:</source>
<target state="new">Member preferences:</target>
<note />
</trans-unit>
<trans-unit id="Never_if_unnecessary">
<source>Never if unnecessary</source>
<target state="new">Never if unnecessary</target>
......@@ -42,16 +42,16 @@
<target state="new">Parentheses preferences:</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly">
<source>Prefer_readonly</source>
<target state="new">Prefer_readonly</target>
<note />
</trans-unit>
<trans-unit id="Prefer_readonly_fields">
<source>Prefer readonly fields</source>
<target state="new">Prefer readonly fields</target>
<note />
</trans-unit>
<trans-unit id="Remove_unused_members">
<source>Remove unused members</source>
<target state="new">Remove unused members</target>
<note />
</trans-unit>
<trans-unit id="Reset_Visual_Studio_default_keymapping">
<source>Reset Visual Studio default keymapping</source>
<target state="new">Reset Visual Studio default keymapping</target>
......
......@@ -288,15 +288,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
End Set
End Property
Public Property Style_RemoveUnusedMembers As String
Get
Return GetXmlOption(CodeStyleOptions.RemoveUnusedMembers)
End Get
Set(value As String)
SetXmlOption(CodeStyleOptions.RemoveUnusedMembers, value)
End Set
End Property
Public Property Option_PlaceSystemNamespaceFirst As Boolean
Get
Return GetBooleanOption(GenerationOptions.PlaceSystemNamespaceFirst)
......
......@@ -558,7 +558,7 @@ End Class"
Dim codeBlockPreferencesGroupTitle = ServicesVSResources.Code_block_preferences_colon
Dim expressionPreferencesGroupTitle = ServicesVSResources.Expression_preferences_colon
Dim nothingPreferencesGroupTitle = BasicVSResources.nothing_checking_colon
Dim memberPreferencesGroupTitle = ServicesVSResources.Member_preferences_colon
Dim fieldPreferencesGroupTitle = ServicesVSResources.Field_preferences_colon
' qualify with Me. group
Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.QualifyFieldAccess, BasicVSResources.Qualify_field_access_with_Me, s_fieldDeclarationPreviewTrue, s_fieldDeclarationPreviewFalse, Me, optionSet, qualifyGroupTitle, qualifyMemberAccessPreferences))
......@@ -588,9 +588,8 @@ End Class"
Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferNullPropagation, ServicesVSResources.Prefer_null_propagation, s_preferNullPropagation, s_preferNullPropagation, Me, optionSet, nothingPreferencesGroupTitle))
Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferIsNullCheckOverReferenceEqualityMethod, BasicVSResources.Prefer_Is_Nothing_for_reference_equality_checks, s_preferIsNothingCheckOverReferenceEquals, s_preferIsNothingCheckOverReferenceEquals, Me, optionSet, nothingPreferencesGroupTitle))
' Member preferences (field/method/property/event).
Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferReadonly, ServicesVSResources.Prefer_readonly_fields, s_preferReadonly, s_preferReadonly, Me, optionSet, memberPreferencesGroupTitle))
Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.RemoveUnusedMembers, ServicesVSResources.Remove_unused_members, s_removeUnusedMembers, s_removeUnusedMembers, Me, optionSet, memberPreferencesGroupTitle))
' field preferences
Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferReadonly, ServicesVSResources.Prefer_readonly, s_preferReadonly, s_preferReadonly, Me, optionSet, fieldPreferencesGroupTitle))
End Sub
Private Sub AddParenthesesOptions(optionSet As OptionSet)
......
......@@ -208,14 +208,6 @@ public class CodeStyleOptions
EditorConfigStorageLocation.ForBoolCodeStyleOption("dotnet_style_readonly_field"),
new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferReadonly") });
internal static readonly PerLanguageOption<CodeStyleOption<bool>> RemoveUnusedMembers = new PerLanguageOption<CodeStyleOption<bool>>(
nameof(CodeStyleOptions),
nameof(RemoveUnusedMembers),
defaultValue: TrueWithSuggestionEnforcement,
storageLocations: new OptionStorageLocation[]{
EditorConfigStorageLocation.ForBoolCodeStyleOption("dotnet_style_remove_unused_member"),
new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.RemoveUnusedMembers") });
private static CodeStyleOption<AccessibilityModifiersRequired> ParseAccessibilityModifiersRequired(string optionString)
{
if (TryGetCodeStyleValueAndOptionalNotification(optionString,
......
......@@ -242,7 +242,6 @@ protected void WriteOptionSetTo(OptionSet options, string language, ObjectWriter
WriteOptionTo(options, language, CodeStyleOptions.PreferInferredTupleNames, writer, cancellationToken);
WriteOptionTo(options, language, CodeStyleOptions.PreferInferredAnonymousTypeMemberNames, writer, cancellationToken);
WriteOptionTo(options, language, CodeStyleOptions.PreferReadonly, writer, cancellationToken);
WriteOptionTo(options, language, CodeStyleOptions.RemoveUnusedMembers, writer, cancellationToken);
}
protected OptionSet ReadOptionSetFrom(OptionSet options, string language, ObjectReader reader, CancellationToken cancellationToken)
......@@ -273,7 +272,6 @@ protected OptionSet ReadOptionSetFrom(OptionSet options, string language, Object
options = ReadOptionFrom(options, language, CodeStyleOptions.PreferInferredTupleNames, reader, cancellationToken);
options = ReadOptionFrom(options, language, CodeStyleOptions.PreferInferredAnonymousTypeMemberNames, reader, cancellationToken);
options = ReadOptionFrom(options, language, CodeStyleOptions.PreferReadonly, reader, cancellationToken);
options = ReadOptionFrom(options, language, CodeStyleOptions.RemoveUnusedMembers, reader, cancellationToken);
return options;
}
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -27,16 +28,18 @@ public static bool IsTargetOfObjectMemberInitializer(this IOperation operation)
public static ValueUsageInfo GetValueUsageInfo(this IOperation operation)
{
/*
| code | Read | Write | ReadableRef | WritableRef |
| nameof(x) | | | | |
| x.Prop = 1 | | ✔️ | | |
| x.Prop += 1 | ✔️ | ✔️ | | |
| x.Prop++ | ✔️ | ✔️ | | |
| Foo(x.Prop) | ✔️ | | | |
| Foo(x.Prop), | | | ✔️ | |
| code | Read | Write | ReadableRef | WritableRef | NonReadWriteRef |
| x.Prop = 1 | | ✔️ | | | |
| x.Prop += 1 | ✔️ | ✔️ | | | |
| x.Prop++ | ✔️ | ✔️ | | | |
| Foo(x.Prop) | ✔️ | | | | |
| Foo(x.Prop), | | | ✔️ | | |
where void Foo(in T v)
| Foo(out x.Prop) | | | | ✔️ |
| Foo(ref x.Prop) | | | ✔️ | ✔️ |
| Foo(out x.Prop) | | | | ✔️ | |
| Foo(ref x.Prop) | | | ✔️ | ✔️ | |
| nameof(x) | | | | | ✔ | ️
| sizeof(x) | | | | | ✔ | ️
| typeof(x) | | | | | ✔ | ️
*/
......@@ -53,13 +56,17 @@ public static ValueUsageInfo GetValueUsageInfo(this IOperation operation)
}
else if (operation.Parent is IParenthesizedOperation parenthesizedOperation)
{
return parenthesizedOperation.GetValueUsageInfo();
// Note: IParenthesizedOperation is specific to VB, where the parens cause a copy, so this cannot be classified as a write.
Debug.Assert(parenthesizedOperation.Language == LanguageNames.VisualBasic);
return parenthesizedOperation.GetValueUsageInfo() &
~(ValueUsageInfo.Write | ValueUsageInfo.WritableRef);
}
else if (operation.Parent is INameOfOperation ||
operation.Parent is ITypeOfOperation ||
operation.Parent is ISizeOfOperation)
{
return ValueUsageInfo.None;
return ValueUsageInfo.NonReadWriteRef;
}
else if (operation.Parent is IArgumentOperation argumentOperation)
{
......
......@@ -7,11 +7,12 @@ namespace Microsoft.CodeAnalysis
[Flags]
internal enum ValueUsageInfo
{
None = 0x0000,
Read = 0x0001,
Write = 0x0010,
ReadableRef = 0x0100,
WritableRef = 0x1000,
None = 0x00000,
Read = 0x00001,
Write = 0x00010,
ReadableRef = 0x00100,
WritableRef = 0x01000,
NonReadWriteRef = 0x10000,
ReadWrite = Read | Write,
ReadableWritableRef = ReadableRef | WritableRef
......@@ -24,5 +25,8 @@ public static bool ContainsReadOrReadableRef(this ValueUsageInfo valueUsageInfo)
public static bool ContainsWriteOrWritableRef(this ValueUsageInfo valueUsageInfo)
=> (valueUsageInfo & (ValueUsageInfo.Write | ValueUsageInfo.WritableRef)) != 0;
public static bool ContainsNonReadWriteRef(this ValueUsageInfo valueUsageInfo)
=> (valueUsageInfo & ValueUsageInfo.NonReadWriteRef) != 0;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册