mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 15:59:48 +00:00
Added switch expression method analyzer & code fix
This commit is contained in:
parent
db9bb8f487
commit
319d3f9b94
@ -3,9 +3,10 @@
|
|||||||
### New Rules
|
### New Rules
|
||||||
|
|
||||||
Rule ID | Category | Severity | Notes
|
Rule ID | Category | Severity | Notes
|
||||||
-----------|----------|----------|-----------------------------
|
-----------|----------|----------|--------------------------------
|
||||||
MWAIS0001 | Usage | Error | ProviderAccessAnalyzer
|
MWAIS0001 | Usage | Error | ProviderAccessAnalyzer
|
||||||
MWAIS0002 | Naming | Error | ConstStaticAnalyzer
|
MWAIS0002 | Naming | Error | ConstStaticAnalyzer
|
||||||
MWAIS0003 | Naming | Error | UnderscorePrefixAnalyzer
|
MWAIS0003 | Naming | Error | UnderscorePrefixAnalyzer
|
||||||
MWAIS0004 | Usage | Error | RandomInstantiationAnalyzer
|
MWAIS0004 | Usage | Error | RandomInstantiationAnalyzer
|
||||||
MWAIS0005 | Usage | Error | ThisUsageAnalyzer
|
MWAIS0005 | Usage | Error | ThisUsageAnalyzer
|
||||||
|
MWAIS0006 | Style | Error | SwitchExpressionMethodAnalyzer
|
@ -7,4 +7,5 @@ public static class Identifier
|
|||||||
public const string UNDERSCORE_PREFIX_ANALYZER = $"{Tools.ID_PREFIX}0003";
|
public const string UNDERSCORE_PREFIX_ANALYZER = $"{Tools.ID_PREFIX}0003";
|
||||||
public const string RANDOM_INSTANTIATION_ANALYZER = $"{Tools.ID_PREFIX}0004";
|
public const string RANDOM_INSTANTIATION_ANALYZER = $"{Tools.ID_PREFIX}0004";
|
||||||
public const string THIS_USAGE_ANALYZER = $"{Tools.ID_PREFIX}0005";
|
public const string THIS_USAGE_ANALYZER = $"{Tools.ID_PREFIX}0005";
|
||||||
|
public const string SWITCH_EXPRESSION_METHOD_ANALYZER = $"{Tools.ID_PREFIX}0006";
|
||||||
}
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace SourceCodeRules.StyleAnalyzers;
|
||||||
|
|
||||||
|
#pragma warning disable RS1038
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
#pragma warning restore RS1038
|
||||||
|
public class SwitchExpressionMethodAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
private const string DIAGNOSTIC_ID = Identifier.SWITCH_EXPRESSION_METHOD_ANALYZER;
|
||||||
|
|
||||||
|
private static readonly string TITLE = "Method with switch expression should use inline expression body";
|
||||||
|
|
||||||
|
private static readonly string MESSAGE_FORMAT = "Method with a switch expression should use inline expression body syntax with the switch keyword on the same line";
|
||||||
|
|
||||||
|
private static readonly string DESCRIPTION = "Methods that only return a switch expression should use the expression body syntax (=>) with the switch keyword on the same line for better readability.";
|
||||||
|
|
||||||
|
private const string CATEGORY = "Style";
|
||||||
|
|
||||||
|
private static readonly DiagnosticDescriptor RULE = new(
|
||||||
|
DIAGNOSTIC_ID,
|
||||||
|
TITLE,
|
||||||
|
MESSAGE_FORMAT,
|
||||||
|
CATEGORY,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: DESCRIPTION);
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [RULE];
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
context.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
|
||||||
|
{
|
||||||
|
var methodDeclaration = (MethodDeclarationSyntax)context.Node;
|
||||||
|
|
||||||
|
// Fall 1: Methode hat Block-Body mit einem Return-Statement, das eine Switch-Expression ist
|
||||||
|
if (methodDeclaration is { Body: not null, ExpressionBody: null })
|
||||||
|
{
|
||||||
|
var statements = methodDeclaration.Body.Statements;
|
||||||
|
if (statements.Count == 1 && statements[0] is ReturnStatementSyntax { Expression: SwitchExpressionSyntax })
|
||||||
|
{
|
||||||
|
var diagnostic = Diagnostic.Create(RULE, methodDeclaration.Identifier.GetLocation());
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall 2: Methode hat Expression-Body, aber die Switch-Expression beginnt auf einer neuen Zeile
|
||||||
|
var expressionBody = methodDeclaration.ExpressionBody;
|
||||||
|
if (expressionBody?.Expression is SwitchExpressionSyntax switchExpr)
|
||||||
|
{
|
||||||
|
var arrowToken = expressionBody.ArrowToken;
|
||||||
|
var switchToken = switchExpr.SwitchKeyword;
|
||||||
|
bool hasNewLineBetweenArrowAndSwitch = false;
|
||||||
|
|
||||||
|
foreach (var trivia in arrowToken.TrailingTrivia)
|
||||||
|
{
|
||||||
|
if (trivia.IsKind(SyntaxKind.EndOfLineTrivia))
|
||||||
|
{
|
||||||
|
hasNewLineBetweenArrowAndSwitch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe Leading Trivia des Switch-Keywords, falls notwendig
|
||||||
|
if (!hasNewLineBetweenArrowAndSwitch)
|
||||||
|
{
|
||||||
|
foreach (var trivia in switchToken.LeadingTrivia)
|
||||||
|
{
|
||||||
|
if (trivia.IsKind(SyntaxKind.EndOfLineTrivia))
|
||||||
|
{
|
||||||
|
hasNewLineBetweenArrowAndSwitch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNewLineBetweenArrowAndSwitch)
|
||||||
|
{
|
||||||
|
var diagnostic = Diagnostic.Create(RULE, methodDeclaration.Identifier.GetLocation());
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,162 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Composition;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CodeActions;
|
||||||
|
using Microsoft.CodeAnalysis.CodeFixes;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace SourceCodeRules.StyleCodeFixes;
|
||||||
|
|
||||||
|
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SwitchExpressionMethodCodeFixProvider)), Shared]
|
||||||
|
public class SwitchExpressionMethodCodeFixProvider : CodeFixProvider
|
||||||
|
{
|
||||||
|
public sealed override ImmutableArray<string> FixableDiagnosticIds => [Identifier.SWITCH_EXPRESSION_METHOD_ANALYZER];
|
||||||
|
|
||||||
|
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
|
||||||
|
|
||||||
|
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||||
|
{
|
||||||
|
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
||||||
|
if (root == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var diagnostic = context.Diagnostics.First();
|
||||||
|
var diagnosticSpan = diagnostic.Location.SourceSpan;
|
||||||
|
var methodDeclaration = root.FindToken(diagnosticSpan.Start)
|
||||||
|
.Parent?.AncestorsAndSelf()
|
||||||
|
.OfType<MethodDeclarationSyntax>()
|
||||||
|
.First();
|
||||||
|
|
||||||
|
if(methodDeclaration == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.RegisterCodeFix(
|
||||||
|
CodeAction.Create(
|
||||||
|
title: "Use inline expression body for switch expression",
|
||||||
|
createChangedDocument: c => UseInlineExpressionBodyAsync(context.Document, methodDeclaration, c),
|
||||||
|
equivalenceKey: nameof(SwitchExpressionMethodCodeFixProvider)),
|
||||||
|
diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Document> UseInlineExpressionBodyAsync(Document document, MethodDeclarationSyntax methodDecl, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var sourceText = await document.GetTextAsync(cancellationToken);
|
||||||
|
var parameterText = methodDecl.ParameterList.ToString();
|
||||||
|
var methodStartLine = sourceText.Lines.GetLineFromPosition(methodDecl.SpanStart);
|
||||||
|
|
||||||
|
SwitchExpressionSyntax? switchExpr = null;
|
||||||
|
ExpressionSyntax? governingExpression = null;
|
||||||
|
var switchBodyText = string.Empty;
|
||||||
|
|
||||||
|
if (methodDecl.Body != null)
|
||||||
|
{
|
||||||
|
// Case: Block-Body with a Return-Statement that contains a Switch-Expression
|
||||||
|
var returnStmt = (ReturnStatementSyntax)methodDecl.Body.Statements[0];
|
||||||
|
if (returnStmt.Expression is not SwitchExpressionSyntax matchingSwitchExpr)
|
||||||
|
return document;
|
||||||
|
|
||||||
|
switchExpr = matchingSwitchExpr;
|
||||||
|
governingExpression = switchExpr.GoverningExpression;
|
||||||
|
|
||||||
|
// Extract the switch body text:
|
||||||
|
var switchStart = switchExpr.SwitchKeyword.SpanStart;
|
||||||
|
var switchEnd = switchExpr.CloseBraceToken.Span.End;
|
||||||
|
switchBodyText = sourceText.ToString(TextSpan.FromBounds(switchStart, switchEnd));
|
||||||
|
}
|
||||||
|
else if (methodDecl.ExpressionBody != null)
|
||||||
|
{
|
||||||
|
// Case 2: Expression-Body with a poorly formatted Switch-Expression
|
||||||
|
switchExpr = (SwitchExpressionSyntax)methodDecl.ExpressionBody.Expression;
|
||||||
|
governingExpression = switchExpr.GoverningExpression;
|
||||||
|
|
||||||
|
// Extract the switch body text:
|
||||||
|
var switchStart = switchExpr.SwitchKeyword.SpanStart;
|
||||||
|
var switchEnd = switchExpr.CloseBraceToken.Span.End;
|
||||||
|
switchBodyText = sourceText.ToString(TextSpan.FromBounds(switchStart, switchEnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (switchExpr is null || governingExpression is null)
|
||||||
|
return document;
|
||||||
|
|
||||||
|
// Extract the governing expression and the switch body:
|
||||||
|
var govExprText = sourceText.ToString(governingExpression.Span);
|
||||||
|
|
||||||
|
// Create the new method with inline expression body and correct formatting:
|
||||||
|
var returnTypeText = methodDecl.ReturnType.ToString();
|
||||||
|
var modifiersText = string.Join(" ", methodDecl.Modifiers);
|
||||||
|
var methodNameText = methodDecl.Identifier.Text;
|
||||||
|
|
||||||
|
// Determine the indentation of the method:
|
||||||
|
var methodIndentation = "";
|
||||||
|
for (var i = methodStartLine.Start; i < methodDecl.SpanStart; i++)
|
||||||
|
{
|
||||||
|
if (char.IsWhiteSpace(sourceText[i]))
|
||||||
|
methodIndentation += sourceText[i];
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erstelle die neue Methode mit Expression-Body und korrekter Formatierung
|
||||||
|
var newMethodText = new StringBuilder();
|
||||||
|
newMethodText.Append($"{modifiersText} {returnTypeText} {methodNameText}{parameterText} => {govExprText} switch");
|
||||||
|
|
||||||
|
// Formatiere die geschweiften Klammern und den Switch-Body
|
||||||
|
var switchBody = switchBodyText.Substring("switch".Length).Trim();
|
||||||
|
|
||||||
|
// Bestimme die Einrückung für die Switch-Cases (4 Spaces oder 1 Tab mehr als die Methode)
|
||||||
|
var caseIndentation = methodIndentation + " "; // 4 Spaces Einrückung
|
||||||
|
|
||||||
|
// Verarbeite die Klammern und formatiere den Body
|
||||||
|
var formattedSwitchBody = FormatSwitchBody(switchBody, methodIndentation, caseIndentation);
|
||||||
|
newMethodText.Append(formattedSwitchBody);
|
||||||
|
|
||||||
|
// Ersetze die alte Methoden-Deklaration mit dem neuen Text
|
||||||
|
var newText = sourceText.Replace(methodDecl.Span, newMethodText.ToString());
|
||||||
|
return document.WithText(newText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatSwitchBody(string switchBody, string methodIndentation, string caseIndentation)
|
||||||
|
{
|
||||||
|
var result = new StringBuilder();
|
||||||
|
|
||||||
|
// Remove braces from the switch body:
|
||||||
|
var bodyWithoutBraces = switchBody.Trim();
|
||||||
|
if (bodyWithoutBraces.StartsWith("{"))
|
||||||
|
bodyWithoutBraces = bodyWithoutBraces.Substring(1);
|
||||||
|
if (bodyWithoutBraces.EndsWith("}"))
|
||||||
|
bodyWithoutBraces = bodyWithoutBraces.Substring(0, bodyWithoutBraces.Length - 1);
|
||||||
|
|
||||||
|
bodyWithoutBraces = bodyWithoutBraces.Trim();
|
||||||
|
|
||||||
|
// Add braces with correct indentation:
|
||||||
|
result.AppendLine();
|
||||||
|
result.Append($"{methodIndentation}{{");
|
||||||
|
|
||||||
|
// Process each line of the switch body:
|
||||||
|
var lines = bodyWithoutBraces.Split(["\r\n", "\n"], System.StringSplitOptions.None);
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
result.AppendLine();
|
||||||
|
|
||||||
|
var trimmedLine = line.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(trimmedLine))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Add correct indentation for each case:
|
||||||
|
result.Append(caseIndentation);
|
||||||
|
result.Append(trimmedLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the closing brace with correct indentation:
|
||||||
|
result.AppendLine();
|
||||||
|
result.Append($"{methodIndentation}}};");
|
||||||
|
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user