Skip to content

Commit

Permalink
Merge pull request #905 from Maxwe11/SA1024-codefix
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed May 31, 2015
2 parents ddc123d + d9b012e commit 5aef39c
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using StyleCop.Analyzers.SpacingRules;
using TestHelper;
Expand All @@ -11,8 +12,34 @@
/// <summary>
/// Unit tests for <see cref="SA1024ColonsMustBeSpacedCorrectly"/>
/// </summary>
public class SA1024UnitTests : DiagnosticVerifier
public class SA1024UnitTests : CodeFixVerifier
{
private const string ExpectedCode = @"using System;
public class Foo<T> : object where T : IFormattable
{
public Foo()/* test */ : base()
{
}
public Foo(int x) : this()
{
Bar(value: x > 2 ? 2 : 3);
}
private int Bar(int value)
{
_label:
switch (value)
{
case 2:
case 3:
return value;
default:
goto _label;
}
}
}";

/// <summary>
/// Verifies that the analyzer will properly handle an empty source.
/// </summary>
Expand Down Expand Up @@ -112,7 +139,7 @@ public async Task TestInvalidSpacedColonsMustBeFollowedAsync()
public class Foo<T> :object where T :IFormattable
{
public Foo() :base()
public Foo()/* test */ :base()
{
}
public Foo(int x) :this()
Expand All @@ -136,14 +163,15 @@ private int Bar(int value)

DiagnosticResult[] expected =
{
this.CSharpDiagnostic().WithLocation(3, 21).WithArguments(string.Empty, "followed"),
this.CSharpDiagnostic().WithLocation(3, 37).WithArguments(string.Empty, "followed"),
this.CSharpDiagnostic().WithLocation(5, 18).WithArguments(string.Empty, "followed"),
this.CSharpDiagnostic().WithLocation(8, 23).WithArguments(string.Empty, "followed"),
this.CSharpDiagnostic().WithLocation(10, 30).WithArguments(string.Empty, "followed"),
this.CSharpDiagnostic().WithLocation(3, 21).WithArguments(string.Empty, "followed", string.Empty),
this.CSharpDiagnostic().WithLocation(3, 37).WithArguments(string.Empty, "followed", string.Empty),
this.CSharpDiagnostic().WithLocation(5, 28).WithArguments(string.Empty, "followed", string.Empty),
this.CSharpDiagnostic().WithLocation(8, 23).WithArguments(string.Empty, "followed", string.Empty),
this.CSharpDiagnostic().WithLocation(10, 30).WithArguments(string.Empty, "followed", string.Empty),
};

await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, ExpectedCode).ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -181,14 +209,15 @@ private int Bar(int value)

DiagnosticResult[] expected =
{
this.CSharpDiagnostic().WithLocation(3, 20).WithArguments(string.Empty, "preceded"),
this.CSharpDiagnostic().WithLocation(3, 36).WithArguments(string.Empty, "preceded"),
this.CSharpDiagnostic().WithLocation(5, 27).WithArguments(string.Empty, "preceded"),
this.CSharpDiagnostic().WithLocation(8, 22).WithArguments(string.Empty, "preceded"),
this.CSharpDiagnostic().WithLocation(10, 29).WithArguments(string.Empty, "preceded"),
this.CSharpDiagnostic().WithLocation(3, 20).WithArguments(string.Empty, "preceded", string.Empty),
this.CSharpDiagnostic().WithLocation(3, 36).WithArguments(string.Empty, "preceded", string.Empty),
this.CSharpDiagnostic().WithLocation(5, 27).WithArguments(string.Empty, "preceded", string.Empty),
this.CSharpDiagnostic().WithLocation(8, 22).WithArguments(string.Empty, "preceded", string.Empty),
this.CSharpDiagnostic().WithLocation(10, 29).WithArguments(string.Empty, "preceded", string.Empty),
};

await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, ExpectedCode).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -202,7 +231,7 @@ public async Task TestInvalidSpacedColonsMustNotBePrecededAsync()
public class Foo<T> : object where T : IFormattable
{
public Foo() : base()
public Foo()/* test */ : base()
{
}
public Foo(int x) : this()
Expand All @@ -226,19 +255,72 @@ private int Bar(int value)

DiagnosticResult[] expected =
{
this.CSharpDiagnostic().WithLocation(10, 19).WithArguments(" not", "preceded"),
this.CSharpDiagnostic().WithLocation(15, 12).WithArguments(" not", "preceded"),
this.CSharpDiagnostic().WithLocation(19, 20).WithArguments(" not", "preceded"),
this.CSharpDiagnostic().WithLocation(21, 21).WithArguments(" not", "preceded"),
this.CSharpDiagnostic().WithLocation(10, 19).WithArguments(" not", "preceded", string.Empty),
this.CSharpDiagnostic().WithLocation(15, 12).WithArguments(" not", "preceded", string.Empty),
this.CSharpDiagnostic().WithLocation(19, 20).WithArguments(" not", "preceded", string.Empty),
this.CSharpDiagnostic().WithLocation(21, 21).WithArguments(" not", "preceded", string.Empty),
};

await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, ExpectedCode).ConfigureAwait(false);
}

