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";
});
}