Skip to content

Commit 2cd1db3

Browse files
committed
Add support for suppressed analyzers in element order code fix
1 parent 4de65c5 commit 2cd1db3

3 files changed

Lines changed: 156 additions & 38 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1203UnitTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
namespace StyleCop.Analyzers.Test.OrderingRules
55
{
66
using System.Collections.Generic;
7+
using System.Collections.Immutable;
78
using System.Threading;
89
using System.Threading.Tasks;
10+
using Microsoft.CodeAnalysis;
911
using Microsoft.CodeAnalysis.CodeFixes;
1012
using Microsoft.CodeAnalysis.Diagnostics;
1113
using StyleCop.Analyzers.OrderingRules;
@@ -14,6 +16,8 @@ namespace StyleCop.Analyzers.Test.OrderingRules
1416

1517
public class SA1203UnitTests : CodeFixVerifier
1618
{
19+
private bool suppressSA1202 = false;
20+
1721
[Fact]
1822
public async Task TestNoDiagnosticAsync()
1923
{
@@ -382,6 +386,57 @@ public async Task TestOnlyLeadingWhitespaceIsMovedAsync()
382386
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
383387
}
384388

389+
/// <summary>
390+
/// Verifies that the code fix will move the non constant fields before the constant ones with SA1202 suppressed.
391+
/// </summary>
392+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
393+
[Fact]
394+
public async Task TestCodeFixWithSA1202SuppressedAsync()
395+
{
396+
this.suppressSA1202 = true;
397+
398+
var testCode = @"public class Foo
399+
{
400+
private const string Before1 = ""test"";
401+
402+
public const string Before2 = ""test"";
403+
404+
private int field1;
405+
406+
private const string After1 = ""test"";
407+
408+
public int between;
409+
410+
public const string After2 = ""test"";
411+
}
412+
";
413+
414+
var fixedTestCode = @"public class Foo
415+
{
416+
private const string Before1 = ""test"";
417+
418+
public const string Before2 = ""test"";
419+
420+
private const string After1 = ""test"";
421+
422+
public const string After2 = ""test"";
423+
424+
private int field1;
425+
426+
public int between;
427+
}
428+
";
429+
430+
var diagnosticResults = new[]
431+
{
432+
this.CSharpDiagnostic().WithLocation(9, 26).WithArguments("private"),
433+
this.CSharpDiagnostic().WithLocation(13, 25).WithArguments("public")
434+
};
435+
await this.VerifyCSharpDiagnosticAsync(testCode, diagnosticResults, CancellationToken.None).ConfigureAwait(false);
436+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
437+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
438+
}
439+
385440
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
386441
{
387442
yield return new SA1203ConstantsMustAppearBeforeFields();
@@ -391,5 +446,20 @@ protected override CodeFixProvider GetCSharpCodeFixProvider()
391446
{
392447
return new ElementOrderCodeFixProvider();
393448
}
449+
450+
protected override Solution CreateSolution(ProjectId projectId, string language)
451+
{
452+
Solution solution = base.CreateSolution(projectId, language);
453+
if (this.suppressSA1202)
454+
{
455+
Project project = solution.GetProject(projectId);
456+
CompilationOptions options = project.CompilationOptions;
457+
ImmutableDictionary<string, ReportDiagnostic> specificOptions = options.SpecificDiagnosticOptions;
458+
options = options.WithSpecificDiagnosticOptions(specificOptions.Add(SA1202ElementsMustBeOrderedByAccess.DiagnosticId, ReportDiagnostic.Suppress));
459+
solution = solution.WithProjectCompilationOptions(projectId, options);
460+
}
461+
462+
return solution;
463+
}
394464
}
395465
}

StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/ElementOrderCodeFixProvider.cs

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ public class ElementOrderCodeFixProvider : CodeFixProvider
2828
SA1214StaticReadonlyElementsMustAppearBeforeStaticNonReadonlyElements.DiagnosticId,
2929
SA1215InstanceReadonlyElementsMustAppearBeforeInstanceNonReadonlyElements.DiagnosticId);
3030

31+
private static bool checkElementType;
32+
private static bool checkAccessLevel;
33+
private static bool checkConst;
34+
private static bool checkStatic;
35+
private static bool checkReadonly;
36+
3137
/// <inheritdoc/>
3238
public override ImmutableArray<string> FixableDiagnosticIds => FixableDiagnostics;
3339

