Forge
csharp4405de34
1using System.Collections.Immutable;
2using Microsoft.CodeAnalysis;
3using Microsoft.CodeAnalysis.CSharp;
4using Microsoft.CodeAnalysis.CSharp.Syntax;
5using Microsoft.CodeAnalysis.Diagnostics;
6
7namespace CascadeIDE.ArchitectureAnalyzers;
8
9/// <summary>
10/// ADR 0066: <c>Features/UiChrome</c> (presentation shell) не импортирует отрисовку/палитру кабины
11/// (<c>Cockpit/PrimitivesKit</c>); <c>Cockpit/PrimitivesKit</c> не импортирует <c>Features.UiChrome</c>.
12/// </summary>
13[DiagnosticAnalyzer(LanguageNames.CSharp)]
14public sealed class CockpitUiPresentationBoundaryAnalyzer : DiagnosticAnalyzer
15{
16 public const string UiChromeMustNotUsePrimitivesKitId = "CASCOPE011";
17 public const string PrimitivesKitMustNotUseUiChromeId = "CASCOPE012";
18
19 private static readonly DiagnosticDescriptor UiChromeRule = new(
20 UiChromeMustNotUsePrimitivesKitId,
21 "UiChrome не должен ссылаться на Cockpit.PrimitivesKit",
22 "Features/UiChrome (ADR 0066) не должен импортировать CascadeIDE.Cockpit.PrimitivesKit; оверлеи и хром — отдельно от отрисовки приборов (найдено: {0})",
23 "Architecture",
24 DiagnosticSeverity.Error,
25 isEnabledByDefault: true,
26 description: "Смешение presentation IDE и библиотеки отрисовки deck/кабины; используйте тему/CascadeTheme или вынесите общее в нейтральный слой.");
27
28 private static readonly DiagnosticDescriptor PrimitivesKitRule = new(
29 PrimitivesKitMustNotUseUiChromeId,
30 "PrimitivesKit не должен ссылаться на Features.UiChrome",
31 "Cockpit/PrimitivesKit (ADR 0066) не должен импортировать CascadeIDE.Features.UiChrome; граница Surface/Views для зон — отдельно (см. ADR 0036 CASCOPE002 в других слоях Cockpit).",
32 "Architecture",
33 DiagnosticSeverity.Error,
34 isEnabledByDefault: true,
35 description: "Отрисовка приборов не зависит от контролов зон и хрома IDE.");
36
37 public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
38 ImmutableArray.Create(UiChromeRule, PrimitivesKitRule);
39
40 public override void Initialize(AnalysisContext context)
41 {
42 context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
43 context.EnableConcurrentExecution();
44 context.RegisterSyntaxNodeAction(AnalyzeUsingDirective, SyntaxKind.UsingDirective);
45 }
46
47 private static void AnalyzeUsingDirective(SyntaxNodeAnalysisContext context)
48 {
49 if (context.Node is not UsingDirectiveSyntax u)
50 return;
51
52 var path = context.Node.SyntaxTree.FilePath;
53 var name = u.Name?.ToString() ?? "";
54
55 if (IsFeaturesUiChromePath(path))
56 {
57 if (name.StartsWith("CascadeIDE.Cockpit.PrimitivesKit", StringComparison.Ordinal)
58 || name == "CascadeIDE.Cockpit.PrimitivesKit")
59 {
60 context.ReportDiagnostic(Diagnostic.Create(UiChromeRule, u.GetLocation(), name));
61 }
62
63 return;
64 }
65
66 if (IsCockpitPrimitivesKitPath(path))
67 {
68 if (name.StartsWith("CascadeIDE.Features.UiChrome", StringComparison.Ordinal)
69 || name == "CascadeIDE.Features.UiChrome")
70 {
71 context.ReportDiagnostic(Diagnostic.Create(PrimitivesKitRule, u.GetLocation()));
72 }
73 }
74 }
75
76 private static bool IsFeaturesUiChromePath(string? filePath)
77 {
78 if (string.IsNullOrEmpty(filePath))
79 return false;
80 var n = filePath.Replace('\\', '/');
81 return n.Contains("/Features/UiChrome/", StringComparison.OrdinalIgnoreCase);
82 }
83
84 private static bool IsCockpitPrimitivesKitPath(string? filePath)
85 {
86 if (string.IsNullOrEmpty(filePath))
87 return false;
88 var n = filePath.Replace('\\', '/');
89 return n.Contains("/Cockpit/PrimitivesKit/", StringComparison.OrdinalIgnoreCase);
90 }
91}
92
View only · write via MCP/CIDE