using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace FrenchExDev.Net.State.Economy.Dsl.Analyzers; /// /// ECO004 — UnauditedClosureAct. /// /// Une loi de règlement (FR) ou un acte de clôture budgétaire (US) doit /// être soumis à un audit externe avant promulgation — Cour des comptes /// en France (Const. art. 47-2 + LOLF art. 58), Government Accountability /// Office aux États-Unis (31 U.S.C. § 712). L'analyzer émet un /// warning opt-in Error quand une classe porte [ClosureAct] /// sans [CourDesComptesOpinion] ni [GaoAudit]. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class Eco004UnauditedClosureActAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "ECO004"; private const string Title = "Acte de clôture budgétaire non audité"; private const string MessageFormat = "'{0}' est déclarée [ClosureAct({1})] mais ne porte ni [CourDesComptesOpinion] " + "ni [GaoAudit]. Un acte de clôture doit être audité par un organe indépendant."; private const string Description = "La certification des comptes par un auditeur externe indépendant est un " + "pré-requis démocratique à la décharge donnée à l'exécutif. Manquante, " + "elle invalide la chaîne de responsabilité budgétaire."; private const string Category = "StateEconomy"; private const string HelpLink = "https://www.ccomptes.fr/fr/competences-et-procedures/certification-des-comptes"; private static readonly DiagnosticDescriptor Rule = new( id: DiagnosticId, title: Title, messageFormat: MessageFormat, category: Category, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description, helpLinkUri: HelpLink); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType); } private static void AnalyzeType(SymbolAnalysisContext ctx) { var symbol = (INamedTypeSymbol)ctx.Symbol; var closure = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "ClosureActAttribute"); if (closure is null) return; if (HasAudit(symbol)) return; var year = closure.ConstructorArguments.Length >= 1 ? closure.ConstructorArguments[0].Value?.ToString() ?? "?" : "?"; var loc = symbol.Locations.FirstOrDefault() ?? Location.None; var diagnostic = Diagnostic.Create(Rule, loc, symbol.Name, year); ctx.ReportDiagnostic(diagnostic); } private static bool HasAudit(INamedTypeSymbol symbol) => symbol.GetAttributes().Any(a => { var name = a.AttributeClass?.Name; return name == "CourDesComptesOpinionAttribute" || name == "GaoAuditAttribute"; }); }