未验证 提交 5a4bf5a8 编写于 作者: C Chris Sienkiewicz 提交者: GitHub

Expose generator internals: (#46296)

* Expose generator internals:
- Add a new api + result objects to return generator driver internals
- Rework the way we handle exceptions, especially for syntax walk based operations
- Ensure we don't mix up generator and driver exceptions
上级 8a0c1f46
......@@ -6436,4 +6436,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="IDS_FeatureExtensionGetEnumerator" xml:space="preserve">
<value>extension GetEnumerator</value>
</data>
</root>
</root>
\ No newline at end of file
......@@ -16,6 +16,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
using Xunit;
......@@ -698,6 +699,209 @@ class C { }
$"{generator2.GetType().Module.ModuleVersionId}_{generator2.GetType().FullName}_source.cs"
}, filePaths);
}
[Fact]
public void RunResults_Are_Empty_Before_Generation()
{
GeneratorDriver driver = new CSharpGeneratorDriver(TestOptions.Regular, ImmutableArray<ISourceGenerator>.Empty, CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
var results = driver.GetRunResult();
Assert.Empty(results.GeneratedTrees);
Assert.Empty(results.Diagnostics);
Assert.Empty(results.Results);
}
[Fact]
public void RunResults_Are_Available_After_Generation()
{
var source = @"
class C { }
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.AddSource("test", SourceText.From("public class D {}", Encoding.UTF8)); });
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunGenerators(compilation);
var results = driver.GetRunResult();
Assert.Single(results.GeneratedTrees);
Assert.Single(results.Results);
Assert.Empty(results.Diagnostics);
var result = results.Results.Single();
Assert.Null(result.Exception);
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
Assert.Equal(results.GeneratedTrees.Single(), result.GeneratedSources.Single().SyntaxTree);
}
[Fact]
public void RunResults_Combine_SyntaxTrees()
{
var source = @"
class C { }
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.AddSource("test", SourceText.From("public class D {}", Encoding.UTF8)); sgc.AddSource("test2", SourceText.From("public class E {}", Encoding.UTF8)); });
var generator2 = new SingleFileTestGenerator("public class F{}");
var generator3 = new SingleFileTestGenerator2("public class G{}");
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator, generator2, generator3), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunGenerators(compilation);
var results = driver.GetRunResult();
Assert.Equal(4, results.GeneratedTrees.Length);
Assert.Equal(3, results.Results.Length);
Assert.Empty(results.Diagnostics);
var result1 = results.Results[0];
var result2 = results.Results[1];
var result3 = results.Results[2];
Assert.Null(result1.Exception);
Assert.Empty(result1.Diagnostics);
Assert.Equal(2, result1.GeneratedSources.Length);
Assert.Equal(results.GeneratedTrees[0], result1.GeneratedSources[0].SyntaxTree);
Assert.Equal(results.GeneratedTrees[1], result1.GeneratedSources[1].SyntaxTree);
Assert.Null(result2.Exception);
Assert.Empty(result2.Diagnostics);
Assert.Single(result2.GeneratedSources);
Assert.Equal(results.GeneratedTrees[2], result2.GeneratedSources[0].SyntaxTree);
Assert.Null(result3.Exception);
Assert.Empty(result3.Diagnostics);
Assert.Single(result3.GeneratedSources);
Assert.Equal(results.GeneratedTrees[3], result3.GeneratedSources[0].SyntaxTree);
}
[Fact]
public void RunResults_Combine_Diagnostics()
{
var source = @"
class C { }
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
string description = "This is a test diagnostic";
DiagnosticDescriptor generatorDiagnostic1 = new DiagnosticDescriptor("TG001", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
DiagnosticDescriptor generatorDiagnostic2 = new DiagnosticDescriptor("TG002", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
DiagnosticDescriptor generatorDiagnostic3 = new DiagnosticDescriptor("TG003", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
var diagnostic1 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic1, Location.None);
var diagnostic2 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic2, Location.None);
var diagnostic3 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic3, Location.None);
var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic1); sgc.ReportDiagnostic(diagnostic2); });
var generator2 = new CallbackGenerator2((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic3); });
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator, generator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunGenerators(compilation);
var results = driver.GetRunResult();
Assert.Equal(2, results.Results.Length);
Assert.Equal(3, results.Diagnostics.Length);
Assert.Empty(results.GeneratedTrees);
var result1 = results.Results[0];
var result2 = results.Results[1];
Assert.Null(result1.Exception);
Assert.Equal(2, result1.Diagnostics.Length);
Assert.Empty(result1.GeneratedSources);
Assert.Equal(results.Diagnostics[0], result1.Diagnostics[0]);
Assert.Equal(results.Diagnostics[1], result1.Diagnostics[1]);
Assert.Null(result2.Exception);
Assert.Single(result2.Diagnostics);
Assert.Empty(result2.GeneratedSources);
Assert.Equal(results.Diagnostics[2], result2.Diagnostics[0]);
}
[Fact]
public void FullGeneration_Diagnostics_AreSame_As_RunResults()
{
var source = @"
class C { }
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
string description = "This is a test diagnostic";
DiagnosticDescriptor generatorDiagnostic1 = new DiagnosticDescriptor("TG001", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
DiagnosticDescriptor generatorDiagnostic2 = new DiagnosticDescriptor("TG002", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
DiagnosticDescriptor generatorDiagnostic3 = new DiagnosticDescriptor("TG003", "Test Diagnostic", description, "Generators", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: description);
var diagnostic1 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic1, Location.None);
var diagnostic2 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic2, Location.None);
var diagnostic3 = Microsoft.CodeAnalysis.Diagnostic.Create(generatorDiagnostic3, Location.None);
var generator = new CallbackGenerator((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic1); sgc.ReportDiagnostic(diagnostic2); });
var generator2 = new CallbackGenerator2((ic) => { }, (sgc) => { sgc.ReportDiagnostic(diagnostic3); });
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(generator, generator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunFullGeneration(compilation, out _, out var fullDiagnostics);
var results = driver.GetRunResult();
Assert.Equal(3, results.Diagnostics.Length);
Assert.Equal(3, fullDiagnostics.Length);
AssertEx.Equal(results.Diagnostics, fullDiagnostics);
}
[Fact]
public void Cancellation_During_Execution_Doesnt_Report_As_Generator_Error()
{
var source = @"
class C
{
}
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
CancellationTokenSource cts = new CancellationTokenSource();
var testGenerator = new CallbackGenerator(
onInit: (i) => { },
onExecute: (e) => { cts.Cancel(); }
);
// test generator cancels the token. Check that the call to this generator doesn't make it look like it errored.
var testGenerator2 = new CallbackGenerator2(
onInit: (i) => { },
onExecute: (e) => { e.AddSource("a", SourceText.From("public class E {}", Encoding.UTF8)); }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(testGenerator, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
var oldDriver = driver;
Assert.Throws<OperationCanceledException>(() =>
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics, cts.Token)
);
Assert.Same(oldDriver, driver);
}
}
}
......@@ -5,11 +5,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities.TestGenerators;
using Xunit;
......@@ -84,14 +86,14 @@ class C { }
Assert.Single(compilation.SyntaxTrees);
var testGenerator = new CallbackGenerator(
onInit: Initialize,
onInit: initialize,
onExecute: (e) => { }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver.RunFullGeneration(compilation, out _, out _);
void Initialize(InitializationContext initContext)
void initialize(InitializationContext initContext)
{
initContext.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver());
Assert.Throws<InvalidOperationException>(() =>
......@@ -192,21 +194,248 @@ void Function()
Assert.IsType<CompilationUnitSyntax>(testReceiver.VisitedNodes[0]);
}
[Fact]
public void Syntax_Receiver_Exception_During_Creation()
{
var source = @"
class C
{
int Property { get; set; }
void Function()
{
var x = 5;
x += 4;
}
}
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
var testGenerator = new CallbackGenerator(
onInit: (i) => i.RegisterForSyntaxNotifications(() => throw new Exception("Test Exception")),
onExecute: (e) => { Assert.True(false); }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
var results = driver.GetRunResult();
Assert.Empty(results.GeneratedTrees);
Assert.Single(results.Diagnostics);
Assert.Single(results.Results);
Assert.Single(results.Results[0].Diagnostics);
Assert.NotNull(results.Results[0].Exception);
Assert.Equal("Test Exception", results.Results[0].Exception?.Message);
outputDiagnostics.Verify(
Diagnostic(ErrorCode.WRN_GeneratorFailedDuringGeneration).WithArguments("CallbackGenerator").WithLocation(1, 1)
);
}
[Fact]
public void Syntax_Receiver_Exception_During_Visit()
{
var source = @"
class C
{
int Property { get; set; }
void Function()
{
var x = 5;
x += 4;
}
}
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
var testGenerator = new CallbackGenerator(
onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax) throw new Exception("Test Exception"); })),
onExecute: (e) => { e.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)); }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
var results = driver.GetRunResult();
Assert.Empty(results.GeneratedTrees);
Assert.Single(results.Diagnostics);
Assert.Single(results.Results);
Assert.Single(results.Results[0].Diagnostics);
Assert.NotNull(results.Results[0].Exception);
Assert.Equal("Test Exception", results.Results[0].Exception?.Message);
outputDiagnostics.Verify(
Diagnostic(ErrorCode.WRN_GeneratorFailedDuringGeneration).WithArguments("CallbackGenerator").WithLocation(1, 1)
);
}
[Fact]
public void Syntax_Receiver_Exception_During_Visit_Stops_Visits_On_Other_Trees()
{
var source1 = @"
class C
{
int Property { get; set; }
}
";
var source2 = @"
class D
{
public void Method() { }
}
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(new[] { source1, source2 }, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Equal(2, compilation.SyntaxTrees.Count());
TestSyntaxReceiver receiver1 = new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is PropertyDeclarationSyntax) throw new Exception("Test Exception"); });
var testGenerator1 = new CallbackGenerator(
onInit: (i) => i.RegisterForSyntaxNotifications(() => receiver1),
onExecute: (e) => { }
);
TestSyntaxReceiver receiver2 = new TestSyntaxReceiver(tag: 1);
var testGenerator2 = new CallbackGenerator2(
onInit: (i) => i.RegisterForSyntaxNotifications(() => receiver2),
onExecute: (e) => { }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(testGenerator1, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
var results = driver.GetRunResult();
Assert.DoesNotContain(receiver1.VisitedNodes, n => n is MethodDeclarationSyntax);
Assert.Contains(receiver2.VisitedNodes, n => n is MethodDeclarationSyntax);
}
[Fact]
public void Syntax_Receiver_Exception_During_Visit_Doesnt_Stop_Other_Receivers()
{
var source = @"
class C
{
int Property { get; set; }
void Function()
{
var x = 5;
x += 4;
}
}
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
var testGenerator = new CallbackGenerator(
onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax) throw new Exception("Test Exception"); })),
onExecute: (e) => { }
);
ISyntaxReceiver? receiver = null;
var testGenerator2 = new CallbackGenerator2(
onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 1)),
onExecute: (e) => { receiver = e.SyntaxReceiver; e.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)); }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(testGenerator, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
var results = driver.GetRunResult();
Assert.Single(results.GeneratedTrees);
Assert.Single(results.Diagnostics);
Assert.Equal(2, results.Results.Length);
Assert.Single(results.Results[0].Diagnostics);
Assert.NotNull(results.Results[0].Exception);
Assert.Equal("Test Exception", results.Results[0].Exception?.Message);
Assert.Empty(results.Results[1].Diagnostics);
var testReceiver = (TestSyntaxReceiver)receiver!;
Assert.Equal(1, testReceiver.Tag);
Assert.Equal(21, testReceiver.VisitedNodes.Count);
outputDiagnostics.Verify(
Diagnostic(ErrorCode.WRN_GeneratorFailedDuringGeneration).WithArguments("CallbackGenerator").WithLocation(1, 1)
);
}
[Fact]
public void Syntax_Receiver_Is_Not_Created_If_Exception_During_Initialize()
{
var source = @"
class C
{
int Property { get; set; }
void Function()
{
var x = 5;
x += 4;
}
}
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);
TestSyntaxReceiver? receiver = null;
var testGenerator = new CallbackGenerator(
onInit: (i) => { i.RegisterForSyntaxNotifications(() => receiver = new TestSyntaxReceiver()); throw new Exception("test exception"); },
onExecute: (e) => { Assert.True(false); }
);
GeneratorDriver driver = new CSharpGeneratorDriver(parseOptions, ImmutableArray.Create<ISourceGenerator>(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray<AdditionalText>.Empty);
driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics);
var results = driver.GetRunResult();
Assert.Null(receiver);
outputDiagnostics.Verify(
Diagnostic(ErrorCode.WRN_GeneratorFailedDuringInitialization).WithArguments("CallbackGenerator").WithLocation(1, 1)
);
}
class TestSyntaxReceiver : ISyntaxReceiver
{
private readonly Action<SyntaxNode>? _callback;
public List<SyntaxNode> VisitedNodes { get; } = new List<SyntaxNode>();
public int Tag { get; }
public TestSyntaxReceiver(int tag = 0)
public TestSyntaxReceiver(int tag = 0, Action<SyntaxNode>? callback = null)
{
Tag = tag;
_callback = callback;
}
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
VisitedNodes.Add(syntaxNode);
if (_callback is object)
{
_callback(syntaxNode);
}
}
}
}
......
......@@ -9,10 +9,14 @@ Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalysisResultAsy
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalysisResultAsync(Microsoft.CodeAnalysis.AdditionalText file, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Diagnostics.AnalysisResult>
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.get -> int
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.set -> void
Microsoft.CodeAnalysis.GeneratorDriverRunResult.Diagnostics.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostic>
Microsoft.CodeAnalysis.GeneratorDriverRunResult.GeneratedTrees.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.SyntaxTree>
Microsoft.CodeAnalysis.GeneratorDriverRunResult.Results.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.GeneratorRunResult>
Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider
Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider.SyntaxTreeOptionsProvider() -> void
abstract Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider.IsGenerated(Microsoft.CodeAnalysis.SyntaxTree tree) -> bool?
abstract Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider.TryGetDiagnosticValue(Microsoft.CodeAnalysis.SyntaxTree tree, string diagnosticId, out Microsoft.CodeAnalysis.ReportDiagnostic severity) -> bool
Microsoft.CodeAnalysis.GeneratorRunResult.GeneratedSources.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.GeneratedSourceResult>
const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointMethodName = "<Main>$" -> string
const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointTypeName = "<Program>$" -> string
Microsoft.CodeAnalysis.CompilationOptions.SyntaxTreeOptionsProvider.get -> Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider
......@@ -20,3 +24,14 @@ Microsoft.CodeAnalysis.CompilationOptions.WithSyntaxTreeOptionsProvider(Microsof
Microsoft.CodeAnalysis.Operations.IPatternOperation.NarrowedType.get -> Microsoft.CodeAnalysis.ITypeSymbol
virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterAdditionalFileAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext> action) -> void
virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterAdditionalFileAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext> action) -> void
Microsoft.CodeAnalysis.GeneratedSourceResult
Microsoft.CodeAnalysis.GeneratedSourceResult.HintName.get -> string
Microsoft.CodeAnalysis.GeneratedSourceResult.SourceText.get -> Microsoft.CodeAnalysis.Text.SourceText
Microsoft.CodeAnalysis.GeneratedSourceResult.SyntaxTree.get -> Microsoft.CodeAnalysis.SyntaxTree
Microsoft.CodeAnalysis.GeneratorDriver.GetRunResult() -> Microsoft.CodeAnalysis.GeneratorDriverRunResult
Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Microsoft.CodeAnalysis.Compilation compilation, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.GeneratorDriver
Microsoft.CodeAnalysis.GeneratorDriverRunResult
Microsoft.CodeAnalysis.GeneratorRunResult
Microsoft.CodeAnalysis.GeneratorRunResult.Diagnostics.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostic>
Microsoft.CodeAnalysis.GeneratorRunResult.Exception.get -> System.Exception
Microsoft.CodeAnalysis.GeneratorRunResult.Generator.get -> Microsoft.CodeAnalysis.ISourceGenerator
\ No newline at end of file
......@@ -9,7 +9,6 @@
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
#nullable enable
namespace Microsoft.CodeAnalysis
......@@ -46,80 +45,19 @@ internal GeneratorDriver(ParseOptions parseOptions, ImmutableArray<ISourceGenera
_state = new GeneratorDriverState(parseOptions, optionsProvider, generators, additionalTexts, ImmutableArray.Create(new GeneratorState[generators.Length]), ImmutableArray<PendingEdit>.Empty, editsFailed: true);
}
public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken = default)
public GeneratorDriver RunGenerators(Compilation compilation, CancellationToken cancellationToken = default)
{
// with no generators, there is no work to do
if (_state.Generators.Length == 0)
{
outputCompilation = compilation;
diagnostics = ImmutableArray<Diagnostic>.Empty;
return this;
}
// run the actual generation
var state = StateWithPendingEditsApplied(_state);
var stateBuilder = ArrayBuilder<GeneratorState>.GetInstance();
var receivers = PooledDictionary<ISourceGenerator, ISyntaxReceiver>.GetInstance();
var diagnosticsBag = new DiagnosticBag();
for (int i = 0; i < state.Generators.Length; i++)
{
var generator = state.Generators[i];
var generatorState = state.GeneratorStates[i];
// initialize the generator if needed
if (!generatorState.Info.Initialized)
{
generatorState = InitializeGenerator(generator, diagnosticsBag, cancellationToken);
}
stateBuilder.Add(generatorState);
// create the syntax receiver if requested
if (generatorState.Info.SyntaxReceiverCreator is object)
{
var rx = generatorState.Info.SyntaxReceiverCreator();
receivers.Add(generator, rx);
}
}
// Run a syntax walk if any of the generators requested it
if (receivers.Count > 0)
{
GeneratorSyntaxWalker walker = new GeneratorSyntaxWalker(receivers.Values.ToImmutableArray());
foreach (var syntaxTree in compilation.SyntaxTrees)
{
walker.Visit(syntaxTree.GetRoot(cancellationToken));
}
}
// https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this
for (int i = 0; i < state.Generators.Length; i++)
{
var generator = state.Generators[i];
var generatorState = stateBuilder[i];
try
{
// don't try and generate if initialization failed
if (!generatorState.Info.Initialized)
{
continue;
}
var state = RunGeneratorsCore(compilation, diagnosticsBag: null, cancellationToken); //don't directly collect diagnostics on this path
return FromState(state);
}
// we create a new context for each run of the generator. We'll never re-use existing state, only replace anything we have
_ = receivers.TryGetValue(generator, out var syntaxReceiverOpt);
var context = new SourceGeneratorContext(compilation, state.AdditionalTexts.NullToEmpty(), state.OptionsProvider, syntaxReceiverOpt, diagnosticsBag);
generator.Execute(context);
stateBuilder[i] = generatorState.WithSources(ParseAdditionalSources(generator, context.AdditionalSources.ToImmutableAndFree(), cancellationToken));
}
catch
{
diagnosticsBag.Add(Diagnostic.Create(MessageProvider, MessageProvider.WRN_GeneratorFailedDuringGeneration, generator.GetType().Name));
}
}
state = state.With(generatorStates: stateBuilder.ToImmutableAndFree());
diagnostics = diagnosticsBag.ToReadOnlyAndFree();
public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken = default)
{
var diagnosticsBag = DiagnosticBag.GetInstance();
var state = RunGeneratorsCore(compilation, diagnosticsBag, cancellationToken);
// build the final state, and return
diagnostics = diagnosticsBag.ToReadOnlyAndFree();
return BuildFinalCompilation(compilation, out outputCompilation, state, cancellationToken);
}
......@@ -183,6 +121,138 @@ public GeneratorDriver AddAdditionalTexts(ImmutableArray<AdditionalText> additio
return FromState(newState);
}
public GeneratorDriverRunResult GetRunResult()
{
var results = _state.Generators.ZipAsArray(
_state.GeneratorStates,
(generator, generatorState)
=> new GeneratorRunResult(generator,
diagnostics: generatorState.Diagnostics,
exception: generatorState.Exception,
generatedSources: generatorState.SourceTexts.ZipAsArray(
generatorState.Trees,
(sourceText, tree) => new GeneratedSourceResult(tree, sourceText.Text, sourceText.HintName))));
return new GeneratorDriverRunResult(results);
}
internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, DiagnosticBag? diagnosticsBag, CancellationToken cancellationToken = default)
{
// with no generators, there is no work to do
if (_state.Generators.IsEmpty)
{
return _state;
}
// run the actual generation
var state = StateWithPendingEditsApplied(_state);
var stateBuilder = ArrayBuilder<GeneratorState>.GetInstance(state.Generators.Length);
var walkerBuilder = ArrayBuilder<GeneratorSyntaxWalker?>.GetInstance(state.Generators.Length, fillWithValue: null); // we know there is at max 1 per generator
int receiverCount = 0;
for (int i = 0; i < state.Generators.Length; i++)
{
var generator = state.Generators[i];
var generatorState = state.GeneratorStates[i];
// initialize the generator if needed
if (!generatorState.Info.Initialized)
{
InitializationContext context = new InitializationContext(cancellationToken);
Exception? ex = null;
try
{
generator.Initialize(context);
}
catch (Exception e)
{
ex = e;
}
generatorState = ex is null
? new GeneratorState(context.InfoBuilder.ToImmutable())
: SetGeneratorException(MessageProvider, GeneratorState.Uninitialized, generator, ex, diagnosticsBag, isInit: true);
}
// create the syntax receiver if requested
if (generatorState.Info.SyntaxReceiverCreator is object)
{
try
{
var rx = generatorState.Info.SyntaxReceiverCreator();
walkerBuilder.SetItem(i, new GeneratorSyntaxWalker(rx));
generatorState = generatorState.WithReceiver(rx);
receiverCount++;
}
catch (Exception e)
{
generatorState = SetGeneratorException(MessageProvider, generatorState, generator, e, diagnosticsBag);
}
}
stateBuilder.Add(generatorState);
}
// Run a syntax walk if any of the generators requested it
if (receiverCount > 0)
{
foreach (var tree in compilation.SyntaxTrees)
{
var root = tree.GetRoot(cancellationToken);
// https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this
for (int i = 0; i < walkerBuilder.Count; i++)
{
var walker = walkerBuilder[i];
if (walker is object)
{
try
{
walker.Visit(root);
}
catch (Exception e)
{
stateBuilder[i] = SetGeneratorException(MessageProvider, stateBuilder[i], state.Generators[i], e, diagnosticsBag);
walkerBuilder.SetItem(i, null); // don't re-visit this walker for any other trees
}
}
}
}
}
walkerBuilder.Free();
// https://github.com/dotnet/roslyn/issues/42629: should be possible to parallelize this
for (int i = 0; i < state.Generators.Length; i++)
{
var generator = state.Generators[i];
var generatorState = stateBuilder[i];
// don't try and generate if initialization or syntax walk failed
if (generatorState.Exception is object)
{
continue;
}
Debug.Assert(generatorState.Info.Initialized);
// we create a new context for each run of the generator. We'll never re-use existing state, only replace anything we have
var context = new SourceGeneratorContext(compilation, state.AdditionalTexts.NullToEmpty(), state.OptionsProvider, generatorState.SyntaxReceiver);
try
{
generator.Execute(context);
}
catch (Exception e)
{
stateBuilder[i] = SetGeneratorException(MessageProvider, generatorState, generator, e, diagnosticsBag);
continue;
}
(var sources, var diagnostics) = context.ToImmutableAndFree();
stateBuilder[i] = new GeneratorState(generatorState.Info, sources, ParseAdditionalSources(generator, sources, cancellationToken), diagnostics);
diagnosticsBag?.AddRange(diagnostics);
}
state = state.With(generatorStates: stateBuilder.ToImmutableAndFree());
return state;
}
// When we expose this publicly, remove arbitrary edit adding and replace with dedicated edit types
internal GeneratorDriver WithPendingEdits(ImmutableArray<PendingEdit> edits)
{
......@@ -203,7 +273,7 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin
if (edit.AcceptedBy(generatorState.Info))
{
// attempt to apply the edit
var context = new EditContext(generatorState.Sources.Keys.ToImmutableArray(), cancellationToken);
var context = new EditContext(generatorState.SourceTexts.ToImmutableArray(), cancellationToken);
var succeeded = edit.TryApply(generatorState.Info, context);
if (!succeeded)
{
......@@ -213,7 +283,8 @@ private GeneratorDriverState ApplyPartialEdit(GeneratorDriverState state, Pendin
}
// update the state with the new edits
state = state.With(generatorStates: state.GeneratorStates.SetItem(i, generatorState.WithSources(ParseAdditionalSources(generator, context.AdditionalSources.ToImmutableAndFree(), cancellationToken))));
var additionalSources = context.AdditionalSources.ToImmutableAndFree();
state = state.With(generatorStates: state.GeneratorStates.SetItem(i, new GeneratorState(generatorState.Info, sourceTexts: additionalSources, trees: ParseAdditionalSources(generator, additionalSources, cancellationToken), diagnostics: ImmutableArray<Diagnostic>.Empty)));
}
}
state = edit.Commit(state);
......@@ -234,28 +305,12 @@ private static GeneratorDriverState StateWithPendingEditsApplied(GeneratorDriver
return state.With(edits: ImmutableArray<PendingEdit>.Empty, editsFailed: false);
}
private GeneratorState InitializeGenerator(ISourceGenerator generator, DiagnosticBag diagnostics, CancellationToken cancellationToken)
{
GeneratorInfo info = default;
try
{
InitializationContext context = new InitializationContext(cancellationToken);
generator.Initialize(context);
info = context.InfoBuilder.ToImmutable();
}
catch
{
diagnostics.Add(Diagnostic.Create(MessageProvider, MessageProvider.WRN_GeneratorFailedDuringInitialization, generator.GetType().Name));
}
return new GeneratorState(info);
}
private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state, Compilation compilation)
{
ArrayBuilder<SyntaxTree> trees = ArrayBuilder<SyntaxTree>.GetInstance();
foreach (var generatorState in state.GeneratorStates)
{
foreach (var (_, tree) in generatorState.Sources)
foreach (var tree in generatorState.Trees)
{
if (tree is object && compilation.ContainsSyntaxTree(tree))
{
......@@ -269,16 +324,16 @@ private static Compilation RemoveGeneratedSyntaxTrees(GeneratorDriverState state
return comp;
}
private ImmutableDictionary<GeneratedSourceText, SyntaxTree> ParseAdditionalSources(ISourceGenerator generator, ImmutableArray<GeneratedSourceText> generatedSources, CancellationToken cancellationToken)
private ImmutableArray<SyntaxTree> ParseAdditionalSources(ISourceGenerator generator, ImmutableArray<GeneratedSourceText> generatedSources, CancellationToken cancellationToken)
{
var trees = PooledDictionary<GeneratedSourceText, SyntaxTree>.GetInstance();
var trees = ArrayBuilder<SyntaxTree>.GetInstance(generatedSources.Length);
var type = generator.GetType();
var prefix = $"{type.Module.ModuleVersionId}_{type.FullName}";
foreach (var source in generatedSources)
{
trees.Add(source, ParseGeneratedSourceText(source, $"{prefix}_{source.HintName}", cancellationToken));
trees.Add(ParseGeneratedSourceText(source, $"{prefix}_{source.HintName}", cancellationToken));
}
return trees.ToImmutableDictionaryAndFree();
return trees.ToImmutableAndFree();
}
private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compilation outputCompilation, GeneratorDriverState state, CancellationToken cancellationToken)
......@@ -286,7 +341,7 @@ private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compi
ArrayBuilder<SyntaxTree> trees = ArrayBuilder<SyntaxTree>.GetInstance();
foreach (var generatorState in state.GeneratorStates)
{
trees.AddRange(generatorState.Sources.Values);
trees.AddRange(generatorState.Trees);
}
outputCompilation = compilation.AddSyntaxTrees(trees);
trees.Free();
......@@ -296,6 +351,14 @@ private GeneratorDriver BuildFinalCompilation(Compilation compilation, out Compi
return FromState(state);
}
private static GeneratorState SetGeneratorException(CommonMessageProvider provider, GeneratorState generatorState, ISourceGenerator generator, Exception e, DiagnosticBag? diagnosticBag, bool isInit = false)
{
var message = isInit ? provider.WRN_GeneratorFailedDuringInitialization : provider.WRN_GeneratorFailedDuringGeneration;
var diagnostic = Diagnostic.Create(provider, message, generator.GetType().Name);
diagnosticBag?.Add(diagnostic);
return new GeneratorState(generatorState.Info, e, diagnostic);
}
internal abstract CommonMessageProvider MessageProvider { get; }
internal abstract GeneratorDriver FromState(GeneratorDriverState state);
......
......@@ -2,31 +2,81 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
#nullable enable
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents the current state of a generator
/// </summary>
internal readonly struct GeneratorState
{
/// <summary>
/// Gets an uninitalized generator state
/// </summary>
internal static GeneratorState Uninitialized;
/// <summary>
/// Creates a new generator state that just contains information
/// </summary>
public GeneratorState(GeneratorInfo info)
: this(info, ImmutableDictionary<GeneratedSourceText, SyntaxTree>.Empty)
: this(info, ImmutableArray<GeneratedSourceText>.Empty, ImmutableArray<SyntaxTree>.Empty, ImmutableArray<Diagnostic>.Empty, syntaxReceiver: null, exception: null)
{
}
/// <summary>
/// Creates a new generator state that contains an exception and the associated diagnostic
/// </summary>
public GeneratorState(GeneratorInfo info, Exception e, Diagnostic error)
: this(info, ImmutableArray<GeneratedSourceText>.Empty, ImmutableArray<SyntaxTree>.Empty, ImmutableArray.Create(error), syntaxReceiver: null, exception: e)
{
}
private GeneratorState(GeneratorInfo info, ImmutableDictionary<GeneratedSourceText, SyntaxTree> sources)
/// <summary>
/// Creates a generator state that contains results
/// </summary>
public GeneratorState(GeneratorInfo info, ImmutableArray<GeneratedSourceText> sourceTexts, ImmutableArray<SyntaxTree> trees, ImmutableArray<Diagnostic> diagnostics)
: this(info, sourceTexts, trees, diagnostics, syntaxReceiver: null, exception: null)
{
this.Sources = sources;
}
private GeneratorState(GeneratorInfo info, ImmutableArray<GeneratedSourceText> sourceTexts, ImmutableArray<SyntaxTree> trees, ImmutableArray<Diagnostic> diagnostics, ISyntaxReceiver? syntaxReceiver, Exception? exception)
{
this.SourceTexts = sourceTexts;
this.Trees = trees;
this.Info = info;
this.Diagnostics = diagnostics;
this.SyntaxReceiver = syntaxReceiver;
this.Exception = exception;
}
internal ImmutableDictionary<GeneratedSourceText, SyntaxTree> Sources { get; }
internal ImmutableArray<GeneratedSourceText> SourceTexts { get; }
internal ImmutableArray<SyntaxTree> Trees { get; }
internal GeneratorInfo Info { get; }
internal GeneratorState WithSources(ImmutableDictionary<GeneratedSourceText, SyntaxTree> sources)
internal ISyntaxReceiver? SyntaxReceiver { get; }
internal Exception? Exception { get; }
internal ImmutableArray<Diagnostic> Diagnostics { get; }
/// <summary>
/// Adds an <see cref="ISyntaxReceiver"/> to this generator state
/// </summary>
internal GeneratorState WithReceiver(ISyntaxReceiver syntaxReceiver)
{
return new GeneratorState(this.Info, sources);
Debug.Assert(this.Exception is null);
return new GeneratorState(this.Info,
sourceTexts: this.SourceTexts,
trees: this.Trees,
diagnostics: this.Diagnostics,
syntaxReceiver: syntaxReceiver,
exception: null);
}
}
}
......@@ -2,28 +2,22 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
#nullable enable
namespace Microsoft.CodeAnalysis
{
internal sealed class GeneratorSyntaxWalker : SyntaxWalker
{
private readonly ImmutableArray<ISyntaxReceiver> _syntaxReceivers;
private readonly ISyntaxReceiver _syntaxReceiver;
public GeneratorSyntaxWalker(ImmutableArray<ISyntaxReceiver> syntaxReceivers)
internal GeneratorSyntaxWalker(ISyntaxReceiver syntaxReceiver)
{
_syntaxReceivers = syntaxReceivers;
_syntaxReceiver = syntaxReceiver;
}
public override void Visit(SyntaxNode node)
{
foreach (var receiver in _syntaxReceivers)
{
receiver.OnVisitSyntaxNode(node);
}
_syntaxReceiver.OnVisitSyntaxNode(node);
base.Visit(node);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Text;
#nullable enable
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents the results of running a generation pass over a set of <see cref="ISourceGenerator"/>s.
/// </summary>
public class GeneratorDriverRunResult
{
private ImmutableArray<Diagnostic> _lazyDiagnostics;
private ImmutableArray<SyntaxTree> _lazyGeneratedTrees;
internal GeneratorDriverRunResult(ImmutableArray<GeneratorRunResult> results)
{
this.Results = results;
}
/// <summary>
/// The individual result of each <see cref="ISourceGenerator"/> that was run in this generator pass, one per generator.
/// </summary>
public ImmutableArray<GeneratorRunResult> Results { get; }
/// <summary>
/// The <see cref="Diagnostic"/>s produced by all generators run during this generation pass.
/// </summary>
/// <remarks>
/// This is equivalent to the union of all <see cref="GeneratorRunResult.Diagnostics"/> in <see cref="Results"/>.
/// </remarks>
public ImmutableArray<Diagnostic> Diagnostics
{
get
{
if (_lazyDiagnostics.IsDefault)
{
ImmutableInterlocked.InterlockedInitialize(ref _lazyDiagnostics, Results.SelectMany(r => r.Diagnostics).ToImmutableArray());
}
return _lazyDiagnostics;
}
}
/// <summary>
/// The <see cref="SyntaxTree"/>s produced during this generation pass by parsing each <see cref="SourceText"/> added by each generator.
/// </summary>
/// <remarks>
/// This is equivalent to the union of all <see cref="GeneratedSourceResult.SyntaxTree"/>s in each <see cref="GeneratorRunResult.GeneratedSources"/> in each <see cref="Results"/>
/// </remarks>
public ImmutableArray<SyntaxTree> GeneratedTrees
{
get
{
if (_lazyGeneratedTrees.IsDefault)
{
ImmutableInterlocked.InterlockedInitialize(ref _lazyGeneratedTrees, Results.SelectMany(r => r.GeneratedSources.Select(g => g.SyntaxTree)).ToImmutableArray());
}
return _lazyGeneratedTrees;
}
}
}
/// <summary>
/// Represents the results of a single <see cref="ISourceGenerator"/> generation pass.
/// </summary>
public readonly struct GeneratorRunResult
{
internal GeneratorRunResult(ISourceGenerator generator, ImmutableArray<GeneratedSourceResult> generatedSources, ImmutableArray<Diagnostic> diagnostics, Exception? exception)
{
Debug.Assert(exception is null || (generatedSources.IsEmpty && diagnostics.Length == 1));
this.Generator = generator;
this.GeneratedSources = generatedSources;
this.Diagnostics = diagnostics;
this.Exception = exception;
}
/// <summary>
/// The <see cref="ISourceGenerator"/> that produced this result.
/// </summary>
public ISourceGenerator Generator { get; }
/// <summary>
/// The sources that were added by <see cref="Generator"/> during the generation pass this result represents.
/// </summary>
public ImmutableArray<GeneratedSourceResult> GeneratedSources { get; }
/// <summary>
/// A collection of <see cref="Diagnostic"/>s reported by <see cref="Generator"/>
/// </summary>
/// <remarks>
/// When generation fails due to an <see cref="Exception"/> being thrown, a single diagnostic is added
/// to represent the failure. Any generator reported diagnostics up to the failure point are not included.
/// </remarks>
public ImmutableArray<Diagnostic> Diagnostics { get; }
/// <summary>
/// An <see cref="System.Exception"/> instance that was thrown by the generator, or <c>null</c> if the generator completed without error.
/// </summary>
/// <remarks>
/// When this property has a value, <see cref="GeneratedSources"/> property is guaranteed to be empty, and the <see cref="Diagnostics"/>
/// collection will contain a single diagnostic indicating that the generator failed.
/// </remarks>
public Exception? Exception { get; }
}
/// <summary>
/// Represents the results of an <see cref="ISourceGenerator"/> calling <see cref="SourceGeneratorContext.AddSource(string, SourceText)"/>.
/// </summary>
/// <remarks>
/// This contains the original <see cref="SourceText"/> added by the generator, along with the parsed representation of that text in <see cref="SyntaxTree"/>.
/// </remarks>
public readonly struct GeneratedSourceResult
{
internal GeneratedSourceResult(SyntaxTree tree, SourceText text, string hintName)
{
this.SyntaxTree = tree;
this.SourceText = text;
this.HintName = hintName;
}
/// <summary>
/// The <see cref="SyntaxTree"/> that was produced from parsing the <see cref="GeneratedSourceResult.SourceText"/>.
/// </summary>
public SyntaxTree SyntaxTree { get; }
/// <summary>
/// The <see cref="SourceText"/> that was added by the generator.
/// </summary>
public SourceText SourceText { get; }
/// <summary>
/// An identifier provided by the generator that identifies the added <see cref="SourceText"/>.
/// </summary>
public string HintName { get; }
}
}
......@@ -18,15 +18,17 @@ namespace Microsoft.CodeAnalysis
{
private readonly DiagnosticBag _diagnostics;
internal SourceGeneratorContext(Compilation compilation, ImmutableArray<AdditionalText> additionalTexts, AnalyzerConfigOptionsProvider optionsProvider, ISyntaxReceiver? syntaxReceiver, DiagnosticBag diagnostics, CancellationToken cancellationToken = default)
private readonly AdditionalSourcesCollection _additionalSources;
internal SourceGeneratorContext(Compilation compilation, ImmutableArray<AdditionalText> additionalTexts, AnalyzerConfigOptionsProvider optionsProvider, ISyntaxReceiver? syntaxReceiver, CancellationToken cancellationToken = default)
{
Compilation = compilation;
AdditionalFiles = additionalTexts;
AnalyzerConfigOptions = optionsProvider;
SyntaxReceiver = syntaxReceiver;
CancellationToken = cancellationToken;
AdditionalSources = new AdditionalSourcesCollection();
_diagnostics = diagnostics;
_additionalSources = new AdditionalSourcesCollection();
_diagnostics = new DiagnosticBag();
}
/// <summary>
......@@ -59,14 +61,12 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray<Addition
/// </summary>
public CancellationToken CancellationToken { get; }
internal AdditionalSourcesCollection AdditionalSources { get; }
/// <summary>
/// Adds a <see cref="SourceText"/> to the compilation
/// </summary>
/// <param name="hintName">An identifier that can be used to reference this source text, must be unique within this generator</param>
/// <param name="sourceText">The <see cref="SourceText"/> to add to the compilation</param>
public void AddSource(string hintName, SourceText sourceText) => AdditionalSources.Add(hintName, sourceText);
public void AddSource(string hintName, SourceText sourceText) => _additionalSources.Add(hintName, sourceText);
/// <summary>
/// Adds a <see cref="Diagnostic"/> to the users compilation
......@@ -76,6 +76,9 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray<Addition
/// The severity of the diagnostic may cause the compilation to fail, depending on the <see cref="Compilation"/> settings.
/// </remarks>
public void ReportDiagnostic(Diagnostic diagnostic) => _diagnostics.Add(diagnostic);
internal (ImmutableArray<GeneratedSourceText> sources, ImmutableArray<Diagnostic> diagnostics) ToImmutableAndFree()
=> (_additionalSources.ToImmutableAndFree(), _diagnostics.ToReadOnlyAndFree());
}
/// <summary>
......
......@@ -26,7 +26,7 @@ public SingleFileTestGenerator(string content, string hintName = "generatedFile"
public void Execute(SourceGeneratorContext context)
{
context.AdditionalSources.Add(this._hintName, SourceText.From(_content, Encoding.UTF8));
context.AddSource(this._hintName, SourceText.From(_content, Encoding.UTF8));
}
public void Initialize(InitializationContext context)
......@@ -59,7 +59,7 @@ public void Execute(SourceGeneratorContext context)
_onExecute(context);
if (!string.IsNullOrWhiteSpace(_source))
{
context.AdditionalSources.Add("source.cs", SourceText.From(_source, Encoding.UTF8));
context.AddSource("source.cs", SourceText.From(_source, Encoding.UTF8));
}
}
public void Initialize(InitializationContext context) => _onInit(context);
......@@ -80,7 +80,7 @@ public void Execute(SourceGeneratorContext context)
{
foreach (var file in context.AdditionalFiles)
{
AddSourceForAdditionalFile(context.AdditionalSources, file);
context.AddSource(GetGeneratedFileName(file.Path), SourceText.From("", Encoding.UTF8));
}
}
......@@ -93,14 +93,12 @@ bool UpdateContext(EditContext context, AdditionalFileEdit edit)
{
if (edit is AdditionalFileAddedEdit add && CanApplyChanges)
{
AddSourceForAdditionalFile(context.AdditionalSources, add.AddedText);
context.AdditionalSources.Add(GetGeneratedFileName(add.AddedText.Path), SourceText.From("", Encoding.UTF8));
return true;
}
return false;
}
private void AddSourceForAdditionalFile(AdditionalSourcesCollection sources, AdditionalText file) => sources.Add(GetGeneratedFileName(file.Path), SourceText.From("", Encoding.UTF8));
private string GetGeneratedFileName(string path) => $"{Path.GetFileNameWithoutExtension(path.Replace('\\', Path.DirectorySeparatorChar))}.generated";
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册