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