Skip to content

Commit 6285950

Browse files
committed
Implemented SA1133 (incl. tests + codefix)
1 parent 3cb375e commit 6285950

7 files changed

Lines changed: 240 additions & 3 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.ReadabilityRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Composition;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Helpers;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CodeActions;
14+
using Microsoft.CodeAnalysis.CodeFixes;
15+
using Microsoft.CodeAnalysis.CSharp;
16+
using Microsoft.CodeAnalysis.CSharp.Syntax;
17+
18+
/// <summary>
19+
/// Implements a code fix for <see cref="SA1133DoNotCombineAttributes"/>.
20+
/// </summary>
21+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1133CodeFixProvider))]
22+
[Shared]
23+
internal class SA1133CodeFixProvider : CodeFixProvider
24+
{
25+
/// <inheritdoc/>
26+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
27+
ImmutableArray.Create(SA1133DoNotCombineAttributes.DiagnosticId);
28+
29+
/// <inheritdoc/>
30+
public override FixAllProvider GetFixAllProvider()
31+
{
32+
return CustomFixAllProviders.BatchFixer;
33+
}
34+
35+
/// <inheritdoc/>
36+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
37+
{
38+
foreach (var diagnostic in context.Diagnostics)
39+
{
40+
context.RegisterCodeFix(
41+
CodeAction.Create(
42+
ReadabilityResources.SA1133CodeFix,
43+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
44+
nameof(SA1133CodeFixProvider)),
45+
diagnostic);
46+
}
47+
48+
return SpecializedTasks.CompletedTask;
49+
}
50+
51+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
52+
{
53+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
54+
var violatingAttribute = (AttributeSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan);
55+
var attributeList = (AttributeListSyntax)violatingAttribute.Parent;
56+
var newAttributeLists = new List<AttributeListSyntax>();
57+
58+
var indentationOptions = IndentationOptions.FromDocument(document);
59+
var indentationSteps = IndentationHelper.GetIndentationSteps(indentationOptions, attributeList);
60+
var indentationTrivia = IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, indentationSteps);
61+
62+
for (var i = 0; i < attributeList.Attributes.Count; i++)
63+
{
64+
var newAttributes = SyntaxFactory.SeparatedList(new[] { attributeList.Attributes[i] });
65+
var newAttributeList = SyntaxFactory.AttributeList(attributeList.Target, newAttributes);
66+
67+
newAttributeList = (i == 0)
68+
? newAttributeList.WithLeadingTrivia(attributeList.GetLeadingTrivia())
69+
: newAttributeList.WithLeadingTrivia(indentationTrivia);
70+
71+
newAttributeList = (i == (attributeList.Attributes.Count - 1))
72+
? newAttributeList.WithTrailingTrivia(attributeList.GetTrailingTrivia())
73+
: newAttributeList.WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed);
74+
75+
newAttributeLists.Add(newAttributeList);
76+
}
77+
78+
var newSyntaxRoot = syntaxRoot.ReplaceNode(attributeList, newAttributeLists);
79+
var newDocument = document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
80+
81+
return newDocument;
82+
}
83+
}
84+
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
<Compile Include="ReadabilityRules\SA1129CodeFixProvider.cs" />
128128
<Compile Include="ReadabilityRules\SA1131CodeFixProvider.cs" />
129129
<Compile Include="ReadabilityRules\SA1132CodeFixProvider.cs" />
130+
<Compile Include="ReadabilityRules\SA1133CodeFixProvider.cs" />
130131
<Compile Include="ReadabilityRules\SX1101CodeFixProvider.cs" />
131132
<Compile Include="Settings\SettingsFileCodeFixProvider.cs" />
132133
<Compile Include="SpacingRules\SA1003CodeFixProvider.cs" />
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.ReadabilityRules
5+
{
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.CodeAnalysis.CodeFixes;
11+
using Microsoft.CodeAnalysis.Diagnostics;
12+
using StyleCop.Analyzers.ReadabilityRules;
13+
using TestHelper;
14+
using Xunit;
15+
16+
/// <summary>
17+
/// This class contains unit tests for the <see cref="SA1133DoNotCombineAttributes"/> class.
18+
/// </summary>
19+
public class SA1133UnitTests : CodeFixVerifier
20+
{
21+
/// <summary>
22+
/// Verifies that a single attribute will not produce a diagnostic.
23+
/// </summary>
24+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
25+
[Fact]
26+
public async Task VerifyThatSingleAttributesDoNotProduceDiagnosticAsync()
27+
{
28+
var testCode = @"using System.ComponentModel;
29+
30+
[EditorBrowsable(EditorBrowsableState.Never)]
31+
public class TestClass
32+
{
33+
}
34+
";
35+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
36+
}
37+
38+
/// <summary>
39+
/// Verifies that a multiple attributes on the same line will not produce a diagnostic.
40+
/// </summary>
41+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
42+
[Fact]
43+
public async Task VerifyThatMultipleAttributesOnSameLineDoNotProduceDiagnosticAsync()
44+
{
45+
var testCode = @"using System.ComponentModel;
46+
47+
[EditorBrowsable(EditorBrowsableState.Never)][DesignOnly(true)]
48+
public class TestClass
49+
{
50+
}
51+
";
52+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
53+
}
54+
55+
/// <summary>
56+
/// Verifies that an attribute list will produce the required diagnostics.
57+
/// </summary>
58+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
59+
[Fact]
60+
public async Task VerifyThatAttributeListProducesDiagnosticAsync()
61+
{
62+
var testCode = @"using System.ComponentModel;
63+
64+
[EditorBrowsable(EditorBrowsableState.Never), DesignOnly(true)]
65+
public class TestClass
66+
{
67+
/// <summary>
68+
/// Test method.
69+
/// </summary>
70+
[EditorBrowsable(EditorBrowsableState.Never), DesignOnly(true), DisplayName(""Test"")] // test comment
71+
public void TestMethod()
72+
{
73+
}
74+
}
75+
";
76+
77+
var fixedTestCode = @"using System.ComponentModel;
78+
79+
[EditorBrowsable(EditorBrowsableState.Never)]
80+
[DesignOnly(true)]
81+
public class TestClass
82+
{
83+
/// <summary>
84+
/// Test method.
85+
/// </summary>
86+
[EditorBrowsable(EditorBrowsableState.Never)]
87+
[DesignOnly(true)]
88+
[DisplayName(""Test"")] // test comment
89+
public void TestMethod()
90+
{
91+
}
92+
}
93+
";
94+
95+
DiagnosticResult[] expected =
96+
{
97+
this.CSharpDiagnostic().WithLocation(3, 47),
98+
this.CSharpDiagnostic().WithLocation(9, 51)
99+
};
100+
101+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
102+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
103+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
104+
}
105+
106+
/// <inheritdoc/>
107+
protected override CodeFixProvider GetCSharpCodeFixProvider()
108+
{
109+
return new SA1133CodeFixProvider();
110+
}
111+
112+
/// <inheritdoc/>
113+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
114+
{
115+
yield return new SA1133DoNotCombineAttributes();
116+
}
117+
}
118+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@
323323
<Compile Include="ReadabilityRules\SA1130UnitTests.cs" />
324324
<Compile Include="ReadabilityRules\SA1131UnitTests.cs" />
325325
<Compile Include="ReadabilityRules\SA1132UnitTests.cs" />
326+
<Compile Include="ReadabilityRules\SA1133UnitTests.cs" />
326327
<Compile Include="ReadabilityRules\SX1101UnitTests.cs" />
327328
<Compile Include="Settings\SettingsFileCodeFixProviderUnitTests.cs" />
328329
<Compile Include="Settings\SettingsUnitTests.cs" />

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,14 @@
459459
<data name="SA1132Title" xml:space="preserve">
460460
<value>Do not combine fields</value>
461461
</data>
462+
<data name="SA1133CodeFix" xml:space="preserve">
463+
<value>Give each attribute its own square brackets</value>
464+
</data>
462465
<data name="SA1133Description" xml:space="preserve">
463466
<value>Each attribute usage should be placed in its own set of square brackets for maximum readability.</value>
464467
</data>
465468
<data name="SA1133MessageFormat" xml:space="preserve">
466-
<value />
469+
<value>Each attribute should be placed in its own set of square brackets.</value>
467470
</data>
468471
<data name="SA1133Title" xml:space="preserve">
469472
<value>Do not combine attributes</value>

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1133DoNotCombineAttributes.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
namespace StyleCop.Analyzers.ReadabilityRules
55
{
6+
using System;
67
using System.Collections.Immutable;
78
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
using Microsoft.CodeAnalysis.CSharp.Syntax;
811
using Microsoft.CodeAnalysis.Diagnostics;
912

1013
/// <summary>
@@ -25,14 +28,32 @@ internal class SA1133DoNotCombineAttributes : DiagnosticAnalyzer
2528
private static readonly DiagnosticDescriptor Descriptor =
2629
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.DisabledNoTests, Description, HelpLink);
2730

31+
private static readonly Action<CompilationStartAnalysisContext> CompilationStartAction = HandleCompilationStart;
32+
private static readonly Action<SyntaxNodeAnalysisContext> HandleAttributeListAction = HandleAttributeList;
33+
2834
/// <inheritdoc/>
2935
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
3036
ImmutableArray.Create(Descriptor);
3137

3238
/// <inheritdoc/>
3339
public override void Initialize(AnalysisContext context)
3440
{
35-
// TODO: Implement analysis
41+
context.RegisterCompilationStartAction(CompilationStartAction);
42+
}
43+
44+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
45+
{
46+
context.RegisterSyntaxNodeActionHonorExclusions(HandleAttributeListAction, SyntaxKind.AttributeList);
47+
}
48+
49+
private static void HandleAttributeList(SyntaxNodeAnalysisContext context)
50+
{
51+
AttributeListSyntax attributeList = (AttributeListSyntax)context.Node;
52+
53+
if (attributeList.Attributes.Count > 1)
54+
{
55+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, attributeList.Attributes[1].GetLocation()));
56+
}
3657
}
3758
}
3859
}

0 commit comments

Comments
 (0)