@@ -40,6 +46,18 @@ public override FixAllProvider GetFixAllProvider()
4046
/// <inheritdoc/>
4147
public override Task RegisterCodeFixesAsync(CodeFixContext context)
4248
{
49+
SemanticModel semanticModel;
50+
context.Document.TryGetSemanticModel(out semanticModel);
51+
if (semanticModel != null)
52+
{
53+
checkElementType = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1201ElementsMustAppearInTheCorrectOrder.DiagnosticId);
54+
checkAccessLevel = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1202ElementsMustBeOrderedByAccess.DiagnosticId);
55+
checkConst = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1203ConstantsMustAppearBeforeFields.DiagnosticId);
56+
checkStatic = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1204StaticElementsMustAppearBeforeInstanceElements.DiagnosticId);
57+
checkReadonly = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1214StaticReadonlyElementsMustAppearBeforeStaticNonReadonlyElements.DiagnosticId)
58+
|| !semanticModel.Compilation.IsAnalyzerSuppressed(SA1215InstanceReadonlyElementsMustAppearBeforeInstanceNonReadonlyElements.DiagnosticId);
59+
}
60+
4361
foreach (Diagnostic diagnostic in context.Diagnostics.Where(d => FixableDiagnostics.Contains(d.Id)))
4462
{
4563
context.RegisterCodeFix(CodeAction.Create(OrderingResources.ElementOrderCodeFix, token => GetTransformedDocumentAsync(context.Document, diagnostic, token), equivalenceKey: nameof(ElementOrderCodeFixProvider)), diagnostic);
@@ -66,48 +84,47 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
6684
private static SyntaxNode UpdateSyntaxRoot(MemberDeclarationSyntax memberDeclaration, SyntaxNode syntaxRoot)
6785
{
6886
var parentDeclaration = memberDeclaration.Parent;
87+
var memberToMove = new MemberOrderHelper(memberDeclaration, checkElementType, checkAccessLevel, checkConst, checkStatic, checkReadonly);
6988

7089
if (parentDeclaration is TypeDeclarationSyntax)
7190
{
72-
return HandleTypeDeclaration(memberDeclaration, (TypeDeclarationSyntax)parentDeclaration, syntaxRoot);
91+
return HandleTypeDeclaration(memberToMove, (TypeDeclarationSyntax)parentDeclaration, syntaxRoot);
7392
}
7493

7594
if (parentDeclaration is NamespaceDeclarationSyntax)
7695
{
77-
return HandleNamespaceDeclaration(memberDeclaration, (NamespaceDeclarationSyntax)parentDeclaration, syntaxRoot);
96+
return HandleNamespaceDeclaration(memberToMove, (NamespaceDeclarationSyntax)parentDeclaration, syntaxRoot);
7897
}
7998

8099
if (parentDeclaration is CompilationUnitSyntax)
81100
{
82-
return HandleCompilationUnitDeclaration(memberDeclaration, (CompilationUnitSyntax)parentDeclaration, syntaxRoot);
101+
return HandleCompilationUnitDeclaration(memberToMove, (CompilationUnitSyntax)parentDeclaration, syntaxRoot);
83102
}
84103

85104
return syntaxRoot;
86105
}
87106

88-
private static SyntaxNode HandleTypeDeclaration(MemberDeclarationSyntax memberDeclaration, TypeDeclarationSyntax typeDeclarationNode, SyntaxNode syntaxRoot)
107+
private static SyntaxNode HandleTypeDeclaration(MemberOrderHelper memberOrder, TypeDeclarationSyntax typeDeclarationNode, SyntaxNode syntaxRoot)
89108
{
90-
var memberToMove = new MemberOrderHelper(memberDeclaration);
91-
return MoveMember(memberToMove, typeDeclarationNode.Members, syntaxRoot);
109+
return MoveMember(memberOrder, typeDeclarationNode.Members, syntaxRoot);
92110
}
93111

94-
private static SyntaxNode HandleCompilationUnitDeclaration(MemberDeclarationSyntax memberDeclaration, CompilationUnitSyntax compilationUnitDeclaration, SyntaxNode syntaxRoot)
112+
private static SyntaxNode HandleCompilationUnitDeclaration(MemberOrderHelper memberOrder, CompilationUnitSyntax compilationUnitDeclaration, SyntaxNode syntaxRoot)
95113
{
96-
var memberToMove = new MemberOrderHelper(memberDeclaration);
97-
return MoveMember(memberToMove, compilationUnitDeclaration.Members, syntaxRoot);
114+
return MoveMember(memberOrder, compilationUnitDeclaration.Members, syntaxRoot);
98115
}
99116

100-
private static SyntaxNode HandleNamespaceDeclaration(MemberDeclarationSyntax memberDeclaration, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxNode syntaxRoot)
117+
private static SyntaxNode HandleNamespaceDeclaration(MemberOrderHelper memberOrder, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxNode syntaxRoot)
101118
{
102-
var memberToMove = new MemberOrderHelper(memberDeclaration);
103-
return MoveMember(memberToMove, namespaceDeclaration.Members, syntaxRoot);
119+
return MoveMember(memberOrder, namespaceDeclaration.Members, syntaxRoot);
104120
}
105121

106122
private static SyntaxNode MoveMember(MemberOrderHelper memberOrder, SyntaxList<MemberDeclarationSyntax> members, SyntaxNode syntaxRoot)
107123
{
108124
foreach (var member in members)
109125
{
110-
var orderHelper = new MemberOrderHelper(member);
126+
var orderHelper = new MemberOrderHelper(member, checkElementType, checkAccessLevel, checkConst, checkStatic, checkReadonly);
127+
111128
if (orderHelper.Priority < memberOrder.Priority)
112129
{
113130
syntaxRoot = MoveMember(syntaxRoot, memberOrder.Member, member);
@@ -142,6 +159,23 @@ private class FixAll : DocumentBasedFixAllProvider
142159
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
143160
{
144161
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
162+
if (diagnostics.IsEmpty)
163+
{
164+
return null;
165+
}
166+
167+
SemanticModel semanticModel;
168+
document.TryGetSemanticModel(out semanticModel);
169+
if (semanticModel != null)
170+
{
171+
checkElementType = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1201ElementsMustAppearInTheCorrectOrder.DiagnosticId);
172+
checkAccessLevel = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1202ElementsMustBeOrderedByAccess.DiagnosticId);
173+
checkConst = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1203ConstantsMustAppearBeforeFields.DiagnosticId);
174+
checkStatic = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1204StaticElementsMustAppearBeforeInstanceElements.DiagnosticId);
175+
checkReadonly = !semanticModel.Compilation.IsAnalyzerSuppressed(SA1214StaticReadonlyElementsMustAppearBeforeStaticNonReadonlyElements.DiagnosticId)
176+
|| !semanticModel.Compilation.IsAnalyzerSuppressed(SA1215InstanceReadonlyElementsMustAppearBeforeInstanceNonReadonlyElements.DiagnosticId);
177+
}
178+
145179
var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false);
146180

