@@ -12,91 +12,105 @@ namespace FunctionalStateMachine.Diagrams
1212 [ Generator ]
1313 public sealed class StateMachineDiagramGenerator : IIncrementalGenerator
1414 {
15- private const string AttributeName = "FunctionalStateMachine.Diagrams.StateMachineDiagramAttribute" ;
15+ private const string AttributeName = "FunctionalStateMachine.Diagrams.StateMachineDiagramAttribute" ;
1616
17- public void Initialize ( IncrementalGeneratorInitializationContext context )
18- {
19- context . RegisterPostInitializationOutput ( ctx =>
17+ public void Initialize ( IncrementalGeneratorInitializationContext context )
2018 {
21- ctx . AddSource ( "StateMachineDiagramAttribute.g.cs" , """
22- using System;
19+ context . RegisterPostInitializationOutput ( ctx =>
20+ {
21+ ctx . AddSource (
22+ "StateMachineDiagramAttribute.g.cs" ,
23+ """
24+ using System;
2325
24- namespace FunctionalStateMachine.Diagrams
25- {
26- [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
26+ namespace FunctionalStateMachine.Diagrams
27+ {
28+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
2729 internal sealed class StateMachineDiagramAttribute : Attribute
2830 {
29- public StateMachineDiagramAttribute(string name )
31+ public StateMachineDiagramAttribute(string outputPath )
3032 {
31- Name = name ;
33+ OutputPath = outputPath ;
3234 }
3335
34- public string Name { get; }
36+ public string OutputPath { get; }
3537 }
3638 }
3739 """ ) ;
3840 } ) ;
3941
40- var diagrams = context . SyntaxProvider . ForAttributeWithMetadataName (
41- AttributeName ,
42- static ( node , _ ) => node is MethodDeclarationSyntax ,
43- static ( ctx , _ ) => ( MethodDeclarationSyntax ) ctx . TargetNode )
44- . Combine ( context . CompilationProvider )
45- . Combine ( context . AnalyzerConfigOptionsProvider ) ;
42+ var diagrams = context . SyntaxProvider . ForAttributeWithMetadataName (
43+ AttributeName ,
44+ static ( node , _ ) => node is MethodDeclarationSyntax ,
45+ static ( ctx , _ ) => ( MethodDeclarationSyntax ) ctx . TargetNode )
46+ . Combine ( context . CompilationProvider )
47+ . Combine ( context . AnalyzerConfigOptionsProvider ) ;
4648
47- context . RegisterSourceOutput ( diagrams , static ( ctx , data ) =>
48- {
49- var ( ( methodSyntax , compilation ) , options ) = data ;
50- if ( ! options . GlobalOptions . TryGetValue ( "build_property.ProjectDir" , out var projectDir ) )
51- {
52- return ;
53- }
49+ context . RegisterSourceOutput (
50+ diagrams ,
51+ static ( ctx , data ) =>
52+ {
53+ var ( ( methodSyntax , compilation ) , options ) = data ;
54+ if ( ! options . GlobalOptions . TryGetValue ( "build_property.ProjectDir" , out var projectDir ) )
55+ {
56+ return ;
57+ }
5458
55- var model = compilation . GetSemanticModel ( methodSyntax . SyntaxTree ) ;
56- if ( model . GetDeclaredSymbol ( methodSyntax , ctx . CancellationToken ) is not IMethodSymbol methodSymbol )
57- {
58- return ;
59- }
59+ var model = compilation . GetSemanticModel ( methodSyntax . SyntaxTree ) ;
60+ if ( model . GetDeclaredSymbol ( methodSyntax , ctx . CancellationToken ) is not IMethodSymbol methodSymbol )
61+ {
62+ return ;
63+ }
64+
65+ var attribute = methodSymbol . GetAttributes ( )
66+ . FirstOrDefault ( attr => attr . AttributeClass ? . ToDisplayString ( ) == AttributeName ) ;
67+ if ( attribute is null )
68+ {
69+ return ;
70+ }
6071
61- var attribute = methodSymbol . GetAttributes ( )
62- . FirstOrDefault ( attr => attr . AttributeClass ? . ToDisplayString ( ) == AttributeName ) ;
63- if ( attribute is null )
72+ var outputPathValue = attribute . ConstructorArguments . Length == 1
73+ ? attribute . ConstructorArguments [ 0 ] . Value ? . ToString ( )
74+ : null ;
75+ if ( string . IsNullOrWhiteSpace ( outputPathValue ) )
6476 {
65- return ;
77+ outputPathValue = $ " { methodSymbol . Name } .md" ;
6678 }
6779
68- var diagramName = attribute . ConstructorArguments . Length == 1
69- ? attribute . ConstructorArguments [ 0 ] . Value ? . ToString ( )
70- : methodSymbol . Name ;
80+ var diagramName = Path . GetFileNameWithoutExtension ( outputPathValue ) ;
7181 if ( string . IsNullOrWhiteSpace ( diagramName ) )
7282 {
7383 diagramName = methodSymbol . Name ;
7484 }
7585
76- var chains = DiagramBuilder . GetInvocationChains ( methodSyntax ) ;
77- var diagram = DiagramBuilder . BuildDiagram ( diagramName ! , chains ) ;
78- if ( diagram is null )
79- {
80- return ;
81- }
82-
83- var outputDir = Path . Combine ( projectDir , "diagrams" ) ;
84- Directory . CreateDirectory ( outputDir ) ;
85- var outputPath = Path . Combine ( outputDir , $ "{ diagramName } .md") ;
86+ var chains = DiagramBuilder . GetInvocationChains ( methodSyntax ) ;
87+ var diagram = DiagramBuilder . BuildDiagram ( diagramName ! , chains ) ;
88+ if ( diagram is null )
89+ {
90+ return ;
91+ }
8692
87- if ( File . Exists ( outputPath ) )
93+ var outputPath = Path . IsPathRooted ( outputPathValue )
94+ ? outputPathValue
95+ : Path . Combine ( projectDir , outputPathValue ) ;
96+ var outputDir = Path . GetDirectoryName ( outputPath ) ;
97+ if ( ! string . IsNullOrWhiteSpace ( outputDir ) )
8898 {
89- var existing = File . ReadAllText ( outputPath ) ;
90- if ( string . Equals ( existing , diagram , StringComparison . Ordinal ) )
91- {
92- return ;
93- }
99+ Directory . CreateDirectory ( outputDir ) ;
94100 }
95101
96- File . WriteAllText ( outputPath , diagram ) ;
97- } ) ;
98- }
102+ if ( File . Exists ( outputPath ) )
103+ {
104+ var existing = File . ReadAllText ( outputPath ) ;
105+ if ( string . Equals ( existing , diagram , StringComparison . Ordinal ) )
106+ {
107+ return ;
108+ }
109+ }
99110
111+ File . WriteAllText ( outputPath , diagram ) ;
112+ } ) ;
113+ }
100114 }
101115}
102116
@@ -146,17 +160,20 @@ public static List<List<InvocationInfo>> GetInvocationChains(MethodDeclarationSy
146160 {
147161 states . Add ( startState ) ;
148162 }
163+
149164 break ;
150165 case "For" :
151166 if ( pendingTrigger && ! hasTransition && currentState != null && currentTrigger != null )
152167 {
153168 transitions . Add ( new Transition ( currentState , currentState , currentTrigger ) ) ;
154169 }
170+
155171 currentState = GetFirstArg ( step ) ;
156172 if ( currentState != null )
157173 {
158174 states . Add ( currentState ) ;
159175 }
176+
160177 currentTrigger = null ;
161178 pendingTrigger = false ;
162179 hasTransition = false ;
@@ -168,12 +185,14 @@ public static List<List<InvocationInfo>> GetInvocationChains(MethodDeclarationSy
168185 childToParent [ currentState ] = parent ;
169186 states . Add ( parent ) ;
170187 }
188+
171189 break ;
172190 case "On" :
173191 if ( pendingTrigger && ! hasTransition && currentState != null && currentTrigger != null )
174192 {
175193 transitions . Add ( new Transition ( currentState , currentState , currentTrigger ) ) ;
176194 }
195+
177196 currentTrigger = GetTriggerLabel ( step ) ;
178197 pendingTrigger = currentTrigger != null ;
179198 hasTransition = false ;
@@ -188,13 +207,15 @@ public static List<List<InvocationInfo>> GetInvocationChains(MethodDeclarationSy
188207 hasTransition = true ;
189208 pendingTrigger = false ;
190209 }
210+
191211 break ;
192212 case "Build" :
193213 if ( pendingTrigger && ! hasTransition && currentState != null && currentTrigger != null )
194214 {
195215 transitions . Add ( new Transition ( currentState , currentState , currentTrigger ) ) ;
196216 pendingTrigger = false ;
197217 }
218+
198219 break ;
199220 }
200221 }
@@ -376,12 +397,13 @@ private static void RenderState(
376397 {
377398 sb . AppendLine ( $ "{ Indent ( depth ) } { ids [ state ] } [{ state } ]") ;
378399 }
400+
379401 return ;
380402 }
381403
382404 sb . AppendLine ( $ "{ Indent ( depth ) } subgraph SG_{ Sanitize ( state ) } [{ state } ]") ;
383405 var shouldRenderParentNode = transitionStates . Contains ( state )
384- || string . Equals ( startState , state , StringComparison . Ordinal ) ;
406+ || string . Equals ( startState , state , StringComparison . Ordinal ) ;
385407 if ( shouldRenderParentNode && rendered . Add ( state ) )
386408 {
387409 sb . AppendLine ( $ "{ Indent ( depth + 1 ) } { ids [ state ] } [{ state } ]") ;
0 commit comments