@@ -21,6 +21,7 @@ namespace CodeGeneration.Roslyn.Tasks
2121 using Microsoft . CodeAnalysis . Text ;
2222 using Task = System . Threading . Tasks . Task ;
2323 using Validation ;
24+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
2425
2526 public class Helper
2627#if NET46
@@ -76,7 +77,7 @@ public void Execute()
7677
7778 Task . Run ( async delegate
7879 {
79- var project = this . CreateProject ( ) ;
80+ var compilation = this . CreateCompilation ( ) ;
8081 var outputFiles = new List < ITaskItem > ( ) ;
8182 var writtenFiles = new List < ITaskItem > ( ) ;
8283
@@ -91,35 +92,36 @@ where string.Equals(item.GetMetadata("Generator"), $"MSBuild:{this.TargetName}",
9192 select item . ItemSpec ,
9293 StringComparer . OrdinalIgnoreCase ) ;
9394
94- foreach ( var inputDocument in project . Documents )
95+ foreach ( var inputSyntaxTree in compilation . SyntaxTrees )
9596 {
9697 this . CancellationToken . ThrowIfCancellationRequested ( ) ;
9798
9899 // Skip over documents that aren't on the prescribed list of files to scan.
99- if ( ! explicitIncludeList . Contains ( inputDocument . Name ) )
100+ if ( ! explicitIncludeList . Contains ( inputSyntaxTree . FilePath ) )
100101 {
101102 continue ;
102103 }
103104
104- string sourceHash = inputDocument . Name . GetHashCode ( ) . ToString ( "x" , CultureInfo . InvariantCulture ) ;
105- string outputFilePath = Path . Combine ( this . IntermediateOutputDirectory , Path . GetFileNameWithoutExtension ( inputDocument . Name ) + $ ".{ sourceHash } .generated.cs") ;
105+ string sourceHash = inputSyntaxTree . FilePath . GetHashCode ( ) . ToString ( "x" , CultureInfo . InvariantCulture ) ;
106+ string outputFilePath = Path . Combine ( this . IntermediateOutputDirectory , Path . GetFileNameWithoutExtension ( inputSyntaxTree . FilePath ) + $ ".{ sourceHash } .generated.cs") ;
106107
107108 // Code generation is relatively fast, but it's not free.
108109 // And when we run the Simplifier.ReduceAsync it's dog slow.
109110 // So skip files that haven't changed since we last generated them.
110111 bool generated = false ;
111112 DateTime outputLastModified = File . GetLastWriteTime ( outputFilePath ) ;
112- if ( File . GetLastWriteTime ( inputDocument . Name ) > outputLastModified || assembliesLastModified > outputLastModified )
113+ if ( File . GetLastWriteTime ( inputSyntaxTree . FilePath ) > outputLastModified || assembliesLastModified > outputLastModified )
113114 {
114- var outputDocument = await DocumentTransform . TransformAsync (
115- inputDocument ,
116- new ProgressLogger ( this . Log , inputDocument . Name ) ) ;
117-
118- // Only produce a new file if the generated document is not empty.
119- var semanticModel = await outputDocument . GetSemanticModelAsync ( this . CancellationToken ) ;
120- if ( ! CSharpDeclarationComputer . GetDeclarationsInSpan ( semanticModel , TextSpan . FromBounds ( 0 , semanticModel . SyntaxTree . Length ) , false , this . CancellationToken ) . IsEmpty )
115+ var generatedSyntaxTree = await DocumentTransform . TransformAsync (
116+ compilation ,
117+ inputSyntaxTree ,
118+ new ProgressLogger ( this . Log , inputSyntaxTree . FilePath ) ) ;
119+
120+ // Only produce a new file if the generated document has generated a type.
121+ bool anyMembersGenerated = generatedSyntaxTree ? . GetRoot ( this . CancellationToken ) . DescendantNodes ( ) . OfType < TypeDeclarationSyntax > ( ) . Any ( ) ?? false ;
122+ if ( anyMembersGenerated )
121123 {
122- var outputText = await outputDocument . GetTextAsync ( this . CancellationToken ) ;
124+ var outputText = generatedSyntaxTree . GetText ( this . CancellationToken ) ;
123125 using ( var outputFileStream = File . OpenWrite ( outputFilePath ) )
124126 using ( var outputWriter = new StreamWriter ( outputFileStream ) )
125127 {
@@ -130,12 +132,12 @@ where string.Equals(item.GetMetadata("Generator"), $"MSBuild:{this.TargetName}",
130132 outputFileStream . SetLength ( outputFileStream . Position ) ;
131133 }
132134
133- this . Log . LogMessage ( MessageImportance . Normal , "{0} -> {1}" , inputDocument . Name , outputFilePath ) ;
135+ this . Log . LogMessage ( MessageImportance . Normal , "{0} -> {1}" , inputSyntaxTree . FilePath , outputFilePath ) ;
134136 generated = true ;
135137 }
136138 else
137139 {
138- this . Log . LogMessage ( MessageImportance . Low , "{0} used no code generation attributes." , inputDocument . Name ) ;
140+ this . Log . LogMessage ( MessageImportance . Low , "{0} used no code generation attributes." , inputSyntaxTree . FilePath ) ;
139141 }
140142 }
141143 else
@@ -238,24 +240,22 @@ private Assembly TryLoadAssembly(AssemblyName assemblyName)
238240 return null ;
239241 }
240242
241- private Project CreateProject ( )
243+ private CSharpCompilation CreateCompilation ( )
242244 {
243- var workspace = new AdhocWorkspace ( ) ;
244- var project = workspace . CurrentSolution . AddProject ( "codegen" , "codegen" , "C#" )
245- . WithCompilationOptions ( new CSharpCompilationOptions ( OutputKind . DynamicallyLinkedLibrary ) )
246- . WithMetadataReferences ( this . ReferencePath . Select ( p => MetadataReference . CreateFromFile ( p . ItemSpec ) ) ) ;
247-
245+ var compilation = CSharpCompilation . Create ( "codegen" )
246+ . WithOptions ( new CSharpCompilationOptions ( OutputKind . DynamicallyLinkedLibrary ) )
247+ . WithReferences ( this . ReferencePath . Select ( p => MetadataReference . CreateFromFile ( p . ItemSpec ) ) ) ;
248248 foreach ( var sourceFile in this . Compile )
249249 {
250- using ( var stream = File . OpenRead ( sourceFile . ItemSpec ) )
250+ using ( var stream = File . OpenRead ( sourceFile . GetMetadata ( "FullPath" ) ) )
251251 {
252252 this . CancellationToken . ThrowIfCancellationRequested ( ) ;
253253 var text = SourceText . From ( stream ) ;
254- project = project . AddDocument ( sourceFile . ItemSpec , text ) . Project ;
254+ compilation = compilation . AddSyntaxTrees ( CSharpSyntaxTree . ParseText ( text , path : sourceFile . ItemSpec , cancellationToken : this . CancellationToken ) ) ;
255255 }
256256 }
257257
258- return project ;
258+ return compilation ;
259259 }
260260 }
261261
0 commit comments