From d91b11e32adf398a3d90b3388acffbfe1bd4788d Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 9 Jun 2026 10:28:04 +0200 Subject: [PATCH] Added detection for compile-time constant patterns --- .../UsageAnalyzers/EmptyStringAnalyzer.cs | 84 +++++++++++-------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs index 5092d436..c4fe1392 100644 --- a/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs +++ b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs @@ -13,76 +13,94 @@ namespace SourceCodeRules.UsageAnalyzers; public sealed class EmptyStringAnalyzer : DiagnosticAnalyzer { private const string DIAGNOSTIC_ID = Identifier.EMPTY_STRING_ANALYZER; - + private static readonly string TITLE = """ Use string.Empty instead of "" """; - + private static readonly string MESSAGE_FORMAT = """ Use string.Empty instead of "" """; - - private static readonly string DESCRIPTION = """Empty string literals ("") should be replaced with string.Empty for better code consistency and readability except in const contexts."""; - + + private static readonly string DESCRIPTION = """Empty string literals ("") should be replaced with string.Empty for better code consistency and readability except in contexts requiring compile-time constants."""; + private const string CATEGORY = "Usage"; - + private static readonly DiagnosticDescriptor RULE = new(DIAGNOSTIC_ID, TITLE, MESSAGE_FORMAT, CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true, description: DESCRIPTION); - + public override ImmutableArray SupportedDiagnostics => [RULE]; - + public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSyntaxNodeAction(AnalyzeEmptyStringLiteral, SyntaxKind.StringLiteralExpression); } - + private static void AnalyzeEmptyStringLiteral(SyntaxNodeAnalysisContext context) { var stringLiteral = (LiteralExpressionSyntax)context.Node; if (stringLiteral.Token.ValueText != string.Empty) return; - - if (IsInConstContext(stringLiteral)) + + if (RequiresCompileTimeConstant(stringLiteral)) return; - - if (IsInParameterDefaultValue(stringLiteral)) - return; - + var diagnostic = Diagnostic.Create(RULE, stringLiteral.GetLocation()); context.ReportDiagnostic(diagnostic); } - - private static bool IsInConstContext(LiteralExpressionSyntax stringLiteral) + + private static bool RequiresCompileTimeConstant(LiteralExpressionSyntax stringLiteral) + { + return IsInConstDeclarationInitializer(stringLiteral) + || IsInParameterDefaultValue(stringLiteral) + || IsInAttributeArgument(stringLiteral) + || IsInSwitchCaseLabel(stringLiteral) + || IsInConstantPattern(stringLiteral); + } + + private static bool IsInConstDeclarationInitializer(LiteralExpressionSyntax stringLiteral) { var variableDeclarator = stringLiteral.FirstAncestorOrSelf(); - if (variableDeclarator is null) + if (variableDeclarator?.Initializer is null || !ContainsNode(variableDeclarator.Initializer.Value, stringLiteral)) return false; - + var declaration = variableDeclarator.Parent?.Parent; return declaration switch { FieldDeclarationSyntax fieldDeclaration => fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword), LocalDeclarationStatementSyntax localDeclaration => localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword), - + _ => false }; } - + private static bool IsInParameterDefaultValue(LiteralExpressionSyntax stringLiteral) { - // Prüfen, ob das String-Literal Teil eines Parameter-Defaults ist var parameter = stringLiteral.FirstAncestorOrSelf(); - if (parameter is null) - return false; - - // Überprüfen, ob das String-Literal im Default-Wert des Parameters verwendet wird - if (parameter.Default is not null && - parameter.Default.Value == stringLiteral) - { - return true; - } - - return false; + return parameter?.Default is not null && ContainsNode(parameter.Default.Value, stringLiteral); + } + + private static bool IsInAttributeArgument(LiteralExpressionSyntax stringLiteral) + { + var attributeArgument = stringLiteral.FirstAncestorOrSelf(); + return attributeArgument is not null && ContainsNode(attributeArgument.Expression, stringLiteral); + } + + private static bool IsInSwitchCaseLabel(LiteralExpressionSyntax stringLiteral) + { + var caseSwitchLabel = stringLiteral.FirstAncestorOrSelf(); + return caseSwitchLabel is not null && ContainsNode(caseSwitchLabel.Value, stringLiteral); + } + + private static bool IsInConstantPattern(LiteralExpressionSyntax stringLiteral) + { + var constantPattern = stringLiteral.FirstAncestorOrSelf(); + return constantPattern is not null && ContainsNode(constantPattern.Expression, stringLiteral); + } + + private static bool ContainsNode(SyntaxNode parent, SyntaxNode child) + { + return parent.SpanStart <= child.SpanStart && child.Span.End <= parent.Span.End; } } \ No newline at end of file