Skip to content

Commit

Permalink
Merge pull request #884 from vweijsters/SA1510
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed May 30, 2015
2 parents ab282ce + 2091e88 commit be1acca
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 121 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
namespace StyleCop.Analyzers.Test.LayoutRules
{
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using StyleCop.Analyzers.LayoutRules;
using TestHelper;
using Xunit;

/// <summary>
/// Unit tests for <see cref="SA1510ChainedStatementBlocksMustNotBePrecededByBlankLine"/>
/// </summary>
public class SA1510UnitTests : CodeFixVerifier
{
/// <summary>
/// Verifies that the analyzer will properly handle an empty source.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task TestEmptySourceAsync()
{
var testCode = string.Empty;
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that valid chained statements will not produce any diagnostics.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task TestValidChainedStatementsAsync()
{
var testCode = @"using System;
namespace Foo
{
public class Bar
{
public int TestElseStatement(int x)
{
if (x > 0)
{
return -x;
}
else
{
return x * x;
}
}
public void TestCatchFinallyStatements()
{
var x = 0;
try
{
x = x + 1;
}
catch (Exception)
{
x = 2;
}
finally
{
x = 3;
}
}
}
}
";

await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that an else statement preceded by a blank line will report the expected diagnostic.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task TestInvalidElseStatementAsync()
{
var testCode = @"using System;
namespace Foo
{
public class Bar
{
public int TestElseStatement(int x)
{
if (x > 0)
{
return -x;
}
else
{
return x * x;
}
}
}
}
";

var fixedTestCode = @"using System;
namespace Foo
{
public class Bar
{
public int TestElseStatement(int x)
{
if (x > 0)
{
return -x;
}
else
{
return x * x;
}
}
}
}
";

DiagnosticResult[] expectedDiagnostics =
{
this.CSharpDiagnostic().WithLocation(14, 13).WithArguments("else")
};

await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostics, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
}

/// <summary>
/// Verifies that an else statement preceded by a blank line will report the expected diagnostic.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task TestInvalidCatchFinallyStatementsAsync()
{
var testCode = @"using System;
namespace Foo
{
public class Bar
{
public void TestCatchFinallyStatements()
{
var x = 0;
try
{
x = x + 1;
}
catch (Exception)
{
x = 2;
}
finally
{
x = 3;
}
}
}
}
";

var fixedTestCode = @"using System;
namespace Foo
{
public class Bar
{
public void TestCatchFinallyStatements()
{
var x = 0;
try
{
x = x + 1;
}
catch (Exception)
{
x = 2;
}
finally
{
x = 3;
}
}
}
}
";

DiagnosticResult[] expectedDiagnostics =
{
this.CSharpDiagnostic().WithLocation(16, 13).WithArguments("catch"),
this.CSharpDiagnostic().WithLocation(21, 13).WithArguments("finally")
};

await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostics, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
}

protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
{
yield return new SA1510ChainedStatementBlocksMustNotBePrecededByBlankLine();
}

protected override CodeFixProvider GetCSharpCodeFixProvider()
{
return new SA1510CodeFixProvider();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@
<Compile Include="LayoutRules\SA1505UnitTests.cs" />
<Compile Include="LayoutRules\SA1507UnitTests.cs" />
<Compile Include="LayoutRules\SA1509UnitTests.cs" />
<Compile Include="LayoutRules\SA1510UnitTests.cs" />
<Compile Include="LayoutRules\SA1511UnitTests.cs" />
<Compile Include="LayoutRules\SA1512UnitTests.cs" />
<Compile Include="LayoutRules\SA1513UnitTests.cs" />
Expand Down
107 changes: 107 additions & 0 deletions StyleCop.Analyzers/StyleCop.Analyzers/Helpers/TriviaHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,112 @@ internal static SyntaxTriviaList GetContainingTriviaList(SyntaxTrivia trivia, ou
triviaIndex = prevToken.TrailingTrivia.Count + token.LeadingTrivia.IndexOf(trivia);
return prevToken.TrailingTrivia.AddRange(token.LeadingTrivia);
}

/// <summary>
/// Determines if the given token has leading blank lines. Leading whitespace on the same line as the token is ignored.
/// </summary>
/// <param name="token">The token to check for leading blank lines.</param>
/// <returns>True if the token has leading blank lines.</returns>
internal static bool HasLeadingBlankLines(this SyntaxToken token)
{
if (!token.HasLeadingTrivia)
{
return false;
}

var triviaList = token.LeadingTrivia;

// skip any leading whitespace
var index = triviaList.Count - 1;
while ((index >= 0) && triviaList[index].IsKind(SyntaxKind.WhitespaceTrivia))
{
index--;
}

if ((index < 0) || !triviaList[index].IsKind(SyntaxKind.EndOfLineTrivia))
{
return false;
}

var blankLineCount = -1;
while (index >= 0)
{
switch (triviaList[index].Kind())
{
case SyntaxKind.WhitespaceTrivia:
// ignore;
break;
case SyntaxKind.EndOfLineTrivia:
blankLineCount++;
break;
case SyntaxKind.IfDirectiveTrivia:
case SyntaxKind.ElifDirectiveTrivia:
case SyntaxKind.ElseDirectiveTrivia:
case SyntaxKind.EndIfDirectiveTrivia:
// directive trivia have an embedded end of line
blankLineCount++;
return blankLineCount > 0;
default:
return blankLineCount > 0;
}

index--;
}

return true;
}

/// <summary>
/// Strips all leading blank lines from the given token.
/// </summary>
/// <param name="token">The token to strip.</param>
/// <returns>A new token without leading blank lines.</returns>
internal static SyntaxToken WithoutLeadingBlankLines(this SyntaxToken token)
{
var triviaList = token.LeadingTrivia;
var leadingWhitespaceStart = triviaList.Count - 1;

// skip leading whitespace in front of the while keyword
while ((leadingWhitespaceStart > 0) && triviaList[leadingWhitespaceStart - 1].IsKind(SyntaxKind.WhitespaceTrivia))
{
leadingWhitespaceStart--;
}

var blankLinesStart = leadingWhitespaceStart - 1;
var done = false;
while (!done && (blankLinesStart >= 0))
{
switch (triviaList[blankLinesStart].Kind())
{
case SyntaxKind.WhitespaceTrivia:
case SyntaxKind.EndOfLineTrivia:
blankLinesStart--;
break;

case SyntaxKind.IfDirectiveTrivia:
case SyntaxKind.ElifDirectiveTrivia:
case SyntaxKind.ElseDirectiveTrivia:
case SyntaxKind.EndIfDirectiveTrivia:
// directives include an embedded end of line
blankLinesStart++;
done = true;
break;

default:
// include the first end of line (as it is part of the non blank line trivia)
while (!triviaList[blankLinesStart].IsKind(SyntaxKind.EndOfLineTrivia))
{
blankLinesStart++;
}

blankLinesStart++;
done = true;
break;
}
}

var newLeadingTrivia = SyntaxFactory.TriviaList(triviaList.Take(blankLinesStart).Concat(triviaList.Skip(leadingWhitespaceStart)));
return token.WithLeadingTrivia(newLeadingTrivia);
}
}
}

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 @@ -138,6 +138,9 @@
<data name="SA1509CodeFix" xml:space="preserve">
<value>Remove blank lines preceding this curly bracket</value>
</data>
<data name="SA1510CodeFix" xml:space="preserve">
<value>Remove blank line before chained statement</value>
</data>
<data name="SA1511CodeFix" xml:space="preserve">
<value>Remove blank line before while</value>
</data>
Expand Down
Loading

0 comments on commit be1acca

Please sign in to comment.