/// <summary>
/// Verifies that the analyzer will produce the proper diagnostics when the colons not preceded and not followed by space.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task TestInvalidSpacedColonsMustBePrecededAndFollowedAsync()
{
const string testCode = @"using System;
public class Foo<T>:object where T:IFormattable
{
public Foo()/* test */:base()
{
}
public Foo(int x):this()
{
Bar(value: x > 2 ? 2:3);
}
private int Bar(int value)
{
_label:
switch (value)
{
case 2:
case 3:
return value;
default:
goto _label;
}
}
}";

DiagnosticResult[] expected =
{
this.CSharpDiagnostic().WithLocation(3, 20).WithArguments(string.Empty, "preceded", " and followed"),
this.CSharpDiagnostic().WithLocation(3, 35).WithArguments(string.Empty, "preceded", " and followed"),
this.CSharpDiagnostic().WithLocation(5, 27).WithArguments(string.Empty, "preceded", " and followed"),
this.CSharpDiagnostic().WithLocation(8, 22).WithArguments(string.Empty, "preceded", " and followed"),
this.CSharpDiagnostic().WithLocation(10, 29).WithArguments(string.Empty, "preceded", " and followed"),
};

await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, ExpectedCode).ConfigureAwait(false);
}

/// <inheritdoc/>
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
{
yield return new SA1024ColonsMustBeSpacedCorrectly();
}

