Skip to content

Commit 8ecabdc

Browse files
authored
Merge pull request #14 from delegateas/copilot/add-enforce-control-flow-brackets-rule
Add CT0004 analyzer for enforcing braceless control flow statements
2 parents 3e2dec8 + fca3ef4 commit 8ecabdc

File tree

5 files changed

+471
-0
lines changed

5 files changed

+471
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.Collections.Immutable;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
7+
namespace DataverseAnalyzer;
8+
9+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
10+
public sealed class NoBracesForControlFlowAnalyzer : DiagnosticAnalyzer
11+
{
12+
private static readonly Lazy<DiagnosticDescriptor> LazyRule = new(() => new DiagnosticDescriptor(
13+
"CT0004",
14+
Resources.CT0004_Title,
15+
Resources.CT0004_MessageFormat,
16+
"Style",
17+
DiagnosticSeverity.Error,
18+
isEnabledByDefault: true,
19+
description: Resources.CT0004_Description));
20+
21+
public static DiagnosticDescriptor Rule => LazyRule.Value;
22+
23+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
24+
25+
public override void Initialize(AnalysisContext context)
26+
{
27+
if (context is null)
28+
{
29+
throw new ArgumentNullException(nameof(context));
30+
}
31+
32+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
33+
context.EnableConcurrentExecution();
34+
context.RegisterSyntaxNodeAction(AnalyzeIfStatement, SyntaxKind.IfStatement);
35+
context.RegisterSyntaxNodeAction(AnalyzeElseClause, SyntaxKind.ElseClause);
36+
}
37+
38+
private static void AnalyzeIfStatement(SyntaxNodeAnalysisContext context)
39+
{
40+
var ifStatement = (IfStatementSyntax)context.Node;
41+
AnalyzeEmbeddedStatement(context, ifStatement.Statement);
42+
}
43+
44+
private static void AnalyzeElseClause(SyntaxNodeAnalysisContext context)
45+
{
46+
var elseClause = (ElseClauseSyntax)context.Node;
47+
if (elseClause.Statement is not IfStatementSyntax)
48+
{
49+
AnalyzeEmbeddedStatement(context, elseClause.Statement);
50+
}
51+
}
52+
53+
private static void AnalyzeEmbeddedStatement(SyntaxNodeAnalysisContext context, StatementSyntax statement)
54+
{
55+
if (statement is not BlockSyntax block)
56+
{
57+
return;
58+
}
59+
60+
if (block.Statements.Count != 1)
61+
{
62+
return;
63+
}
64+
65+
var singleStatement = block.Statements[0];
66+
if (!IsControlFlowStatement(singleStatement))
67+
{
68+
return;
69+
}
70+
71+
var diagnostic = Diagnostic.Create(Rule, block.GetLocation());
72+
context.ReportDiagnostic(diagnostic);
73+
}
74+
75+
private static bool IsControlFlowStatement(StatementSyntax statement)
76+
{
77+
return statement.Kind() switch
78+
{
79+
SyntaxKind.ReturnStatement => true,
80+
SyntaxKind.ThrowStatement => true,
81+
SyntaxKind.ContinueStatement => true,
82+
SyntaxKind.BreakStatement => true,
83+
SyntaxKind.YieldReturnStatement => false,
84+
SyntaxKind.YieldBreakStatement => true,
85+
_ => false,
86+
};
87+
}
88+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Collections.Immutable;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CodeActions;
4+
using Microsoft.CodeAnalysis.CodeFixes;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
7+
namespace DataverseAnalyzer;
8+
9+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NoBracesForControlFlowCodeFixProvider))]
10+
public sealed class NoBracesForControlFlowCodeFixProvider : CodeFixProvider
11+
{
12+
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(NoBracesForControlFlowAnalyzer.Rule.Id);
13+
14+
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
15+
16+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
17+
{
18+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
19+
if (root is null)
20+
{
21+
return;
22+
}
23+
24+
var diagnostic = context.Diagnostics.FirstOrDefault(d => FixableDiagnosticIds.Contains(d.Id, StringComparer.Ordinal));
25+
if (diagnostic is null)
26+
{
27+
return;
28+
}
29+
30+
var diagnosticSpan = diagnostic.Location.SourceSpan;
31+
var block = root.FindNode(diagnosticSpan) as BlockSyntax;
32+
if (block is null || block.Statements.Count != 1)
33+
{
34+
return;
35+
}
36+
37+
var action = CodeAction.Create(
38+
title: Resources.CT0004_CodeFix_Title,
39+
createChangedDocument: c => RemoveBraces(context.Document, root, block, c),
40+
equivalenceKey: "RemoveBraces");
41+
42+
context.RegisterCodeFix(action, diagnostic);
43+
}
44+
45+
private static Task<Document> RemoveBraces(Document document, SyntaxNode root, BlockSyntax block, CancellationToken cancellationToken)
46+
{
47+
var statement = block.Statements[0];
48+
var newStatement = statement.WithTriviaFrom(block);
49+
50+
var newRoot = root.ReplaceNode(block, newStatement);
51+
var newDocument = document.WithSyntaxRoot(newRoot);
52+
53+
return Task.FromResult(newDocument);
54+
}
55+
}

src/DataverseAnalyzer/Resources.Designer.cs

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/DataverseAnalyzer/Resources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,16 @@
8888
<data name="CT0003_Description" xml:space="preserve">
8989
<value>Object initialization with empty parentheses followed by an initializer is unnecessary and should be avoided for cleaner code.</value>
9090
</data>
91+
<data name="CT0004_Title" xml:space="preserve">
92+
<value>Control flow statement with braces containing only control flow should not have braces</value>
93+
</data>
94+
<data name="CT0004_MessageFormat" xml:space="preserve">
95+
<value>Block containing only a control flow statement (return, throw, continue, break, or yield break) should not have braces</value>
96+
</data>
97+
<data name="CT0004_Description" xml:space="preserve">
98+
<value>When a block contains only a single control flow statement, braces should be omitted to improve code readability and consistency.</value>
99+
</data>
100+
<data name="CT0004_CodeFix_Title" xml:space="preserve">
101+
<value>Remove braces</value>
102+
</data>
91103
</root>

0 commit comments

Comments
 (0)