diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 6e35ba05afac9beff341fc44b6a33efaec64f5a4..98a607631d2103d87b66aed44636c59442b0107d 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6436,4 +6436,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ extension GetEnumerator - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs index ce8b9a5fcfa261941f4e8fcc98d98470b23fd698..42a45ec2c6314499d2d28a582b2fab9f7ec7cbda 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs @@ -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.Empty, CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(generator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(generator, generator2, generator3), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(generator, generator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(generator, generator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(testGenerator, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty); + var oldDriver = driver; + + Assert.Throws(() => + driver = driver.RunFullGeneration(compilation, out var outputCompilation, out var outputDiagnostics, cts.Token) + ); + Assert.Same(oldDriver, driver); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs index 6c77bd9acf0d6127a4d6d89dfa8e2938fd99a248..de043660d28a81e16bbc170947ea3b15f26427f5 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs @@ -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(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.Empty); driver.RunFullGeneration(compilation, out _, out _); - void Initialize(InitializationContext initContext) + void initialize(InitializationContext initContext) { initContext.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()); Assert.Throws(() => @@ -192,21 +194,248 @@ void Function() Assert.IsType(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(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(testGenerator1, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(testGenerator, testGenerator2), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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(testGenerator), CompilerAnalyzerConfigOptionsProvider.Empty, ImmutableArray.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? _callback; + public List VisitedNodes { get; } = new List(); public int Tag { get; } - public TestSyntaxReceiver(int tag = 0) + public TestSyntaxReceiver(int tag = 0, Action? callback = null) { Tag = tag; + _callback = callback; } public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { VisitedNodes.Add(syntaxNode); + if (_callback is object) + { + _callback(syntaxNode); + } } } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index ba677b3ca322d0217a7bb7a73d06a1a796ed392e..5de1fb54c9822aaa70b49dd798499f77fb314890 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -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.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.get -> int Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.set -> void +Microsoft.CodeAnalysis.GeneratorDriverRunResult.Diagnostics.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.GeneratorDriverRunResult.GeneratedTrees.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.GeneratorDriverRunResult.Results.get -> System.Collections.Immutable.ImmutableArray 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 const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointMethodName = "
$" -> string const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointTypeName = "$" -> 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 action) -> void virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterAdditionalFileAction(System.Action 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.GeneratorRunResult.Exception.get -> System.Exception +Microsoft.CodeAnalysis.GeneratorRunResult.Generator.get -> Microsoft.CodeAnalysis.ISourceGenerator \ No newline at end of file diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs index f778253566c8da98833705b2f97528840f95351e..bb9913c70a229e7b054753fdf465bd87846f0ea3 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @@ -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.Empty, editsFailed: true); } - public GeneratorDriver RunFullGeneration(Compilation compilation, out Compilation outputCompilation, out ImmutableArray 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.Empty; - return this; - } - - // run the actual generation - var state = StateWithPendingEditsApplied(_state); - var stateBuilder = ArrayBuilder.GetInstance(); - var receivers = PooledDictionary.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 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 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.GetInstance(state.Generators.Length); + var walkerBuilder = ArrayBuilder.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 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.Empty))); } } state = edit.Commit(state); @@ -234,28 +305,12 @@ private static GeneratorDriverState StateWithPendingEditsApplied(GeneratorDriver return state.With(edits: ImmutableArray.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 trees = ArrayBuilder.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 ParseAdditionalSources(ISourceGenerator generator, ImmutableArray generatedSources, CancellationToken cancellationToken) + private ImmutableArray ParseAdditionalSources(ISourceGenerator generator, ImmutableArray generatedSources, CancellationToken cancellationToken) { - var trees = PooledDictionary.GetInstance(); + var trees = ArrayBuilder.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 trees = ArrayBuilder.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); diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs index 561ab96fb07695d40b4a545c4a1b19c5d3b6dde3..48ba08c000f0f5165058b6270ecb2e71985eeda4 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs @@ -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 { + /// + /// Represents the current state of a generator + /// internal readonly struct GeneratorState { + /// + /// Gets an uninitalized generator state + /// + internal static GeneratorState Uninitialized; + + /// + /// Creates a new generator state that just contains information + /// public GeneratorState(GeneratorInfo info) - : this(info, ImmutableDictionary.Empty) + : this(info, ImmutableArray.Empty, ImmutableArray.Empty, ImmutableArray.Empty, syntaxReceiver: null, exception: null) + { + } + + /// + /// Creates a new generator state that contains an exception and the associated diagnostic + /// + public GeneratorState(GeneratorInfo info, Exception e, Diagnostic error) + : this(info, ImmutableArray.Empty, ImmutableArray.Empty, ImmutableArray.Create(error), syntaxReceiver: null, exception: e) { } - private GeneratorState(GeneratorInfo info, ImmutableDictionary sources) + /// + /// Creates a generator state that contains results + /// + public GeneratorState(GeneratorInfo info, ImmutableArray sourceTexts, ImmutableArray trees, ImmutableArray diagnostics) + : this(info, sourceTexts, trees, diagnostics, syntaxReceiver: null, exception: null) { - this.Sources = sources; + } + + private GeneratorState(GeneratorInfo info, ImmutableArray sourceTexts, ImmutableArray trees, ImmutableArray 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 Sources { get; } + internal ImmutableArray SourceTexts { get; } + + internal ImmutableArray Trees { get; } internal GeneratorInfo Info { get; } - internal GeneratorState WithSources(ImmutableDictionary sources) + internal ISyntaxReceiver? SyntaxReceiver { get; } + + internal Exception? Exception { get; } + + internal ImmutableArray Diagnostics { get; } + + /// + /// Adds an to this generator state + /// + 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); } } } diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs index 7503895a664598631a2e7e91b085674948ba1968..8b804b74d33582865acc5721db5d7d2ea98a2ad7 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorSyntaxWalker.cs @@ -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 _syntaxReceivers; + private readonly ISyntaxReceiver _syntaxReceiver; - public GeneratorSyntaxWalker(ImmutableArray 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); } } - } diff --git a/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs b/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs new file mode 100644 index 0000000000000000000000000000000000000000..ca17e69216e0faf3611fe1ec1beffe849b41dbcb --- /dev/null +++ b/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs @@ -0,0 +1,146 @@ +// 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 +{ + /// + /// Represents the results of running a generation pass over a set of s. + /// + public class GeneratorDriverRunResult + { + private ImmutableArray _lazyDiagnostics; + + private ImmutableArray _lazyGeneratedTrees; + + internal GeneratorDriverRunResult(ImmutableArray results) + { + this.Results = results; + } + + /// + /// The individual result of each that was run in this generator pass, one per generator. + /// + public ImmutableArray Results { get; } + + /// + /// The s produced by all generators run during this generation pass. + /// + /// + /// This is equivalent to the union of all in . + /// + public ImmutableArray Diagnostics + { + get + { + if (_lazyDiagnostics.IsDefault) + { + ImmutableInterlocked.InterlockedInitialize(ref _lazyDiagnostics, Results.SelectMany(r => r.Diagnostics).ToImmutableArray()); + } + return _lazyDiagnostics; + } + } + + /// + /// The s produced during this generation pass by parsing each added by each generator. + /// + /// + /// This is equivalent to the union of all s in each in each + /// + public ImmutableArray GeneratedTrees + { + get + { + if (_lazyGeneratedTrees.IsDefault) + { + ImmutableInterlocked.InterlockedInitialize(ref _lazyGeneratedTrees, Results.SelectMany(r => r.GeneratedSources.Select(g => g.SyntaxTree)).ToImmutableArray()); + } + return _lazyGeneratedTrees; + } + } + } + + /// + /// Represents the results of a single generation pass. + /// + public readonly struct GeneratorRunResult + { + internal GeneratorRunResult(ISourceGenerator generator, ImmutableArray generatedSources, ImmutableArray 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; + } + + /// + /// The that produced this result. + /// + public ISourceGenerator Generator { get; } + + /// + /// The sources that were added by during the generation pass this result represents. + /// + public ImmutableArray GeneratedSources { get; } + + /// + /// A collection of s reported by + /// + /// + /// When generation fails due to an being thrown, a single diagnostic is added + /// to represent the failure. Any generator reported diagnostics up to the failure point are not included. + /// + public ImmutableArray Diagnostics { get; } + + /// + /// An instance that was thrown by the generator, or null if the generator completed without error. + /// + /// + /// When this property has a value, property is guaranteed to be empty, and the + /// collection will contain a single diagnostic indicating that the generator failed. + /// + public Exception? Exception { get; } + } + + /// + /// Represents the results of an calling . + /// + /// + /// This contains the original added by the generator, along with the parsed representation of that text in . + /// + public readonly struct GeneratedSourceResult + { + internal GeneratedSourceResult(SyntaxTree tree, SourceText text, string hintName) + { + this.SyntaxTree = tree; + this.SourceText = text; + this.HintName = hintName; + } + + /// + /// The that was produced from parsing the . + /// + public SyntaxTree SyntaxTree { get; } + + /// + /// The that was added by the generator. + /// + public SourceText SourceText { get; } + + /// + /// An identifier provided by the generator that identifies the added . + /// + public string HintName { get; } + } +} diff --git a/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs b/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs index 3a23d44933141117e1d30eacaf85b668d73ee068..e962d90211454b832610ccf58e73e5e99f264880 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/SourceGeneratorContext.cs @@ -18,15 +18,17 @@ namespace Microsoft.CodeAnalysis { private readonly DiagnosticBag _diagnostics; - internal SourceGeneratorContext(Compilation compilation, ImmutableArray additionalTexts, AnalyzerConfigOptionsProvider optionsProvider, ISyntaxReceiver? syntaxReceiver, DiagnosticBag diagnostics, CancellationToken cancellationToken = default) + private readonly AdditionalSourcesCollection _additionalSources; + + internal SourceGeneratorContext(Compilation compilation, ImmutableArray 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(); } /// @@ -59,14 +61,12 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray public CancellationToken CancellationToken { get; } - internal AdditionalSourcesCollection AdditionalSources { get; } - /// /// Adds a to the compilation /// /// An identifier that can be used to reference this source text, must be unique within this generator /// The to add to the compilation - public void AddSource(string hintName, SourceText sourceText) => AdditionalSources.Add(hintName, sourceText); + public void AddSource(string hintName, SourceText sourceText) => _additionalSources.Add(hintName, sourceText); /// /// Adds a to the users compilation @@ -76,6 +76,9 @@ internal SourceGeneratorContext(Compilation compilation, ImmutableArray settings. /// public void ReportDiagnostic(Diagnostic diagnostic) => _diagnostics.Add(diagnostic); + + internal (ImmutableArray sources, ImmutableArray diagnostics) ToImmutableAndFree() + => (_additionalSources.ToImmutableAndFree(), _diagnostics.ToReadOnlyAndFree()); } /// diff --git a/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs b/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs index 3f8a1282a6f6f41076e71c5f79aff522a45393eb..d7f8b65d098fbe55d5de1dbd1c02e1cfeb66e5f6 100644 --- a/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs +++ b/src/Test/Utilities/Portable/SourceGeneration/TestGenerators.cs @@ -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"; }