Skip to content

Commit

Permalink
Merge pull request #900 from vweijsters/SA1508
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed May 30, 2015
2 parents be1acca + ba17fd0 commit b42b385
Show file tree
Hide file tree
Showing 8 changed files with 1,051 additions and 28 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
<Compile Include="LayoutRules\SA1504UnitTests.cs" />
<Compile Include="LayoutRules\SA1505UnitTests.cs" />
<Compile Include="LayoutRules\SA1507UnitTests.cs" />
<Compile Include="LayoutRules\SA1508UnitTests.cs" />
<Compile Include="LayoutRules\SA1509UnitTests.cs" />
<Compile Include="LayoutRules\SA1510UnitTests.cs" />
<Compile Include="LayoutRules\SA1511UnitTests.cs" />
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 @@ -135,6 +135,9 @@
<data name="SA1507CodeFix" xml:space="preserve">
<value>Remove multiple blank lines</value>
</data>
<data name="SA1508CodeFix" xml:space="preserve">
<value>Remove blank lines preceding this curly bracket</value>
</data>
<data name="SA1509CodeFix" xml:space="preserve">
<value>Remove blank lines preceding this curly bracket</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,5 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
var newSyntaxRoot = syntaxRoot.ReplaceTokens(replaceMap.Keys, (t1, t2) => replaceMap[t1]);
return document.WithSyntaxRoot(newSyntaxRoot);
}

private class TriviaRemover : CSharpSyntaxRewriter
{
private List<SyntaxTrivia> triviaToRemove;

internal TriviaRemover(List<SyntaxTrivia> triviaToRemove)
{
this.triviaToRemove = triviaToRemove;
}

public override SyntaxTrivia VisitTrivia(SyntaxTrivia trivia)
{
if (this.triviaToRemove.Contains(trivia))
{
return default(SyntaxTrivia);
}

return base.VisitTrivia(trivia);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
{
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

/// <summary>
Expand Down Expand Up @@ -39,30 +41,120 @@ public class SA1508ClosingCurlyBracketsMustNotBePrecededByBlankLine : Diagnostic
/// </summary>
public const string DiagnosticId = "SA1508";
private const string Title = "Closing curly brackets must not be preceded by blank line";
private const string MessageFormat = "TODO: Message format";
private const string MessageFormat = "A closing curly bracket must not be preceded by a blank line.";
private const string Category = "StyleCop.CSharp.LayoutRules";
private const string Description = "A closing curly bracket within a C# element, statement, or expression is preceded by a blank line.";
private const string HelpLink = "http://www.stylecop.com/docs/SA1508.html";

private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, AnalyzerConstants.DisabledNoTests, Description, HelpLink);
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);

private static readonly ImmutableArray<DiagnosticDescriptor> SupportedDiagnosticsValue =
ImmutableArray.Create(Descriptor);

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => SupportedDiagnosticsValue;

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleBlock, SyntaxKind.Block);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleInitializers, SyntaxKind.ObjectInitializerExpression, SyntaxKind.CollectionInitializerExpression, SyntaxKind.ArrayInitializerExpression, SyntaxKind.ComplexElementInitializerExpression);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleAnonymousObjectCreation, SyntaxKind.AnonymousObjectCreationExpression);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleSwitchStatement, SyntaxKind.SwitchStatement);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleNamespaceDeclaration, SyntaxKind.NamespaceDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.EnumDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleAccessorList, SyntaxKind.AccessorList);
}

private void HandleBlock(SyntaxNodeAnalysisContext context)
{
var block = (BlockSyntax)context.Node;
this.AnalyzeCloseBrace(context, block.CloseBraceToken);
}

private void HandleInitializers(SyntaxNodeAnalysisContext context)
{
var expression = (InitializerExpressionSyntax)context.Node;
this.AnalyzeCloseBrace(context, expression.CloseBraceToken);
}

private void HandleAnonymousObjectCreation(SyntaxNodeAnalysisContext context)
{
var expression = (AnonymousObjectCreationExpressionSyntax)context.Node;
this.AnalyzeCloseBrace(context, expression.CloseBraceToken);
}

private void HandleSwitchStatement(SyntaxNodeAnalysisContext context)
{
var switchStatement = (SwitchStatementSyntax)context.Node;
this.AnalyzeCloseBrace(context, switchStatement.CloseBraceToken);
}

private void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context)
{
var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node;
this.AnalyzeCloseBrace(context, namespaceDeclaration.CloseBraceToken);
}

