using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace FrenchExDev.Net.State.Economy.Dsl.Analyzers;
///
/// ECO003 — SectorOperationMismatch.
///
/// Vérifie la compatibilité des couples (secteur × opération) au sens SEC 2010.
/// Un membre décoré [SectorOperation(sectorType, operationType)] dont
/// le couple n'est pas accepté par la table Eurostat (ex : B1g sur S14)
/// échoue la compilation. La table est encodée dans
/// SectorOperationCompatibility.IsCompatible.
///
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class Eco003SectorOperationMismatchAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "ECO003";
private const string Title = "Opération SEC 2010 incompatible avec le secteur";
private const string MessageFormat =
"Opération '{0}' déclarée sur secteur '{1}' : couple incompatible au sens SEC 2010 "
+ "(Manuel Eurostat, Annexe A). Corrigez le secteur ou l'opération.";
private const string Description =
"Les tables de comptes nationaux imposent des contraintes fines de "
+ "compatibilité entre secteurs institutionnels et opérations économiques. "
+ "L'analyzer les encode en dur pour rendre impossible une observation "
+ "sémantiquement incohérente.";
private const string Category = "StateEconomy";
private const string HelpLink =
"https://ec.europa.eu/eurostat/web/esa-2010/overview";
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.RegisterSymbolAction(AnalyzeMember, SymbolKind.Property, SymbolKind.Field);
}
private static void AnalyzeMember(SymbolAnalysisContext ctx)
{
var member = ctx.Symbol;
var attr = member.GetAttributes().FirstOrDefault(a =>
a.AttributeClass?.Name == "SectorOperationAttribute");
if (attr is null) return;
if (attr.ConstructorArguments.Length < 2) return;
if (attr.ConstructorArguments[0].Value is not INamedTypeSymbol sector) return;
if (attr.ConstructorArguments[1].Value is not INamedTypeSymbol operation) return;
if (IsCompatible(sector, operation)) return;
var loc = member.Locations.FirstOrDefault() ?? Location.None;
var diagnostic = Diagnostic.Create(Rule, loc, operation.Name, sector.Name);
ctx.ReportDiagnostic(diagnostic);
}
// Réplique de SectorOperationCompatibility.IsCompatible, exécutée sur
// les symboles Roslyn plutôt que sur des Type runtime.
private static bool IsCompatible(INamedTypeSymbol sector, INamedTypeSymbol operation)
{
var op = operation.Name;
var sec = sector.Name;
if (op == "B1g_GrossValueAdded")
{
return sec == "S11_NonFinancialCorporations"
|| sec == "S12_FinancialCorporations"
|| sec == "S13_GeneralGovernment"
|| sec == "S1311_CentralGovernment"
|| sec == "S1313_LocalGovernment"
|| sec == "S1314_SocialSecurityFunds"
|| sec == "S15_NPISH";
}
if (op == "P51g_GrossFixedCapitalFormation")
{
return sec != "S2_RestOfWorld";
}
return true;
}
}