diff --git a/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md b/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md index 62f03de9..e6f97e74 100644 --- a/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md +++ b/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md @@ -10,4 +10,5 @@ MWAIS0004 | Usage | Error | RandomInstantiationAnalyzer MWAIS0005 | Usage | Error | ThisUsageAnalyzer MWAIS0006 | Style | Error | SwitchExpressionMethodAnalyzer - MWAIS0007 | Usage | Error | EmptyStringAnalyzer \ No newline at end of file + MWAIS0007 | Usage | Error | EmptyStringAnalyzer + MWAIS0008 | Naming | Error | LocalConstantsAnalyzer \ No newline at end of file diff --git a/app/SourceCodeRules/SourceCodeRules/Identifier.cs b/app/SourceCodeRules/SourceCodeRules/Identifier.cs index 80c02bc6..aa782cf9 100644 --- a/app/SourceCodeRules/SourceCodeRules/Identifier.cs +++ b/app/SourceCodeRules/SourceCodeRules/Identifier.cs @@ -9,4 +9,5 @@ public static class Identifier public const string THIS_USAGE_ANALYZER = $"{Tools.ID_PREFIX}0005"; public const string SWITCH_EXPRESSION_METHOD_ANALYZER = $"{Tools.ID_PREFIX}0006"; public const string EMPTY_STRING_ANALYZER = $"{Tools.ID_PREFIX}0007"; + public const string LOCAL_CONSTANTS_ANALYZER = $"{Tools.ID_PREFIX}0008"; } \ No newline at end of file diff --git a/app/SourceCodeRules/SourceCodeRules/NamingAnalyzers/LocalConstantsAnalyzer.cs b/app/SourceCodeRules/SourceCodeRules/NamingAnalyzers/LocalConstantsAnalyzer.cs new file mode 100644 index 00000000..da0e130e --- /dev/null +++ b/app/SourceCodeRules/SourceCodeRules/NamingAnalyzers/LocalConstantsAnalyzer.cs @@ -0,0 +1,63 @@ +using System.Collections.Immutable; +using System.Linq; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace SourceCodeRules.NamingAnalyzers; + +#pragma warning disable RS1038 +[DiagnosticAnalyzer(LanguageNames.CSharp)] +#pragma warning restore RS1038 +public sealed class LocalConstantsAnalyzer : DiagnosticAnalyzer +{ + private const string DIAGNOSTIC_ID = Identifier.LOCAL_CONSTANTS_ANALYZER; + + private static readonly string TITLE = "Local constant variables must be in UPPER_CASE"; + + private static readonly string MESSAGE_FORMAT = "Local constant variable '{0}' must be in UPPER_CASE"; + + private static readonly string DESCRIPTION = "All local constant variables should be named using UPPER_CASE with words separated by underscores."; + + private const string CATEGORY = "Naming"; + + 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(AnalyzeLocalDeclaration, SyntaxKind.LocalDeclarationStatement); + } + + private static void AnalyzeLocalDeclaration(SyntaxNodeAnalysisContext context) + { + var localDeclaration = (LocalDeclarationStatementSyntax)context.Node; + if (!localDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.ConstKeyword))) + return; + + foreach (var variable in localDeclaration.Declaration.Variables) + { + var variableName = variable.Identifier.Text; + if (!IsUpperCase(variableName)) + { + var diagnostic = Diagnostic.Create(RULE, variable.Identifier.GetLocation(), variableName); + context.ReportDiagnostic(diagnostic); + } + } + } + + private static bool IsUpperCase(string name) => name.All(c => char.IsUpper(c) || char.IsDigit(c) || c == '_') && + !string.IsNullOrEmpty(name) && name.Any(char.IsLetter); +} \ No newline at end of file