-
Notifications
You must be signed in to change notification settings - Fork 470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add analyzer/fixer for informal IDE brace style with statements. #4647
Changes from 7 commits
9cdb34f
9b80c1a
322cb84
fbefe58
d3acae7
e7def16
e3427af
883f5f9
34f10ba
4c1ef53
85f3358
99a65d5
4d8490e
217adda
e181dc7
25f09d0
85c8abc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
; Please do not edit this file manually, it should only be updated through code fix application. | ||
### New Rules | ||
Rule ID | Category | Severity | Notes | ||
--------|----------|----------|------- | ||
RS0103 | RoslynDiagnosticsMaintainability | Warning | CSharpBlankLinesBetweenStatementsDiagnosticAnalyzer |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
#nullable disable warnings | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
using System; | ||
using System.Collections.Immutable; | ||
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; | ||
using Roslyn.Diagnostics.Analyzers; | ||
|
||
namespace Roslyn.Diagnostics.CSharp.Analyzers.BlankLines | ||
{ | ||
[ExportCodeFixProvider(LanguageNames.CSharp)] | ||
public sealed class CSharpBlankLinesBetweenStatementsCodeFixProvider : CodeFixProvider | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private static readonly SyntaxTrivia s_endOfLine = SyntaxFactory.EndOfLine(Environment.NewLine); | ||
|
||
public override ImmutableArray<string> FixableDiagnosticIds | ||
=> ImmutableArray.Create(RoslynDiagnosticIds.BlankLinesBetweenStatementsRuleId); | ||
|
||
public override FixAllProvider GetFixAllProvider() | ||
=> WellKnownFixAllProviders.BatchFixer; | ||
|
||
public override Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var document = context.Document; | ||
var diagnostic = context.Diagnostics.First(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have some code-fixes doing a loop over the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be worth doing an anlyzer for this then. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this logic is correct. If there were multiple diagnostics for the same token, we'd only want to process one of them anyways. If we looked at all diagnostics, we'd have to dedupe. So this has the same effect. |
||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
RoslynDiagnosticsAnalyzersResources.Add_blank_line_after_block, | ||
c => UpdateDocumentAsync(document, diagnostic, c), | ||
RoslynDiagnosticsAnalyzersResources.Add_blank_line_after_block), | ||
context.Diagnostics); | ||
return Task.CompletedTask; | ||
|
||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private static async Task<Document> UpdateDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) | ||
{ | ||
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); | ||
|
||
var closeBraceToken = root.FindToken(diagnostic.Location.SourceSpan.Start); | ||
var nextToken = closeBraceToken.GetNextToken(); | ||
|
||
var newRoot = root.ReplaceToken( | ||
nextToken, | ||
nextToken.WithLeadingTrivia(nextToken.LeadingTrivia.Insert(0, s_endOfLine))); | ||
|
||
return document.WithSyntaxRoot(newRoot); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Immutable; | ||
using Analyzer.Utilities; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Roslyn.Diagnostics.Analyzers; | ||
|
||
namespace Roslyn.Diagnostics.CSharp.Analyzers.BlankLines | ||
{ | ||
/// <summary> | ||
/// Analyzer that finds code of the form: | ||
/// <code> | ||
/// if (cond) | ||
/// { | ||
/// } | ||
/// NextStatement(); | ||
/// </code> | ||
/// | ||
/// And requires it to be of the form: | ||
/// <code> | ||
/// if (cond) | ||
/// { | ||
/// } | ||
/// | ||
/// NextStatement(); | ||
/// </code> | ||
/// | ||
/// Specifically, all blocks followed by another statement must have a blank line between them. | ||
/// </summary> | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class CSharpBlankLinesBetweenStatementsDiagnosticAnalyzer : DiagnosticAnalyzer | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString( | ||
nameof(RoslynDiagnosticsAnalyzersResources.BlankLinesBetweenStatementsTitle), RoslynDiagnosticsAnalyzersResources.ResourceManager, typeof(RoslynDiagnosticsAnalyzersResources)); | ||
private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString( | ||
nameof(RoslynDiagnosticsAnalyzersResources.BlankLinesBetweenStatementsMessage), RoslynDiagnosticsAnalyzersResources.ResourceManager, typeof(RoslynDiagnosticsAnalyzersResources)); | ||
|
||
internal static DiagnosticDescriptor Rule = new( | ||
RoslynDiagnosticIds.BlankLinesBetweenStatementsRuleId, | ||
s_localizableTitle, | ||
s_localizableMessage, | ||
DiagnosticCategory.RoslynDiagnosticsMaintainability, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, | ||
helpLinkUri: null, | ||
customTags: WellKnownDiagnosticTags.Telemetry); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics | ||
=> ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
|
||
context.RegisterSyntaxNodeAction(AnalyzeBlock, SyntaxKind.Block); | ||
} | ||
|
||
private void AnalyzeBlock(SyntaxNodeAnalysisContext context) | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
var block = (BlockSyntax)context.Node; | ||
|
||
// Don't examine broken blocks. | ||
var closeBrace = block.CloseBraceToken; | ||
if (closeBrace.IsMissing) | ||
return; | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// If the close brace itself doesn't have a newline, then ignore this. This is a case of series of | ||
// statements on the same line. | ||
if (!closeBrace.TrailingTrivia.Any()) | ||
return; | ||
|
||
if (closeBrace.TrailingTrivia.Last().Kind() != SyntaxKind.EndOfLineTrivia) | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
|
||
// Grab whatever comes after the close brace. If it's not the start of a statement, ignore it. | ||
var nextToken = closeBrace.GetNextToken(); | ||
var nextTokenContainingStatement = nextToken.Parent!.FirstAncestorOrSelf<StatementSyntax>(); | ||
if (nextTokenContainingStatement == null) | ||
return; | ||
|
||
if (nextToken != nextTokenContainingStatement.GetFirstToken()) | ||
return; | ||
|
||
// There has to be at least a blank line between the end of the block and the start of the next statement. | ||
|
||
foreach (var trivia in nextToken.LeadingTrivia) | ||
{ | ||
// If there's a blank line between the brace and the next token, we're all set. | ||
if (trivia.Kind() == SyntaxKind.EndOfLineTrivia) | ||
return; | ||
|
||
if (trivia.Kind() == SyntaxKind.WhitespaceTrivia) | ||
continue; | ||
|
||
// got something that wasn't whitespace. Bail out as we don't want to place any restrictions on this code. | ||
return; | ||
} | ||
|
||
context.ReportDiagnostic(Diagnostic.Create( | ||
Rule, | ||
closeBrace.GetLocation())); | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Evangelink Didn't you fix this before (the empty line before the table)? or I'm mis-remembering?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't recall doing the fix :)