1- using Moryx . Cli . Template . Exceptions ;
2- using Moryx . Cli . Template . Extensions ;
3- using System . Text . RegularExpressions ;
1+ using Microsoft . CodeAnalysis ;
2+ using Microsoft . CodeAnalysis . CSharp ;
3+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
4+ using Microsoft . CodeAnalysis . Formatting ;
5+ using Moryx . Cli . Template . Exceptions ;
46
57namespace Moryx . Cli . Template . StateBaseTemplate
68{
79 public partial class StateBaseTemplate
810 {
11+ private const string StateDefinitionAttributeName = "StateDefinition" ;
12+ private const string IsInitialParameterName = "IsInitial" ;
913 private readonly string _content ;
14+ private readonly SyntaxTree _syntaxTree ;
1015
1116 public StateBaseTemplate ( string content )
1217 {
1318 _content = content ;
14- Parse ( content ) ;
19+ _syntaxTree = CSharpSyntaxTree . ParseText ( _content ) ;
20+
21+ Parse ( _syntaxTree ) ;
22+
1523 }
1624
17- public IEnumerable < Constructor > Constructors { get ; private set ; } = new List < Constructor > ( ) ;
18- public IEnumerable < StateDefinition > StateDefinitions { get ; private set ; } = new List < StateDefinition > ( ) ;
25+ public IEnumerable < ConstructorDeclarationSyntax > Constructors { get ; private set ; } = [ ] ;
26+ public IEnumerable < StateDefinition > StateDeclarations { get ; private set ; } = [ ] ;
1927 public string Content { get => _content ; }
2028
2129 public static StateBaseTemplate FromFile ( string fileName )
@@ -24,85 +32,137 @@ public static StateBaseTemplate FromFile(string fileName)
2432 return new StateBaseTemplate ( content ) ;
2533 }
2634
27- private void Parse ( string content )
35+ private void Parse ( SyntaxTree syntaxTree )
2836 {
29- var ctors = new List < Constructor > ( ) ;
30- var states = new Dictionary < int , string > ( ) ;
31-
32- content . Split ( Environment . NewLine ) . Each ( ( line , i ) =>
33- {
34-
35- if ( Regex . Match ( line , @"public\s+\w+\(" ) . Success )
36- {
37- ctors . Add ( new Constructor { Line = i + 1 } ) ;
38- }
39- else {
40- var match = Regex . Match ( line , @"\[\s*StateDefinition\s*\(\s*typeof\s*\(\s*(\w+)" ) ;
41- if ( match . Success )
42- {
43- states . Add ( i , match . Groups [ 1 ] . Value ) ;
44- }
45- }
46- } ) ;
47-
37+ var root = syntaxTree . GetRoot ( ) ;
4838
49- StateDefinitions = ExtractStateDefinitions ( states , content ) ;
50- Constructors = ctors ;
39+ StateDeclarations = ExtractStateDefinitions ( root ) ;
40+ Constructors = ExtractConstructors ( root ) ;
5141 }
5242
53- private IEnumerable < StateDefinition > ExtractStateDefinitions ( Dictionary < int , string > states , string content )
43+ private IEnumerable < ConstructorDeclarationSyntax > ExtractConstructors ( SyntaxNode root )
5444 {
45+ return root . DescendantNodes ( ) . OfType < ConstructorDeclarationSyntax > ( ) . ToList ( ) ;
46+ }
47+
48+ private IEnumerable < StateDefinition > ExtractStateDefinitions ( SyntaxNode root )
49+ {
5550 var result = new List < StateDefinition > ( ) ;
56- var minifiedContent = content . Replace ( Environment . NewLine , "" ) ;
51+ var states = root
52+ . DescendantNodes ( )
53+ . OfType < FieldDeclarationSyntax > ( )
54+ . Where ( p => p . AttributeLists . Any ( a => a . Attributes . Any ( attr => attr . Name . ToString ( ) == StateDefinitionAttributeName ) ) ) ;
5755
5856 foreach ( var state in states )
5957 {
60- var start = minifiedContent . IndexOf ( $ "[StateDefinition(typeof({ state . Value } ") ;
61- var end = minifiedContent . IndexOf ( ";" , start ) ;
62- var s = minifiedContent . Substring ( start , end - start + 1 ) ;
63- var match = Regex . Match ( s , @"(\w+)\s*=\s*(\d+)\s*" ) ;
64- if ( match . Success ) {
65- result . Add ( new StateDefinition
66- {
67- Name = match . Groups [ 1 ] . Value ,
68- Type = state . Value ,
69- Value = Convert . ToInt32 ( match . Groups [ 2 ] . Value ) ,
70- IsInitial = Regex . Match ( s , @"IsInitial\s*=\s*true" ) . Success ,
71- Line = state . Key + 1 ,
72- } ) ;
73- }
58+ var attributeArguments = GetAttributeArguments ( state , StateDefinitionAttributeName ) ;
59+ var variable = state . Declaration . Variables . First ( ) ;
60+ result . Add ( new StateDefinition
61+ {
62+ Name = variable . Identifier . Text ,
63+ Type = attributeArguments . First ( a => a . Key == "" ) . Value ,
64+ Value = Convert . ToInt32 ( variable . Initializer ? . Value . ToString ( ) ) ,
65+ IsInitial = attributeArguments . Any ( a => a . Key == IsInitialParameterName && a . Value . ToString ( ) == "true" ) ,
66+ Node = state ,
67+ } ) ;
68+
7469 }
70+
7571 return result ;
7672 }
7773
74+ public List < KeyValuePair < string , string > > GetAttributeArguments ( FieldDeclarationSyntax field , string attributeName )
75+ {
76+ var semanticModel = CSharpCompilation . Create ( "SemanticModelCompilation" , [ _syntaxTree ] ) . GetSemanticModel ( _syntaxTree ) ;
77+
78+ return field . AttributeLists . Select (
79+ list => list . Attributes
80+ . Where ( attribute => attribute . Name . ToString ( ) == attributeName )
81+ . Select ( attribute =>
82+ attribute . ArgumentList ? . Arguments . Select ( a =>
83+ new KeyValuePair < string , string > (
84+ a . NameEquals ? . Name . Identifier . ValueText ?? "" ,
85+ ( a . Expression ) . ToString ( ) )
86+ )
87+ . ToList ( ) )
88+ . FirstOrDefault ( )
89+ ) . FirstOrDefault ( ) ?? [ ] ;
90+ }
91+
7892 public StateBaseTemplate AddState ( string stateType )
7993 {
80- if ( StateDefinitions . Any ( sd => sd . Type == stateType ) )
94+ if ( StateDeclarations . Any ( sd => sd . Type == $ "typeof( { stateType } )" ) )
8195 {
8296 throw new StateAlreadyExistsException ( stateType ) ;
8397 }
98+ int value = NextConst ( StateDeclarations ) ;
8499
85- var lines = _content . Split ( Environment . NewLine ) ;
86- var newLines = new List < string > ( ) ;
87- var ctorIndex = Constructors . First ( ) . Line - 1 ;
88- newLines . AddRange ( lines . Take ( ctorIndex ) ) ;
89- var initial = StateDefinitions . Any ( sd => sd . IsInitial )
90- ? ""
91- : ", IsInitial = true" ;
92- int value = NextConst ( StateDefinitions ) ;
100+ var parameters = new List < AttributeArgumentSyntax >
101+ {
102+ SyntaxFactory . AttributeArgument (
103+ SyntaxFactory . TypeOfExpression ( SyntaxFactory . ParseTypeName ( stateType ) ) )
104+ } ;
105+
106+ if ( ! StateDeclarations . Any ( sd => sd . IsInitial ) )
107+ {
108+ parameters . Add ( SyntaxFactory . AttributeArgument (
109+ SyntaxFactory . NameEquals ( IsInitialParameterName ) ,
110+ null ,
111+ SyntaxFactory . LiteralExpression ( SyntaxKind . TrueLiteralExpression ) ) ) ;
112+
113+ }
114+
115+ var attribute = SyntaxFactory . Attribute ( SyntaxFactory . ParseName ( StateDefinitionAttributeName ) )
116+ . WithArgumentList ( SyntaxFactory . AttributeArgumentList ( SyntaxFactory . SeparatedList ( parameters ) ) ) ;
117+
118+ var attributeList = SyntaxFactory . AttributeList ( SyntaxFactory . SingletonSeparatedList ( attribute ) ) ;
119+
120+ var stateDeclaration = SyntaxFactory
121+ . FieldDeclaration ( SyntaxFactory . VariableDeclaration ( SyntaxFactory . ParseTypeName ( "int" ) )
122+ . AddVariables ( SyntaxFactory
123+ . VariableDeclarator ( TypeToConst ( stateType ) )
124+ . WithInitializer ( SyntaxFactory . EqualsValueClause (
125+ SyntaxFactory . LiteralExpression (
126+ SyntaxKind . NumericLiteralExpression , SyntaxFactory . Literal ( value ) ) ) ) ) )
127+ . AddModifiers ( SyntaxFactory . Token ( SyntaxKind . ProtectedKeyword ) )
128+ . AddModifiers ( SyntaxFactory . Token ( SyntaxKind . ConstKeyword ) )
129+ . AddAttributeLists ( attributeList ) ;
130+
131+ var updatedRoot = InsertStateDeclaration ( stateDeclaration ) ;
132+ updatedRoot = Formatter . Format ( updatedRoot , new AdhocWorkspace ( ) ) ;
133+ return new StateBaseTemplate ( updatedRoot . ToFullString ( ) ) ;
134+ }
93135
94- newLines . Add ( $ " [StateDefinition(typeof({ stateType } ){ initial } )]") ;
95- newLines . Add ( $ " protected const int { TypeToConst ( stateType ) } = { value } ;") ;
96- newLines . Add ( "" ) ;
136+ private SyntaxNode InsertStateDeclaration ( FieldDeclarationSyntax fieldDeclaration )
137+ {
138+ var root = _syntaxTree . GetRoot ( ) ;
139+ var classDeclaration = root . DescendantNodes ( ) . OfType < ClassDeclarationSyntax > ( ) . FirstOrDefault ( ) ;
140+ SyntaxNode ? updatedClassDeclaration = null ;
97141
98- newLines . AddRange ( lines . TakeLast ( lines . Count ( ) - ctorIndex ) ) ;
142+ if ( classDeclaration != null )
143+ {
144+ var constructor = Constructors . FirstOrDefault ( ) ;
145+ if ( constructor != null )
146+ {
147+ var members = classDeclaration . Members . Insert ( classDeclaration . Members . IndexOf ( constructor ) , fieldDeclaration ) ;
148+ updatedClassDeclaration = classDeclaration . WithMembers ( members ) ;
149+ }
150+ else
151+ {
152+ updatedClassDeclaration = classDeclaration . AddMembers ( fieldDeclaration ) ;
153+ }
99154
100- return new StateBaseTemplate ( string . Join ( Environment . NewLine , newLines ) ) ;
155+ if ( updatedClassDeclaration != null )
156+ {
157+ return root . ReplaceNode ( classDeclaration , updatedClassDeclaration ) ;
158+ }
159+ }
160+ return root ;
101161 }
102162
103163 private int NextConst ( IEnumerable < StateDefinition > stateDefinitions )
104164 {
105- var result = StateDefinitions
165+ var result = StateDeclarations
106166 . OrderByDescending ( sd => sd . Value )
107167 . FirstOrDefault ( ) ?
108168 . Value ?? 0 ;
0 commit comments