/// <inheritdoc/>
protected override CodeFixProvider GetCSharpCodeFixProvider()
{
return new SA1024CodeFixProvider();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
namespace StyleCop.Analyzers.SpacingRules
{
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;

/// <summary>
/// Implements a code fix for <see cref="SA1024ColonsMustBeSpacedCorrectly"/>.
/// </summary>
/// <remarks>
/// <para>To fix a violation of this rule, ensure that the spacing around the colon follows the rule.</para>
/// </remarks>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1024CodeFixProvider))]
[Shared]
public class SA1024CodeFixProvider : CodeFixProvider
{
private static readonly ImmutableArray<string> FixableDiagnostics =
ImmutableArray.Create(SA1024ColonsMustBeSpacedCorrectly.DiagnosticId);

/// <inheritdoc/>
public override ImmutableArray<string> FixableDiagnosticIds => FixableDiagnostics;

/// <inheritdoc/>
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

/// <inheritdoc/>
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
if (!diagnostic.Id.Equals(SA1024ColonsMustBeSpacedCorrectly.DiagnosticId))
{
continue;
}

context.RegisterCodeFix(CodeAction.Create(SpacingResources.SA1024CodeFix,
cancellation => GetTransformedDocumentAsync(context.Document, diagnostic.Location.SourceSpan.Start, cancellation)),
diagnostic);
}

return Task.FromResult(true);
}

private static async Task<Document> GetTransformedDocumentAsync(Document document, int position, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
SyntaxToken token = root.FindToken(position);
bool requireBefore = true;

switch (token.Parent.Kind())
{
case SyntaxKind.LabeledStatement:
case SyntaxKind.CaseSwitchLabel:
case SyntaxKind.DefaultSwitchLabel:
// NameColon is not explicitly listed in the description of this warning, but the behavior is inferred
case SyntaxKind.NameColon:
requireBefore = false;
break;
}

// check for a following space
bool missingFollowingSpace = true;
if (token.HasTrailingTrivia)
{
if (token.TrailingTrivia.First().IsKind(SyntaxKind.WhitespaceTrivia))
{
missingFollowingSpace = false;
}
else if (token.TrailingTrivia.First().IsKind(SyntaxKind.EndOfLineTrivia))
{
missingFollowingSpace = false;
}
}

bool hasPrecedingSpace = token.HasLeadingTrivia;
if (!hasPrecedingSpace)
{
// only the first token on the line has leading trivia, and those are ignored
SyntaxToken precedingToken = token.GetPreviousToken();
SyntaxTriviaList combinedTrivia = precedingToken.TrailingTrivia.AddRange(token.LeadingTrivia);
if (combinedTrivia.Count > 0 && !combinedTrivia.Last().IsKind(SyntaxKind.MultiLineCommentTrivia))
{
hasPrecedingSpace = true;
}
}

if (missingFollowingSpace && requireBefore && !hasPrecedingSpace)
{
SyntaxTrivia whitespace = SyntaxFactory.Space;
SyntaxToken corrected = token.WithTrailingTrivia(token.TrailingTrivia.Insert(0, whitespace))
.WithLeadingTrivia(token.LeadingTrivia.Add(whitespace));
Document updatedDocument = document.WithSyntaxRoot(root.ReplaceToken(token, corrected));
return updatedDocument;
}

if (missingFollowingSpace)
{
SyntaxTrivia whitespace = SyntaxFactory.Space;
SyntaxToken corrected = token.WithTrailingTrivia(token.TrailingTrivia.Insert(0, whitespace));
Document updatedDocument = document.WithSyntaxRoot(root.ReplaceToken(token, corrected));
return updatedDocument;
}

if (hasPrecedingSpace != requireBefore)
{
SyntaxToken corrected;

if (requireBefore)
{
corrected = token.WithLeadingTrivia(token.LeadingTrivia.Add(SyntaxFactory.Space));
}
else
{
token = token.GetPreviousToken();
corrected = token.WithoutTrailingWhitespace();
}

Document updatedDocument = document.WithSyntaxRoot(root.ReplaceToken(token, corrected));
return updatedDocument;
}

return document;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class SA1024ColonsMustBeSpacedCorrectly : DiagnosticAnalyzer
/// </summary>
public const string DiagnosticId = "SA1024";
private const string Title = "Colons Must Be Spaced Correctly";
private const string MessageFormat = "Colon must{0} be {1} by a space.";
private const string MessageFormat = "Colon must{0} be {1}{2} by a space.";
private const string Category = "StyleCop.CSharp.SpacingRules";
private const string Description = "A colon within a C# element is not spaced correctly.";
private const string HelpLink = "http://www.stylecop.com/docs/SA1024.html";
Expand Down Expand Up @@ -138,23 +138,27 @@ private static void HandleColonToken(SyntaxTreeAnalysisContext context, SyntaxTo
{
// only the first token on the line has leading trivia, and those are ignored
SyntaxToken precedingToken = token.GetPreviousToken();
SyntaxTriviaList triviaList = precedingToken.TrailingTrivia;
if (triviaList.Count > 0 && !triviaList.Last().IsKind(SyntaxKind.MultiLineCommentTrivia))
SyntaxTriviaList combinedTrivia = precedingToken.TrailingTrivia.AddRange(token.LeadingTrivia);
if (combinedTrivia.Count > 0 && !combinedTrivia.Last().IsKind(SyntaxKind.MultiLineCommentTrivia))
{
hasPrecedingSpace = true;
}
}

if (missingFollowingSpace)
if (missingFollowingSpace && requireBefore && !hasPrecedingSpace)
{
// colon must{} be {followed} by a space
context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation(), string.Empty, "followed"));
// colon must{} be {preceded}{ and followed} by a space
context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation(), string.Empty, "preceded", " and followed"));
}

if (hasPrecedingSpace != requireBefore)
else if (missingFollowingSpace)
{
// colon must{} be {followed}{} by a space
context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation(), string.Empty, "followed", string.Empty));
}
else if (hasPrecedingSpace != requireBefore)
{
// colon must{ not}? be {preceded} by a space
context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation(), requireBefore ? string.Empty : " not", "preceded"));
// colon must{ not}? be {preceded}{} by a space
context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation(), requireBefore ? string.Empty : " not", "preceded", string.Empty));
}
}
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@
<data name="SA1022CodeFix" xml:space="preserve">
<value>Fix spacing</value>
</data>
<data name="SA1024CodeFix" xml:space="preserve">
<value>Fix spacing</value>
</data>
<data name="SA1025Description" xml:space="preserve">
<value>The code contains multiple whitespace characters in a row.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@
<Compile Include="SpacingRules\SA1022CodeFixProvider.cs" />
<Compile Include="SpacingRules\SA1022PositiveSignsMustBeSpacedCorrectly.cs" />
<Compile Include="SpacingRules\SA1023DereferenceAndAccessOfSymbolsMustBeSpacedCorrectly.cs" />
<Compile Include="SpacingRules\SA1024CodeFixProvider.cs" />
<Compile Include="SpacingRules\SA1024ColonsMustBeSpacedCorrectly.cs" />
<Compile Include="SpacingRules\SA1025CodeMustNotContainMultipleWhitespaceInARow.cs" />
<Compile Include="SpacingRules\SA1026CodeFixProvider.cs" />
Expand Down

0 comments on commit 5aef39c

Please sign in to comment.