mirror of
				https://github.com/MindWorkAI/AI-Studio.git
				synced 2025-10-31 04:40:20 +00:00 
			
		
		
		
	Added switch expression method analyzer & code fix
This commit is contained in:
		
							parent
							
								
									db9bb8f487
								
							
						
					
					
						commit
						319d3f9b94
					
				| @ -2,10 +2,11 @@ | ||||
| 
 | ||||
| ### New Rules | ||||
| 
 | ||||
|  Rule ID   | Category | Severity | Notes                        | ||||
| -----------|----------|----------|----------------------------- | ||||
|  MWAIS0001 | Usage    | Error    | ProviderAccessAnalyzer       | ||||
|  MWAIS0002 | Naming   | Error    | ConstStaticAnalyzer          | ||||
|  MWAIS0003 | Naming   | Error    | UnderscorePrefixAnalyzer     | ||||
|  MWAIS0004 | Usage    | Error    | RandomInstantiationAnalyzer  | ||||
|  MWAIS0005 | Usage    | Error    | ThisUsageAnalyzer            | ||||
|  Rule ID   | Category | Severity | Notes                           | ||||
| -----------|----------|----------|-------------------------------- | ||||
|  MWAIS0001 | Usage    | Error    | ProviderAccessAnalyzer          | ||||
|  MWAIS0002 | Naming   | Error    | ConstStaticAnalyzer             | ||||
|  MWAIS0003 | Naming   | Error    | UnderscorePrefixAnalyzer        | ||||
|  MWAIS0004 | Usage    | Error    | RandomInstantiationAnalyzer     | ||||
|  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 RANDOM_INSTANTIATION_ANALYZER = $"{Tools.ID_PREFIX}0004"; | ||||
|     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