private void HandleTypeDeclaration(SyntaxNodeAnalysisContext context)
{
var typeDeclaration = (BaseTypeDeclarationSyntax)context.Node;
this.AnalyzeCloseBrace(context, typeDeclaration.CloseBraceToken);
}

private void HandleAccessorList(SyntaxNodeAnalysisContext context)
{
var accessorList = (AccessorListSyntax)context.Node;
this.AnalyzeCloseBrace(context, accessorList.CloseBraceToken);
}

private void AnalyzeCloseBrace(SyntaxNodeAnalysisContext context, SyntaxToken closeBraceToken)
{
get
var previousToken = closeBraceToken.GetPreviousToken();
if ((GetLine(closeBraceToken) - GetLine(previousToken)) < 2)
{
// there will be no blank lines when the closing brace and the preceding token are not at least two lines apart.
return;
}

var separatingTrivia = previousToken.TrailingTrivia.AddRange(closeBraceToken.LeadingTrivia);

// skip all leading whitespace for the close brace
var index = separatingTrivia.Count - 1;
while (separatingTrivia[index].IsKind(SyntaxKind.WhitespaceTrivia))
{
index--;
}

var done = false;
var eolCount = 0;
while (!done && index >= 0)
{
switch (separatingTrivia[index].Kind())
{
case SyntaxKind.WhitespaceTrivia:
break;
case SyntaxKind.EndOfLineTrivia:
eolCount++;
break;
default:
done = true;
break;
}

index--;
}

if (eolCount > 1)
{
return SupportedDiagnosticsValue;
context.ReportDiagnostic(Diagnostic.Create(Descriptor, closeBraceToken.GetLocation()));
}
}

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
private static int GetLine(SyntaxToken token)
{
// TODO: Implement analysis
return token.GetLocation().GetLineSpan().StartLinePosition.Line;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
namespace StyleCop.Analyzers.LayoutRules
{
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
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="SA1508ClosingCurlyBracketsMustNotBePrecededByBlankLine"/>.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1508CodeFixProvider))]
[Shared]
public class SA1508CodeFixProvider : CodeFixProvider
{
private static readonly ImmutableArray<string> FixableDiagnostics =
ImmutableArray.Create(SA1508ClosingCurlyBracketsMustNotBePrecededByBlankLine.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.Where(d => FixableDiagnostics.Contains(d.Id)))
{
context.RegisterCodeFix(CodeAction.Create(LayoutResources.SA1508CodeFix, token => GetTransformedDocumentAsync(context.Document, diagnostic, token)), diagnostic);
}

return Task.FromResult(true);
}

private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
{
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

var closeBraceToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
var previousToken = closeBraceToken.GetPreviousToken();

var triviaList = previousToken.TrailingTrivia.AddRange(closeBraceToken.LeadingTrivia);

// skip all leading whitespace for the close brace
var index = triviaList.Count - 1;
while (triviaList[index].IsKind(SyntaxKind.WhitespaceTrivia))
{
index--;
}

var firstLeadingWhitespace = index + 1;

var done = false;
var lastEndOfLineIndex = -1;
while (!done && index >= 0)
{
switch (triviaList[index].Kind())
{
case SyntaxKind.WhitespaceTrivia:
break;
case SyntaxKind.EndOfLineTrivia:
lastEndOfLineIndex = index;
break;
default:
done = true;
break;
}

index--;
}

var replaceMap = new Dictionary<SyntaxToken, SyntaxToken>()
{
[previousToken] = previousToken.WithTrailingTrivia(triviaList.Take(lastEndOfLineIndex + 1)),
[closeBraceToken] = closeBraceToken.WithLeadingTrivia(triviaList.Skip(firstLeadingWhitespace))
};

var newSyntaxRoot = syntaxRoot.ReplaceTokens(replaceMap.Keys, (t1, t2) => replaceMap[t1]);
return document.WithSyntaxRoot(newSyntaxRoot);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
<Compile Include="LayoutRules\SA1507CodeFixProvider.cs" />
<Compile Include="LayoutRules\SA1507CodeMustNotContainMultipleBlankLinesInARow.cs" />
<Compile Include="LayoutRules\SA1508ClosingCurlyBracketsMustNotBePrecededByBlankLine.cs" />
<Compile Include="LayoutRules\SA1508CodeFixProvider.cs" />
<Compile Include="LayoutRules\SA1509CodeFixProvider.cs" />
<Compile Include="LayoutRules\SA1509OpeningCurlyBracketsMustNotBePrecededByBlankLine.cs" />
<Compile Include="LayoutRules\SA1510ChainedStatementBlocksMustNotBePrecededByBlankLine.cs" />
Expand Down

0 comments on commit b42b385

Please sign in to comment.