147181
foreach (var diagnostic in diagnostics)

StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/MemberOrderHelper.cs

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,42 +35,56 @@ internal struct MemberOrderHelper
3535
private readonly ModifierFlags modifierFlags;
3636
private readonly AccessLevel accessibilty;
3737
private readonly int elementPriority;
38+
private readonly bool prioritizeAccess;
3839

3940
/// <summary>
4041
/// Initializes a new instance of the <see cref="MemberOrderHelper"/> struct.
4142
/// </summary>
4243
/// <param name="member">The member to wrap.</param>
43-
public MemberOrderHelper(MemberDeclarationSyntax member)
44+
/// <param name="prioritizeType">Indicates whether to prioritize element type.</param>
45+
/// <param name="prioritizeAccess">Indicates whether to prioritize access level.</param>
46+
/// <param name="prioritizeConst">Indicates whether to prioritize constants.</param>
47+
/// <param name="prioritizeStatic">Indicates whether to prioritize static elements.</param>
48+
/// <param name="prioritizeReadonly">Indicates whether to prioritize readonly elements.</param>
49+
public MemberOrderHelper(MemberDeclarationSyntax member, bool prioritizeType = true, bool prioritizeAccess = true, bool prioritizeConst = true, bool prioritizeStatic = true, bool prioritizeReadonly = true)
4450
{
4551
this.Member = member;
52+
this.prioritizeAccess = prioritizeAccess;
4653
var modifiers = member.GetModifiers();
4754
var type = member.Kind();
4855
type = type == SyntaxKind.EventFieldDeclaration ? SyntaxKind.EventDeclaration : type;
4956

50-
this.elementPriority = TypeMemberOrder.IndexOf(type);
51-
this.modifierFlags = GetModifierFlags(modifiers);
52-
if ((type == SyntaxKind.ConstructorDeclaration && this.modifierFlags.HasFlag(ModifierFlags.Static))
53-
|| (type == SyntaxKind.MethodDeclaration && (member as MethodDeclarationSyntax)?.ExplicitInterfaceSpecifier != null)
54-
|| (type == SyntaxKind.PropertyDeclaration && (member as PropertyDeclarationSyntax)?.ExplicitInterfaceSpecifier != null)
55-
|| (type == SyntaxKind.IndexerDeclaration && (member as IndexerDeclarationSyntax)?.ExplicitInterfaceSpecifier != null))
57+
this.elementPriority = prioritizeType ? TypeMemberOrder.IndexOf(type) : 0;
58+
this.modifierFlags = GetModifierFlags(modifiers, prioritizeConst, prioritizeStatic, prioritizeReadonly);
59+
if (prioritizeAccess)
5660
{
57-
this.accessibilty = AccessLevel.Public;
58-
}
59-
else
60-
{
61-
this.accessibilty = AccessLevelHelper.GetAccessLevel(modifiers);
62-
if (this.accessibilty == AccessLevel.NotSpecified)
61+
if ((type == SyntaxKind.ConstructorDeclaration && this.modifierFlags.HasFlag(ModifierFlags.Static))
62+
|| (type == SyntaxKind.MethodDeclaration && (member as MethodDeclarationSyntax)?.ExplicitInterfaceSpecifier != null)
63+
|| (type == SyntaxKind.PropertyDeclaration && (member as PropertyDeclarationSyntax)?.ExplicitInterfaceSpecifier != null)
64+
|| (type == SyntaxKind.IndexerDeclaration && (member as IndexerDeclarationSyntax)?.ExplicitInterfaceSpecifier != null))
6365
{
64-
if (member.Parent.IsKind(SyntaxKind.CompilationUnit) || member.Parent.IsKind(SyntaxKind.NamespaceDeclaration))
65-
{
66-
this.accessibilty = AccessLevel.Internal;
67-
}
68-
else
66+
this.accessibilty = AccessLevel.Public;
67+
}
68+
else
69+
{
70+
this.accessibilty = AccessLevelHelper.GetAccessLevel(modifiers);
71+
if (this.accessibilty == AccessLevel.NotSpecified)
6972
{
70-
this.accessibilty = AccessLevel.Private;
73+
if (member.Parent.IsKind(SyntaxKind.CompilationUnit) || member.Parent.IsKind(SyntaxKind.NamespaceDeclaration))
74+
{
75+
this.accessibilty = AccessLevel.Internal;
76+
}
77+
else
78+
{
79+
this.accessibilty = AccessLevel.Private;
80+
}
7181
}
7282
}
7383
}
84+
else
85+
{
86+
this.accessibilty = AccessLevel.Public;
87+
}
7488
}
7589

