using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace FrenchExDev.Net.State.Economy.Dsl.Analyzers;
///
/// ECO001 — DeficitAboveMaastricht.
///
/// Émet un diagnostic quand une classe
/// décorée [LoiDeFinances] expose une propriété retournant
/// DeficitRatio dont la valeur littérale dépasse le seuil Maastricht
/// (3% du PIB, art. 126 TFUE + protocole n°12) sans attribut de justification.
///
/// Attributs acceptés comme justification :
///
/// - [Article126TfeuJustification(CouncilRecommendation, CorrectiveDeadline)]
/// - [ExcessiveDeficitProcedure(Opened: true, OpenedDate)]
///
///
/// Le diagnostic est voulu compile-time : un commentaire en prose
/// ne suffit pas, seul l'attribut compte. Principe « zéro string-as-pivot »
/// (part 6 de la série metacratie-compilateur).
///
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class Eco001DeficitAboveMaastrichtAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "ECO001";
private const string Title = "Déficit projeté au-delà du seuil Maastricht";
private const string MessageFormat =
"Déficit projeté {0:P2} > seuil Maastricht 3% sans [Article126TfeuJustification] ni "
+ "[ExcessiveDeficitProcedure(Opened: true)]. Joignez une justification ou ramenez "
+ "le ratio sous le seuil.";
private const string Description =
"SEC 2010 / art. 126 TFUE + protocole n°12 : le déficit des administrations "
+ "publiques (S13) ne doit pas dépasser 3% du PIB sauf justification formelle. "
+ "Voir procédure pour déficit excessif (PDE).";
private const string Category = "StateEconomy";
private const string HelpLink =
"https://eur-lex.europa.eu/legal-content/FR/TXT/?uri=CELEX:12016E126";
private static readonly DiagnosticDescriptor Rule = new(
id: DiagnosticId,
title: Title,
messageFormat: MessageFormat,
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
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.RegisterSyntaxNodeAction(AnalyzeClass, SyntaxKind.ClassDeclaration);
}
private static void AnalyzeClass(SyntaxNodeAnalysisContext ctx)
{
var classDecl = (ClassDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl, ctx.CancellationToken);
if (symbol is null) return;
if (!IsBudgetAct(symbol)) return;
if (HasJustification(symbol)) return;
foreach (var prop in classDecl.Members.OfType())
{
if (!TryGetLiteralDeficit(ctx, prop, out var value)) continue;
if (value <= DeficitThreshold) continue;
var diagnostic = Diagnostic.Create(Rule, prop.GetLocation(), value);
ctx.ReportDiagnostic(diagnostic);
}
}
private const decimal DeficitThreshold = 0.03m;
private static bool IsBudgetAct(INamedTypeSymbol symbol) =>
symbol.GetAttributes().Any(a =>
a.AttributeClass?.Name == "LoiDeFinancesAttribute");
private static bool HasJustification(INamedTypeSymbol symbol)
{
foreach (var attr in symbol.GetAttributes())
{
var name = attr.AttributeClass?.Name;
if (name == "Article126TfeuJustificationAttribute") return true;
if (name == "ExcessiveDeficitProcedureAttribute"
&& attr.ConstructorArguments.Length >= 1
&& attr.ConstructorArguments[0].Value is bool opened
&& opened)
{
return true;
}
}
return false;
}
private static bool TryGetLiteralDeficit(
SyntaxNodeAnalysisContext ctx,
PropertyDeclarationSyntax prop,
out decimal value)
{
value = 0m;
var propSymbol = ctx.SemanticModel.GetDeclaredSymbol(prop, ctx.CancellationToken);
if (propSymbol is null) return false;
if (propSymbol.Type.Name != "DeficitRatio") return false;
// Supporte: public DeficitRatio X => DeficitRatio.Of(0.048m);
ExpressionSyntax? expr = prop.ExpressionBody?.Expression;
if (expr is null) return false;
if (expr is not InvocationExpressionSyntax invocation) return false;
if (invocation.ArgumentList.Arguments.Count != 1) return false;
var arg = invocation.ArgumentList.Arguments[0].Expression;
if (arg is not LiteralExpressionSyntax literal) return false;
if (!literal.IsKind(SyntaxKind.NumericLiteralExpression)) return false;
if (literal.Token.Value is decimal d)
{
value = d;
return true;
}
return false;
}
}