@@ -16,64 +16,83 @@ namespace CodegenCS.TemplateBuilder
16
16
internal class RoslynCompiler
17
17
{
18
18
protected readonly HashSet < PortableExecutableReference > _references = new HashSet < PortableExecutableReference > ( ) ;
19
- protected readonly HashSet < string > _namespaces = new HashSet < string > ( ) ;
19
+ protected readonly Dictionary < string , Func < string , bool > > _namespaces = new Dictionary < string , Func < string , bool > > ( ) ;
20
20
protected readonly CSharpCompilationOptions _compilationOptions ;
21
21
protected readonly CSharpParseOptions _parseOptions ;
22
22
protected readonly string _dotNetCoreDir ;
23
+ protected readonly bool _verboseMode ;
23
24
protected ILogger _logger ;
24
25
protected TemplateBuilder _builder ;
25
26
26
- public RoslynCompiler ( TemplateBuilder builder , ILogger logger , List < string > extraReferences , List < string > extraNamespaces )
27
+ public RoslynCompiler ( TemplateBuilder builder , ILogger logger , bool verboseMode )
27
28
{
28
29
_builder = builder ;
29
30
_logger = logger ;
30
31
var privateCoreLib = typeof ( object ) . GetTypeInfo ( ) . Assembly . Location ;
31
32
_dotNetCoreDir = Path . GetDirectoryName ( privateCoreLib ) ;
32
33
34
+ _compilationOptions = new CSharpCompilationOptions ( OutputKind . DynamicallyLinkedLibrary )
35
+ . WithOverflowChecks ( true )
36
+ . WithOptimizationLevel ( OptimizationLevel . Release )
37
+ //.WithUsings(_namespaces) // TODO: review why adding namespaces here doesn't make any difference - only AddMissingUsing (applied directly to tree) matters
38
+ . WithWarningLevel ( 0 ) ;
39
+
40
+ // For Microsoft.CodeAnalysis.CSharp 4.2.2 LanguageVersion.Preview means C# 11 preview (which includes raw string literals)
41
+ _parseOptions = CSharpParseOptions . Default . WithLanguageVersion ( LanguageVersion . Preview ) ;
42
+ _verboseMode = verboseMode ;
43
+ }
44
+
45
+ public void AddReferences ( List < string > extraReferences , List < string > extraNamespaces )
46
+ {
47
+ // Some namespaces are always added (low risk of conflicting with user classes)
48
+ // but some others will only be added if we detect (using Func) that they are required, to avoid type names conflict
49
+ // Most regex below are checking for non-fully-qualified typename (no leading dot), because if you're using fully-qualified types you don't need "using"
50
+
33
51
#region Default Assemblies and Namespaces
34
52
35
53
#region Core (System, System.Text, System.Threading.Tasks)
36
- _namespaces . Add ( "System" ) ;
37
- _namespaces . Add ( "System.Text" ) ;
38
- _namespaces . Add ( "System.Threading" ) ;
39
- _namespaces . Add ( "System.Threading.Tasks" ) ;
54
+ _namespaces . Add ( "System" , null ) ;
55
+ _namespaces . Add ( "System.Text" , null ) ;
56
+ _namespaces . Add ( "System.Threading" , templateSource => Regex . IsMatch ( templateSource , @"(?<!\.)\bTask\b" ) ) ; //TODO: always add?
57
+ _namespaces . Add ( "System.Threading.Tasks" , templateSource => Regex . IsMatch ( templateSource , @"(?<!\.)\bTask\b" ) ) ; //TODO: always add?
40
58
41
59
// System.Private.CoreLib: this has all of the main core types in the runtime,
42
60
// including most of the types that are on the System namespace (like mscorlib used to be on .net full framework), including object, List<>, Action<>, etc.
43
61
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( object ) . GetTypeInfo ( ) . Assembly . Location ) ) ; // AddAssembly("System.Private.CoreLib.dll");
44
62
45
63
AddAssembly ( "netstandard.dll" ) ;
46
64
65
+ _namespaces . Add ( "System.Runtime.CompilerServices" , null ) ;
47
66
AddAssembly ( "System.Runtime.dll" ) ;
48
67
AddAssembly ( "System.Threading.dll" ) ;
49
68
#endregion
50
69
51
70
#region System.Linq
52
- _namespaces . Add ( "System.Linq" ) ;
71
+ _namespaces . Add ( "System.Linq" , null ) ;
53
72
AddAssembly ( "System.Linq.dll" ) ;
54
73
AddAssembly ( "System.Linq.Expressions.dll" ) ;
55
74
AddAssembly ( "System.Core.dll" ) ;
56
75
#endregion
57
76
58
77
#region System.Collections
59
- _namespaces . Add ( "System.Collections" ) ;
60
- _namespaces . Add ( "System.Collections.Generic" ) ;
61
- _namespaces . Add ( "System.Collections.Concurrent" ) ;
78
+ _namespaces . Add ( "System.Collections" , null ) ;
79
+ _namespaces . Add ( "System.Collections.Generic" , null ) ;
80
+ _namespaces . Add ( "System.Collections.Concurrent" , null ) ;
62
81
AddAssembly ( "System.Collections.dll" ) ; //AddAssembly(MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location));
63
82
AddAssembly ( "System.Collections.Concurrent.dll" ) ;
64
83
AddAssembly ( "System.Collections.NonGeneric.dll" ) ;
65
84
#endregion
66
85
67
86
#region System.Net.Http
68
- _namespaces . Add ( "System.Net" ) ;
69
- _namespaces . Add ( "System.Net.Http" ) ;
87
+ _namespaces . Add ( "System.Net" , null ) ;
88
+ _namespaces . Add ( "System.Net.Http" , null ) ;
70
89
AddAssembly ( "System.Net.Http.dll" ) ;
71
90
AddAssembly ( "System.Net.Primitives.dll" ) ;
72
91
AddAssembly ( "System.Private.Uri.dll" ) ;
73
92
#endregion
74
93
75
94
#region System.IO, System.Console
76
- _namespaces . Add ( "System.IO" ) ;
95
+ _namespaces . Add ( "System.IO" , null ) ;
77
96
AddAssembly ( "System.IO.dll" ) ;
78
97
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( FileInfo ) . GetTypeInfo ( ) . Assembly . Location ) ) ; // System.IO.FileSystem
79
98
@@ -82,15 +101,19 @@ public RoslynCompiler(TemplateBuilder builder, ILogger logger, List<string> extr
82
101
83
102
// InterpolatedColorConsole
84
103
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( InterpolatedColorConsole . ColoredConsole ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
104
+ _namespaces . Add ( "InterpolatedColorConsole.Symbols" , templateSource =>
105
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bPREVIOUS_COLOR\b" ) ||
106
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bPREVIOUS_BACKGROUND_COLOR\b" ) ) ;
107
+
85
108
#endregion
86
109
87
110
88
111
AddAssembly ( "System.Reflection.dll" ) ;
89
- _namespaces . Add ( "System.Reflection" ) ;
112
+ _namespaces . Add ( "System.Reflection" , null ) ;
90
113
91
114
AddAssembly ( typeof ( System . Text . RegularExpressions . Regex ) ) ; // .net framework
92
115
AddAssembly ( "System.Text.RegularExpressions.dll" ) ;
93
- _namespaces . Add ( "System.Text.RegularExpressions" ) ;
116
+ _namespaces . Add ( "System.Text.RegularExpressions" , null ) ;
94
117
95
118
#region TODO: CSharp / Roslyn Analyzers? To allow code generators to be based on Roslyn CodeAnalysis?
96
119
//AddAssembly(typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)); //AddAssembly("Microsoft.CSharp.dll");
@@ -99,55 +122,66 @@ public RoslynCompiler(TemplateBuilder builder, ILogger logger, List<string> extr
99
122
// add nuget references
100
123
#endregion
101
124
102
-
103
-
104
125
AddAssembly ( "System.ComponentModel.Primitives.dll" ) ;
105
126
106
-
107
127
// CodegenCS / CodegenCS.Runtime / CodegenCS.Models.DbSchema
108
128
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( CodegenCS . CodegenContext ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
109
129
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( CodegenCS . Runtime . ExecutionContext ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
110
130
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( CodegenCS . Models . IInputModel ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
111
131
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( CodegenCS . DotNet . DotNetCodegenContext ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
112
132
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( CodegenCS . Models . DbSchema . DatabaseSchema ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
113
- _namespaces . Add ( "CodegenCS" ) ;
114
- _namespaces . Add ( "CodegenCS.Runtime" ) ;
115
- _namespaces . Add ( "CodegenCS.Models" ) ;
116
- _namespaces . Add ( "CodegenCS.DotNet" ) ;
117
- _namespaces . Add ( "CodegenCS.Models.DbSchema" ) ;
133
+ _namespaces . Add ( "CodegenCS" , null ) ;
134
+
135
+ _namespaces . Add ( "CodegenCS.Runtime" , templateSource =>
136
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bCommandLineArgs\b" ) ||
137
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bIAutoBindCommandLineArgs\b" ) ||
138
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bVSExecutionContext\b" ) ||
139
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bExecutionContext\b" ) ||
140
+ ( Regex . IsMatch ( templateSource , @"(?<!\.)\bILogger\b" ) && Regex . IsMatch ( templateSource , @"\bWriteLine(\w*)Async\b" ) ) ) ;
141
+ _namespaces . Add ( "CodegenCS.Models" , templateSource =>
142
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bIInputModel\b" ) ||
143
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bIJsonInputModel\b" ) ||
144
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bIValidatableJsonInputModel\b" ) ||
145
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bIModelFactory\b" ) ) ;
146
+ _namespaces . Add ( "CodegenCS.DotNet" , null ) ;
147
+ _namespaces . Add ( "CodegenCS.Models.DbSchema" , templateSource => Regex . IsMatch ( templateSource , @"(?<!\.)\bDatabaseSchema\b" ) ) ;
148
+ _namespaces . Add ( "CodegenCS.Symbols" , templateSource =>
149
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bIF\(\b" ) ||
150
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bIIF\(\b" ) ||
151
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bBREAKIF\(\b" ) ||
152
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bTLW\(\b" ) ||
153
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bTTW\(\b" ) ||
154
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bCOMMENT\(\b" ) ||
155
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bRAW\(\b" ) ) ;
118
156
119
157
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( NSwag . OpenApiDocument ) . GetTypeInfo ( ) . Assembly . Location ) ) ; // NSwag.Core
120
158
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( NSwag . OpenApiYamlDocument ) . GetTypeInfo ( ) . Assembly . Location ) ) ; // NSwag.Core.Yaml
121
159
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( NJsonSchema . JsonSchema ) . GetTypeInfo ( ) . Assembly . Location ) ) ; // NJsonSchema
122
160
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( NJsonSchema . Annotations . JsonSchemaAttribute ) . GetTypeInfo ( ) . Assembly . Location ) ) ; // NJsonSchema.Annotations
161
+ _namespaces . Add ( "NSwag" , templateSource => Regex . IsMatch ( templateSource , @"(?<!\.)\bOpenApiDocument\b" ) ) ;
123
162
124
163
// Newtonsoft
125
- _namespaces . Add ( "Newtonsoft.Json" ) ;
164
+ _namespaces . Add ( "Newtonsoft.Json" , null ) ; // maybe we should only add namespace if we find references like "JsonConvert"?
126
165
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( Newtonsoft . Json . JsonConvert ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
127
166
128
167
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( System . CommandLine . Argument ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
129
168
AddAssembly ( MetadataReference . CreateFromFile ( typeof ( System . CommandLine . Binding . BindingContext ) . GetTypeInfo ( ) . Assembly . Location ) ) ;
169
+ // System.CommandLine.Command, System.CommandLine.ParseResult
170
+ _namespaces . Add ( "System.CommandLine" , templateSource =>
171
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bConfigureCommand\b" ) ||
172
+ Regex . IsMatch ( templateSource , @"(?<!\.)\bParseResult\b" ) ) ;
173
+ _namespaces . Add ( "System.CommandLine.Binding" , templateSource => Regex . IsMatch ( templateSource , @"(?<!\.)\bBindingContext\b" ) ) ;
174
+ _namespaces . Add ( "System.CommandLine.Invocation" , templateSource => Regex . IsMatch ( templateSource , @"(?<!\.)\bInvocationContext\b" ) ) ;
130
175
131
176
#endregion
132
177
133
- // Add this library? //AddAssembly(typeof(RoslynCompiler));
178
+ // Add this library (TemplateBuilder) ? //AddAssembly(typeof(RoslynCompiler));
134
179
// maybe just add AppDomain.CurrentDomain.GetAssemblies() ?
135
180
136
181
if ( extraNamespaces != null )
137
- extraNamespaces . ForEach ( ns => _namespaces . Add ( ns ) ) ;
182
+ extraNamespaces . ForEach ( ns => _namespaces . Add ( ns , null ) ) ;
138
183
if ( extraReferences != null )
139
184
extraReferences . ForEach ( rfc => AddAssembly ( MetadataReference . CreateFromFile ( rfc ) ) ) ;
140
-
141
- _namespaces . Clear ( ) ; // TODO: review why these namespaces don't make any difference here - only AddMissingUsing (applied directly to tree) matters
142
-
143
- _compilationOptions = new CSharpCompilationOptions ( OutputKind . DynamicallyLinkedLibrary )
144
- . WithOverflowChecks ( true )
145
- . WithOptimizationLevel ( OptimizationLevel . Release )
146
- . WithUsings ( _namespaces )
147
- . WithWarningLevel ( 0 ) ;
148
-
149
- // For Microsoft.CodeAnalysis.CSharp 4.2.2 LanguageVersion.Preview means C# 11 preview (which includes raw string literals)
150
- _parseOptions = CSharpParseOptions . Default . WithLanguageVersion ( LanguageVersion . Preview ) ;
151
185
}
152
186
153
187
@@ -219,8 +253,7 @@ public async Task<CompileResult> CompileAsync(string[] sources, string targetFil
219
253
// ParseText really better? https://stackoverflow.com/questions/16338131/using-roslyn-to-parse-transform-generate-code-am-i-aiming-too-high-or-too-low
220
254
//SyntaxFactory.ParseSyntaxTree(SourceText.From(text, Encoding.UTF8), options, filename);
221
255
222
- AddMissingUsings ( syntaxTrees ) ;
223
-
256
+ await AddMissingUsings ( syntaxTrees ) ;
224
257
225
258
//TODO: support for top-level statements?
226
259
CSharpCompilation compilation = CSharpCompilation . Create ( "assemblyName" , syntaxTrees ,
@@ -291,76 +324,36 @@ public async Task<CompileResult> CompileAsync(string[] sources, string targetFil
291
324
}
292
325
293
326
}
294
- void AddMissingUsings ( List < SyntaxTree > trees )
327
+ async Task AddMissingUsings ( List < SyntaxTree > trees )
295
328
{
296
329
for ( int i = 0 ; i < trees . Count ; i ++ )
297
330
{
298
331
var rootNode = trees [ i ] . GetRoot ( ) as CompilationUnitSyntax ;
299
- AddMissingUsing ( ref rootNode , "CodegenCS" ) ;
300
-
301
332
302
333
string templateSource = rootNode . ToString ( ) ; //TODO: strip strings from CompilationUnitSyntax - we are only interested in checking the template control logic
303
334
304
- // These namespaces probably won't conflict with anything
305
- AddMissingUsing ( ref rootNode , "System" ) ;
306
- AddMissingUsing ( ref rootNode , "System.Collections.Generic" ) ;
307
- AddMissingUsing ( ref rootNode , "System.Linq" ) ;
308
- AddMissingUsing ( ref rootNode , "System.IO" ) ;
309
- AddMissingUsing ( ref rootNode , "System.Runtime.CompilerServices" ) ;
310
- AddMissingUsing ( ref rootNode , "System.Text.RegularExpressions" ) ;
311
- AddMissingUsing ( ref rootNode , "Newtonsoft.Json" ) ; // I doubt this might conflict with anything. Maybe should search for JsonConvert and some other classes
312
-
313
- // To avoid type names conflict we only add some usings if we detect as required
314
- // Most regex below are checking for non-fully-qualified typename (no leading dot).
315
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bDatabaseSchema\b" ) )
316
- AddMissingUsing ( ref rootNode , "CodegenCS.Models.DbSchema" ) ;
317
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bOpenApiDocument\b" ) )
318
- AddMissingUsing ( ref rootNode , "NSwag" ) ;
319
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bCommandLineArgs\b" )
320
- || Regex . IsMatch ( templateSource , @"(?<!\.)\bIAutoBindCommandLineArgs\b" )
321
- || Regex . IsMatch ( templateSource , @"(?<!\.)\bVSExecutionContext\b" )
322
- || Regex . IsMatch ( templateSource , @"(?<!\.)\bExecutionContext\b" )
323
- )
324
- AddMissingUsing ( ref rootNode , "CodegenCS.Runtime" ) ;
325
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bILogger\b" ) && Regex . IsMatch ( templateSource , @"\bWriteLine(\w*)Async\b" ) )
326
- AddMissingUsing ( ref rootNode , "CodegenCS.Runtime" ) ;
327
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bIInputModel\b" )
328
- || Regex . IsMatch ( templateSource , @"(?<!\.)\bIJsonInputModel\b" )
329
- || Regex . IsMatch ( templateSource , @"(?<!\.)\bIValidatableJsonInputModel\b" )
330
- || Regex . IsMatch ( templateSource , @"(?<!\.)\bIModelFactory\b" )
331
- )
332
- AddMissingUsing ( ref rootNode , "CodegenCS.Models" ) ;
333
-
334
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bTask\b" ) )
335
+ foreach ( var ns in _namespaces )
335
336
{
336
- AddMissingUsing ( ref rootNode , "System.Threading" ) ;
337
- AddMissingUsing ( ref rootNode , "System.Threading.Tasks" ) ;
337
+ if ( ns . Value == null )
338
+ {
339
+ if ( _verboseMode )
340
+ await _logger . WriteLineAsync ( ConsoleColor . DarkGray , $ "Automatically adding namespace \" { ns . Key } \" ") ;
341
+ AddMissingUsing ( ref rootNode , ns . Key ) ;
342
+ }
343
+ else if ( ns . Value ( templateSource ) )
344
+ {
345
+ if ( _verboseMode )
346
+ await _logger . WriteLineAsync ( ConsoleColor . DarkGray , $ "Automatically adding namespace \" { ns . Key } \" (due to matching regex)") ;
347
+ AddMissingUsing ( ref rootNode , ns . Key ) ;
348
+ }
338
349
}
339
350
340
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bConfigureCommand\b" ) || Regex . IsMatch ( templateSource , @"(?<!\.)\bParseResult\b" ) )
341
- AddMissingUsing ( ref rootNode , "System.CommandLine" ) ; // System.CommandLine.Command, System.CommandLine.ParseResult
342
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bBindingContext\b" ) )
343
- AddMissingUsing ( ref rootNode , "System.CommandLine.Binding" ) ;
344
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bInvocationContext\b" ) )
345
- AddMissingUsing ( ref rootNode , "System.CommandLine.Invocation" ) ;
346
-
347
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bIF\(\b" ) ||
348
- Regex . IsMatch ( templateSource , @"(?<!\.)\bIIF\(\b" ) ||
349
- Regex . IsMatch ( templateSource , @"(?<!\.)\bBREAKIF\(\b" ) ||
350
- Regex . IsMatch ( templateSource , @"(?<!\.)\bTLW\(\b" ) ||
351
- Regex . IsMatch ( templateSource , @"(?<!\.)\bTTW\(\b" ) ||
352
- Regex . IsMatch ( templateSource , @"(?<!\.)\bCOMMENT\(\b" ) ||
353
- Regex . IsMatch ( templateSource , @"(?<!\.)\bRAW\(\b" ) )
354
- AddMissingUsing ( ref rootNode , "CodegenCS.Symbols" , true ) ;
355
-
356
- if ( Regex . IsMatch ( templateSource , @"(?<!\.)\bPREVIOUS_COLOR\b" ) || Regex . IsMatch ( templateSource , @"(?<!\.)\bPREVIOUS_BACKGROUND_COLOR\b" ) )
357
- AddMissingUsing ( ref rootNode , "InterpolatedColorConsole.Symbols" , true ) ;
358
-
359
- trees [ i ] = SyntaxFactory . SyntaxTree ( rootNode , _parseOptions ) ; // rootNode.SyntaxTree (without _parseOptions) would go back to C# 10 (and we need C# 11 preview)
351
+ trees [ i ] = SyntaxFactory . SyntaxTree ( rootNode , _parseOptions ) ; // rootNode.SyntaxTree (without _parseOptions) would go back to C# 10 (and we need C# 11)
360
352
}
361
353
}
362
- void AddMissingUsing ( ref CompilationUnitSyntax unit , string @namespace , bool isStatic = false )
354
+ void AddMissingUsing ( ref CompilationUnitSyntax unit , string @namespace )
363
355
{
356
+ bool isStatic = @namespace . Equals ( "CodegenCS.Symbols" ) || @namespace . Equals ( "InterpolatedColorConsole.Symbols" ) ; //TODO: yeah, I know...
364
357
var qualifiedName = SyntaxFactory . ParseName ( @namespace ) ;
365
358
if ( ! unit . Usings . Select ( d => d . Name . ToString ( ) ) . Any ( u => u == qualifiedName . ToString ( ) ) )
366
359
{
0 commit comments