Skip to content
This repository was archived by the owner on Dec 12, 2020. It is now read-only.

Commit 34c830b

Browse files
committed
Make the generator cancellable
1 parent dadd639 commit 34c830b

15 files changed

Lines changed: 67 additions & 44 deletions

File tree

src/.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[*.cs]
2+
3+
# CS0028: 'Program.Main(string[], CancellationToken)' has the wrong signature to be an entry point
4+
dotnet_diagnostic.CS0028.severity = none

src/CodeGeneration.Roslyn.Engine/CompilationGenerator.cs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public class CompilationGenerator
8484
/// <param name="progress">Optional handler of diagnostics provided by code generator.</param>
8585
/// <param name="cancellationToken">Cancellation token to interrupt async operations.</param>
8686
/// <returns>A <see cref="Task.CompletedTask"/>.</returns>
87-
public async Task GenerateAsync(IProgress<Diagnostic> progress = null, CancellationToken cancellationToken = default)
87+
public async Task GenerateAsync(IProgress<Diagnostic> progress, CancellationToken cancellationToken)
8888
{
8989
Verify.Operation(this.Compile != null, $"{nameof(Compile)} must be set first.");
9090
Verify.Operation(this.ReferencePath != null, $"{nameof(ReferencePath)} must be set first.");
@@ -104,13 +104,14 @@ public async Task GenerateAsync(IProgress<Diagnostic> progress = null, Cancellat
104104
{
105105
cancellationToken.ThrowIfCancellationRequested();
106106

107-
string sourceHash = Convert.ToBase64String(hasher.ComputeHash(Encoding.UTF8.GetBytes(inputSyntaxTree.FilePath)), 0, 6).Replace('/', '-');
107+
string sourceHash = Convert.ToBase64String(hasher.ComputeHash(Encoding.UTF8.GetBytes(inputSyntaxTree.FilePath)), 0, 6).Replace(Path.AltDirectorySeparatorChar, '-').Replace(Path.DirectorySeparatorChar, '-');
108108
Logger.Info($"File \"{inputSyntaxTree.FilePath}\" hashed to {sourceHash}");
109-
string outputFilePath = Path.Combine(this.IntermediateOutputDirectory, Path.GetFileNameWithoutExtension(inputSyntaxTree.FilePath) + $".{sourceHash}.generated.cs");
109+
string outputFilePath = Path.Combine(this.IntermediateOutputDirectory, Path.GetFileNameWithoutExtension(inputSyntaxTree.FilePath) + FormattableString.Invariant($".{sourceHash}.generated.cs"));
110+
var outputFile = new FileInfo(outputFilePath);
110111

111112
// Code generation is relatively fast, but it's not free.
112113
// So skip files that haven't changed since we last generated them.
113-
DateTime outputLastModified = File.Exists(outputFilePath) ? File.GetLastWriteTime(outputFilePath) : DateTime.MinValue;
114+
DateTime outputLastModified = outputFile.Exists ? outputFile.LastWriteTime : DateTime.MinValue;
114115
if (File.GetLastWriteTime(inputSyntaxTree.FilePath) > outputLastModified || assembliesLastModified > outputLastModified)
115116
{
116117
int retriesLeft = 3;
@@ -123,13 +124,13 @@ public async Task GenerateAsync(IProgress<Diagnostic> progress = null, Cancellat
123124
inputSyntaxTree,
124125
this.ProjectDirectory,
125126
this.LoadPlugin,
126-
progress);
127-
128-
var outputText = generatedSyntaxTree.GetText(cancellationToken);
129-
using (var outputFileStream = File.OpenWrite(outputFilePath))
127+
progress,
128+
cancellationToken);
129+
var outputText = await generatedSyntaxTree.GetTextAsync(cancellationToken);
130+
using (var outputFileStream = outputFile.OpenWrite())
130131
using (var outputWriter = new StreamWriter(outputFileStream))
131132
{
132-
outputText.Write(outputWriter);
133+
outputText.Write(outputWriter, cancellationToken);
133134

134135
// Truncate any data that may be beyond this point if the file existed previously.
135136
outputWriter.Flush();
@@ -142,15 +143,15 @@ public async Task GenerateAsync(IProgress<Diagnostic> progress = null, Cancellat
142143
bool anyTypesGenerated = root.DescendantNodes().OfType<TypeDeclarationSyntax>().Any();
143144
if (!anyTypesGenerated)
144145
{
145-
this.emptyGeneratedFiles.Add(outputFilePath);
146+
this.emptyGeneratedFiles.Add(outputFile.FullName);
146147
}
147148
}
148149
break;
149150
}
150151
catch (IOException ex) when (ex.HResult == ProcessCannotAccessFileHR && retriesLeft > 0)
151152
{
152153
retriesLeft--;
153-
Task.Delay(200).Wait();
154+
await Task.Delay(200, cancellationToken);
154155
}
155156
catch (Exception ex)
156157
{
@@ -162,7 +163,7 @@ public async Task GenerateAsync(IProgress<Diagnostic> progress = null, Cancellat
162163
while (true);
163164
}
164165

165-
this.generatedFiles.Add(outputFilePath);
166+
this.generatedFiles.Add(outputFile.FullName);
166167
}
167168
}
168169

@@ -232,10 +233,7 @@ private static void ReportError(IProgress<Diagnostic> progress, string id, Synta
232233

233234
var location = inputSyntaxTree != null ? Location.Create(inputSyntaxTree, TextSpan.FromBounds(0, 0)) : Location.None;
234235

235-
var messageArgs = new object[]
236-
{
237-
ex,
238-
};
236+
var messageArgs = new object[] { ex };
239237

240238
var reportDiagnostic = Diagnostic.Create(descriptor, location, messageArgs);
241239

src/CodeGeneration.Roslyn.Engine/DocumentTransform.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ public static class DocumentTransform
4242
/// <param name="projectDirectory">The path of the <c>.csproj</c> project file.</param>
4343
/// <param name="assemblyLoader">A function that can load an assembly with the given name.</param>
4444
/// <param name="progress">Reports warnings and errors in code generation.</param>
45+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
4546
/// <returns>A task whose result is the generated document.</returns>
4647
public static async Task<SyntaxTree> TransformAsync(
4748
CSharpCompilation compilation,
4849
SyntaxTree inputDocument,
4950
string projectDirectory,
5051
Func<AssemblyName, Assembly> assemblyLoader,
51-
IProgress<Diagnostic> progress)
52+
IProgress<Diagnostic> progress,
53+
CancellationToken cancellationToken)
5254
{
5355
Requires.NotNull(compilation, nameof(compilation));
5456
Requires.NotNull(inputDocument, nameof(inputDocument));
@@ -70,17 +72,19 @@ public static async Task<SyntaxTree> TransformAsync(
7072
var emittedAttributeLists = ImmutableArray<AttributeListSyntax>.Empty;
7173
var emittedMembers = ImmutableArray<MemberDeclarationSyntax>.Empty;
7274

73-
var root = await inputDocument.GetRootAsync();
75+
var root = await inputDocument.GetRootAsync(cancellationToken);
7476
var memberNodes = root
7577
.DescendantNodesAndSelf(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax || n is TypeDeclarationSyntax)
7678
.OfType<CSharpSyntaxNode>();
7779

7880
foreach (var memberNode in memberNodes)
7981
{
82+
cancellationToken.ThrowIfCancellationRequested();
8083
var attributeData = GetAttributeData(compilation, inputSemanticModel, memberNode);
8184
var generators = FindCodeGenerators(attributeData, assemblyLoader);
8285
foreach (var generator in generators)
8386
{
87+
cancellationToken.ThrowIfCancellationRequested();
8488
var context = new TransformationContext(
8589
memberNode,
8690
inputSemanticModel,
@@ -91,7 +95,7 @@ public static async Task<SyntaxTree> TransformAsync(
9195

9296
var richGenerator = generator as IRichCodeGenerator ?? new EnrichingCodeGeneratorProxy(generator);
9397

94-
var emitted = await richGenerator.GenerateRichAsync(context, progress, CancellationToken.None);
98+
var emitted = await richGenerator.GenerateRichAsync(context, progress, cancellationToken);
9599

96100
emittedExterns = emittedExterns.AddRange(emitted.Externs);
97101
emittedUsings = emittedUsings.AddRange(emitted.Usings);
@@ -244,7 +248,7 @@ public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(
244248

245249
public async Task<RichGenerationResult> GenerateRichAsync(TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken)
246250
{
247-
var generatedMembers = await CodeGenerator.GenerateAsync(context, progress, CancellationToken.None);
251+
var generatedMembers = await CodeGenerator.GenerateAsync(context, progress, cancellationToken);
248252

249253
// Figure out ancestry for the generated type, including nesting types and namespaces.
250254
var wrappedMembers = context.ProcessingNode.Ancestors().Aggregate(generatedMembers, WrapInAncestor);

src/CodeGeneration.Roslyn.Tests.Generators/DuplicateWithSuffixGenerator.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationCon
3535
var results = SyntaxFactory.List<MemberDeclarationSyntax>();
3636

3737
MemberDeclarationSyntax copy = null;
38-
var applyToClass = context.ProcessingNode as ClassDeclarationSyntax;
39-
if (applyToClass != null)
38+
if (context.ProcessingNode is ClassDeclarationSyntax applyToClass)
4039
{
4140
copy = applyToClass
4241
.WithIdentifier(SyntaxFactory.Identifier(applyToClass.Identifier.ValueText + this.suffix));

src/CodeGeneration.Roslyn.Tests.Generators/ExternalDuplicateWithSuffixGenerator.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
namespace CodeGeneration.Roslyn.Tests.Generators
55
{
66
using System;
7-
using System.Collections.Generic;
87
using System.Collections.Immutable;
9-
using System.Linq;
10-
using System.Text;
118
using System.Threading;
129
using System.Threading.Tasks;
1310
using CodeGeneration.Roslyn.Tests.Generators.Dependency;
@@ -36,8 +33,7 @@ public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationCon
3633
var results = SyntaxFactory.List<MemberDeclarationSyntax>();
3734

3835
MemberDeclarationSyntax copy = null;
39-
var applyToClass = context.ProcessingNode as MethodDeclarationSyntax;
40-
if (applyToClass != null)
36+
if (context.ProcessingNode is MethodDeclarationSyntax applyToClass)
4137
{
4238
copy = applyToClass
4339
.WithIdentifier(SyntaxFactory.Identifier(NameGenerator.Combine(applyToClass.Identifier.ValueText, this.suffix)))

src/CodeGeneration.Roslyn.Tests.Generators/MultiplySuffixGenerator.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
namespace CodeGeneration.Roslyn.Tests.Generators
55
{
66
using System;
7-
using System.Collections.Generic;
8-
using System.Collections.Immutable;
97
using System.Linq;
10-
using System.Text;
118
using System.Threading;
129
using System.Threading.Tasks;
1310
using Microsoft.CodeAnalysis;
@@ -28,8 +25,7 @@ public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationCon
2825
var results = SyntaxFactory.List<MemberDeclarationSyntax>();
2926

3027
MemberDeclarationSyntax copy = null;
31-
var applyToClass = context.ProcessingNode as ClassDeclarationSyntax;
32-
if (applyToClass != null)
28+
if (context.ProcessingNode is ClassDeclarationSyntax applyToClass)
3329
{
3430
var properties = applyToClass.Members.OfType<PropertyDeclarationSyntax>()
3531
.Select(x =>

src/CodeGeneration.Roslyn.Tests.RunBuild/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace CodeGeneration.Roslyn.Tests.RunBuild
55
{
66
internal static class Program
77
{
8-
private static void Main(string[] args)
8+
private static void Main()
99
{
1010
// This app is a dummy. But when it is debugged within VS, it builds the Tests
1111
// allowing VS to debug into the build/code generation process.

src/CodeGeneration.Roslyn.Tests/CodeGenerationTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ public partial class CodeGenerationTests
1616
[Fact]
1717
public void SimpleGenerationWorks()
1818
{
19-
var foo = new CodeGenerationTests.Foo();
20-
var fooA = new CodeGenerationTests.FooA();
21-
var fooB = new CodeGenerationTests.FooB();
19+
_ = new CodeGenerationTests.Foo();
20+
_ = new CodeGenerationTests.FooA();
21+
_ = new CodeGenerationTests.FooB();
2222
var multiplied = new MultipliedBar();
2323
multiplied.ValueSuff1020();
2424
Assert.EndsWith(Path.Combine("src", "CodeGeneration.Roslyn.Tests"), DirectoryPathTest.Path, StringComparison.OrdinalIgnoreCase);

src/CodeGeneration.Roslyn.Tests/Helpers/CompilationTestsBase.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Linq;
88
using System.Reflection;
9+
using System.Threading;
910
using System.Threading.Tasks;
1011
using CodeGeneration.Roslyn;
1112
using CodeGeneration.Roslyn.Engine;
@@ -78,7 +79,7 @@ protected static async Task<SyntaxTree> GenerateAsync(string source)
7879
var diagnostics = compilation.GetDiagnostics();
7980
Assert.Empty(diagnostics.Where(x => x.Severity >= DiagnosticSeverity.Warning));
8081
var progress = new Progress<Diagnostic>();
81-
var result = await DocumentTransform.TransformAsync(compilation, tree, null, Assembly.Load, progress);
82+
var result = await DocumentTransform.TransformAsync(compilation, tree, null, Assembly.Load, progress, CancellationToken.None);
8283
return result;
8384
}
8485

src/CodeGeneration.Roslyn.Tests/NestedNamespacesAndTypesTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class NestedNamespacesAndTypesTests
1313
[Fact]
1414
public void NestedNamespaceTest()
1515
{
16-
var nested = new A.B.OuterType.MiddleType.NestedNSTypeA();
16+
_ = new A.B.OuterType.MiddleType.NestedNSTypeA();
1717
}
1818
}
1919

0 commit comments

Comments
 (0)