7690
[Flags]
@@ -142,27 +156,27 @@ public int Priority
142156
/// </value>
143157
public int ModifierPriority => (int)this.modifierFlags;
144158

145-
private static ModifierFlags GetModifierFlags(SyntaxTokenList syntax)
159+
private static ModifierFlags GetModifierFlags(SyntaxTokenList syntax, bool prioritizeConst, bool prioritizeStatic, bool prioritizeReadonly)
146160
{
147-
ModifierFlags flags = 0;
148-
if (syntax.Any(SyntaxKind.ConstKeyword))
161+
var flags = ModifierFlags.None;
162+
if (prioritizeConst && syntax.Any(SyntaxKind.ConstKeyword))
149163
{
150164
flags |= ModifierFlags.Const;
151165
}
152166
else
153167
{
154-
if (syntax.Any(SyntaxKind.StaticKeyword))
168+
if (prioritizeStatic && syntax.Any(SyntaxKind.StaticKeyword))
155169
{
156170
flags |= ModifierFlags.Static;
157171
}
158172

159-
if (syntax.Any(SyntaxKind.ReadOnlyKeyword))
173+
if (prioritizeReadonly && syntax.Any(SyntaxKind.ReadOnlyKeyword))
160174
{
161175
flags |= ModifierFlags.Readonly;
162176
}
163177
}
164178

165-
return flags == 0 ? ModifierFlags.None : flags;
179+
return flags;
166180
}
167181
}
168182
}

0 commit comments

Comments
 (0)