Forge
csharpdeeb25a2
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/// Enforces Environment Readiness canonical pipeline:
11/// channel Build(context) -> surface compositor Compose(...).
12/// </summary>
13[DiagnosticAnalyzer(LanguageNames.CSharp)]
14public sealed class EnvironmentReadinessPipelineAnalyzer : DiagnosticAnalyzer
15{
16 public const string LegacySnapshotBuilderId = "CASCOPE006";
17
18 private static readonly DiagnosticDescriptor LegacySnapshotBuilderRule = new(
19 LegacySnapshotBuilderId,
20 "Environment Readiness: direct snapshot builder usage is forbidden in MainWindowViewModel",
21 "Use IEnvironmentReadinessChannel.Build(...) and IEnvironmentReadinessSurfaceCompositor.Compose(...) instead of EnvironmentReadinessSnapshotBuilder in MainWindowViewModel pipeline",
22 "Architecture",
23 DiagnosticSeverity.Error,
24 isEnabledByDefault: true,
25 description: "Environment Readiness VM pipeline must go through channel and surface compositor.");
26
27 public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
28 ImmutableArray.Create(LegacySnapshotBuilderRule);
29
30 public override void Initialize(AnalysisContext context)
31 {
32 context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
33 context.EnableConcurrentExecution();
34 context.RegisterSyntaxNodeAction(AnalyzeIdentifier, SyntaxKind.IdentifierName);
35 }
36
37 private static void AnalyzeIdentifier(SyntaxNodeAnalysisContext context)
38 {
39 if (!IsMainWindowViewModelFile(context.Node.SyntaxTree.FilePath))
40 return;
41 if (context.Node is not IdentifierNameSyntax id)
42 return;
43 if (!string.Equals(id.Identifier.ValueText, "EnvironmentReadinessSnapshotBuilder", StringComparison.Ordinal))
44 return;
45
46 context.ReportDiagnostic(Diagnostic.Create(LegacySnapshotBuilderRule, id.GetLocation()));
47 }
48
49 private static bool IsMainWindowViewModelFile(string? filePath)
50 {
51 if (string.IsNullOrWhiteSpace(filePath))
52 return false;
53 var normalized = filePath.Replace('\\', '/');
54 return normalized.Contains("/ViewModels/MainWindowViewModel", StringComparison.OrdinalIgnoreCase);
55 }
56}
57
View only · write via MCP/CIDE