public void MultiplePatchType_ExtensionAndTypeAddedToSource() { Compilation inputCompilation = CreateCompilation(@" namespace TestCode2 { public class Dto0 { public double Property { get; set; } } public class Dto1 { public Dto0 Property { get; set; } } public class Program { public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch<Dto0> data) { } public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch<Dto1> data) { } } } "); JsonMergePatchSourceGenerator generator = new JsonMergePatchSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); Assert.True(diagnostics.IsEmpty); Assert.True(outputCompilation.SyntaxTrees.Count() == 4); Assert.True(outputCompilation.GetDiagnostics().IsEmpty); GeneratorDriverRunResult runResult = driver.GetRunResult(); Assert.Equal(3, runResult.GeneratedTrees.Length); Assert.Empty(runResult.Diagnostics); }
public void AddJsonMergePatch_AddsExtensionMethod() { // Create the 'input' compilation that the generator will act on Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { public class Dto { public int Property { get; set; } } public class Program { public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch<Dto> data) { } } } "); var commonGenerator = new JsonMergePatchSourceGenerator(); var sut = new AspNetJsonMergePatchSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(commonGenerator, sut); driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); Assert.True(diagnostics.IsEmpty); GeneratorDriverRunResult runResult = driver.GetRunResult(); GeneratorRunResult testedGenerator = runResult.Results[1]; Assert.Single(testedGenerator.GeneratedSources); Assert.Empty(testedGenerator.Diagnostics); }
public void DefaultRecordType_ExtensionAndTypeAddedToSource() { // Create the 'input' compilation that the generator will act on Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { public record Dto(int Property0, string Property1); public class Program { public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch<Dto> data) { } } } "); JsonMergePatchSourceGenerator generator = new JsonMergePatchSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); Assert.True(diagnostics.IsEmpty); Assert.True(outputCompilation.SyntaxTrees.Count() == 3); Assert.True(outputCompilation.GetDiagnostics().IsEmpty); GeneratorDriverRunResult runResult = driver.GetRunResult(); Assert.Equal(2, runResult.GeneratedTrees.Length); Assert.Empty(runResult.Diagnostics); }
readonly bool IGeneratorTestResult.Compare(GeneratorDriverRunResult result) { if (result is null || result.GeneratedTrees.IsDefaultOrEmpty) { return(false); } if (result.GeneratedTrees[0] is not CSharpSyntaxTree tree) { return(false); } return(SyntaxTree?.IsEquivalentTo(tree) ?? false); }
public void Verify_generator_works() { // Create the 'input' compilation that the generator will act on Compilation inputCompilation = CreateCompilation(@" [assembly: NServiceBusEndpointName(""test"")] namespace MyCode { public class Program { public static void Main(string[] args) { } } } "); // directly create an instance of the generator // (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime) var generator = new CodeGenLibrary.FunctionEndpointTriggerGenerator(); // Create the driver that will control the generation, passing in our generator GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); // Run the generation pass // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls) driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); // We can now assert things about the resulting compilation: Debug.Assert(diagnostics.IsEmpty); // there were no diagnostics created by the generators Debug.Assert(outputCompilation.SyntaxTrees.Count() == 3); // we have 3 syntax trees, the original 'user' provided one, attribute, and a trigger class //Debug.Assert(outputCompilation.GetDiagnostics().IsEmpty); // verify the compilation with the added source has no diagnostics // Or we can look at the results directly: GeneratorDriverRunResult runResult = driver.GetRunResult(); // The runResult contains the combined results of all generators passed to the driver Debug.Assert(runResult.GeneratedTrees.Length == 2); Debug.Assert(runResult.Diagnostics.IsEmpty); // Or you can access the individual results on a by-generator basis GeneratorRunResult generatorResult = runResult.Results[0]; Debug.Assert(generatorResult.Generator == generator); Debug.Assert(generatorResult.Diagnostics.IsEmpty); Debug.Assert(generatorResult.GeneratedSources.Length == 2); Debug.Assert(generatorResult.Exception is null); }
public static void AssertGeneratedFile(this GeneratorDriverRunResult result, string fileName, Action <SyntaxTree>?treeAssertion = null) { SyntaxTree?foundTree = null; foreach (var tree in result.GeneratedTrees) { if (tree.FilePath == $"TypedIds\\TypedIds.Generator\\{fileName}") { foundTree = tree; break; } } Assert.NotNull(foundTree); treeAssertion?.Invoke(foundTree !); }
readonly bool IGeneratorTestResult.Compare(GeneratorDriverRunResult result) { if (result.GeneratedTrees.Length != Length) { return(false); } int length = Length; for (int i = 0; i < length; i++) { if (!result.GeneratedTrees[i].IsEquivalentTo(GeneratedSources[i].SyntaxTree)) { return(false); } } return(true); }
internal static async Task <IReadOnlyList <Diagnostic> > RunGenerator( string code, bool compile = false, LanguageVersion langVersion = LanguageVersion.Preview, MetadataReference[]?additionalRefs = null, bool allowUnsafe = false, CancellationToken cancellationToken = default) { var proj = new AdhocWorkspace() .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) .AddProject("RegexGeneratorTest", "RegexGeneratorTest.dll", "C#") .WithMetadataReferences(additionalRefs is not null ? References.Concat(additionalRefs) : References) .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe) .WithNullableContextOptions(NullableContextOptions.Enable)) .WithParseOptions(new CSharpParseOptions(langVersion)) .AddDocument("RegexGenerator.g.cs", SourceText.From(code, Encoding.UTF8)).Project; Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); Compilation?comp = await proj !.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); Debug.Assert(comp is not null); var generator = new RegexGenerator(); CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(langVersion)); GeneratorDriver gd = cgd.RunGenerators(comp !, cancellationToken); GeneratorDriverRunResult generatorResults = gd.GetRunResult(); if (!compile) { return(generatorResults.Diagnostics); } comp = comp.AddSyntaxTrees(generatorResults.GeneratedTrees.ToArray()); EmitResult results = comp.Emit(Stream.Null, cancellationToken: cancellationToken); ImmutableArray <Diagnostic> generatorDiagnostics = generatorResults.Diagnostics.RemoveAll(d => d.Severity <= DiagnosticSeverity.Hidden); ImmutableArray <Diagnostic> resultsDiagnostics = results.Diagnostics.RemoveAll(d => d.Severity <= DiagnosticSeverity.Hidden); if (!results.Success || resultsDiagnostics.Length != 0 || generatorDiagnostics.Length != 0) { throw new ArgumentException( string.Join(Environment.NewLine, resultsDiagnostics.Concat(generatorDiagnostics)) + Environment.NewLine + string.Join(Environment.NewLine, generatorResults.GeneratedTrees.Select(t => t.ToString()))); } return(generatorResults.Diagnostics.Concat(results.Diagnostics).Where(d => d.Severity != DiagnosticSeverity.Hidden).ToArray()); }
/// <summary> /// Runs a Roslyn generator over a set of source files. /// </summary> public static async Task <(ImmutableArray <Diagnostic>, ImmutableArray <GeneratedSourceResult>)> RunGenerator( ISourceGenerator generator, IEnumerable <Assembly>?references, IEnumerable <string> sources, AnalyzerConfigOptionsProvider?optionsProvider = null, bool includeBaseReferences = true, CancellationToken cancellationToken = default) { Project proj = CreateTestProject(references, includeBaseReferences); proj = proj.WithDocuments(sources); Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); Compilation?comp = await proj !.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator }, optionsProvider: optionsProvider); GeneratorDriver gd = cgd.RunGenerators(comp !, cancellationToken); GeneratorDriverRunResult r = gd.GetRunResult(); return(r.Results[0].Diagnostics, r.Results[0].GeneratedSources); }
public override void Assert(GeneratorDriverRunResult result) { result.Diagnostics.ShouldBeEmpty(); }
public static GeneratorDriverRunResultAssertions Should(this GeneratorDriverRunResult generatorDriverRunResult) => new (generatorDriverRunResult);
internal static async Task <Regex> SourceGenRegexAsync( string pattern, RegexOptions?options = null, TimeSpan?matchTimeout = null, CancellationToken cancellationToken = default) { Assert.True(options is not null || matchTimeout is null); string attr = $"[RegexGenerator({SymbolDisplay.FormatLiteral(pattern, quote: true)}"; if (options is not null) { attr += $", {string.Join(" | ", options.ToString().Split(',').Select(o => $"RegexOptions.{o.Trim()}"))}"; if (matchTimeout is not null) { attr += string.Create(CultureInfo.InvariantCulture, $", {(int)matchTimeout.Value.TotalMilliseconds}"); } } attr += ")]"; // Create the source boilerplate for the pattern string code = $@" using System.Text.RegularExpressions; public partial class C {{ {attr} public static partial Regex Get(); }}"; // Use a cached compilation to save a little time. Rather than creating an entirely new workspace // for each test, just create a single compilation, cache it, and then replace its syntax tree // on each test. if (s_compilation is not Compilation comp) { // Create the project containing the source. var proj = new AdhocWorkspace() .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) .AddProject("Test", "test.dll", "C#") .WithMetadataReferences(s_refs) .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) .WithNullableContextOptions(NullableContextOptions.Enable)) .WithParseOptions(new CSharpParseOptions(LanguageVersion.Preview)) .AddDocument("RegexGenerator.g.cs", SourceText.From("// Empty", Encoding.UTF8)).Project; Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); s_compilation = comp = await proj !.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); Debug.Assert(comp is not null); } comp = comp.ReplaceSyntaxTree(comp.SyntaxTrees.First(), CSharpSyntaxTree.ParseText(SourceText.From(code, Encoding.UTF8), s_previewParseOptions)); // Run the generator GeneratorDriverRunResult generatorResults = s_generatorDriver.RunGenerators(comp !, cancellationToken).GetRunResult(); if (generatorResults.Diagnostics.Length != 0) { throw new ArgumentException( string.Join(Environment.NewLine, generatorResults.Diagnostics) + Environment.NewLine + string.Join(Environment.NewLine, generatorResults.GeneratedTrees.Select(t => NumberLines(t.ToString())))); } // Compile the assembly to a stream var dll = new MemoryStream(); comp = comp.AddSyntaxTrees(generatorResults.GeneratedTrees.ToArray()); EmitResult results = comp.Emit(dll, options: s_emitOptions, cancellationToken: cancellationToken); if (!results.Success || results.Diagnostics.Length != 0) { throw new ArgumentException( string.Join(Environment.NewLine, results.Diagnostics.Concat(generatorResults.Diagnostics)) + Environment.NewLine + string.Join(Environment.NewLine, generatorResults.GeneratedTrees.Select(t => NumberLines(t.ToString())))); } dll.Position = 0; // Load the assembly into its own AssemblyLoadContext. var alc = new RegexLoadContext(Environment.CurrentDirectory); Assembly a = alc.LoadFromStream(dll); // Instantiate a regex using the newly created static Get method that was source generated. Regex r = (Regex)a.GetType("C") !.GetMethod("Get") !.Invoke(null, null) !; // Issue an unload on the ALC, so it'll be collected once the Regex instance is collected alc.Unload(); return(r); }
public void SimpleGeneratorTest() { // Input for our generator. Compilation inputCompilation = CreateCompilation(@" namespace WalletWasabi.Fluent.ViewModels { public class TestViewModel2 { [AutoNotify(PropertyName = ""TestName"")] private bool _prop1; [AutoNotify(SetterModifier = AccessModifier.None)] private bool _prop2 = false; [AutoNotify(SetterModifier = AccessModifier.Public)] private bool _prop3; [AutoNotify(SetterModifier = AccessModifier.Protected)] private bool _prop4; [AutoNotify(SetterModifier = AccessModifier.Private)] private bool _prop5; [AutoNotify(SetterModifier = AccessModifier.Internal)] private bool _prop6; } } "); AutoNotifyGenerator generator = new(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); // Run the generation pass driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); Assert.True(diagnostics.IsEmpty); Assert.Equal(4, outputCompilation.SyntaxTrees.Count()); GeneratorDriverRunResult runResult = driver.GetRunResult(); Assert.Equal(3, runResult.GeneratedTrees.Length); Assert.True(runResult.Diagnostics.IsEmpty); GeneratorRunResult generatorResult = runResult.Results[0]; Assert.True(generatorResult.Exception is null); Assert.Equal(generatorResult.Generator, generator); Assert.True(generatorResult.Diagnostics.IsEmpty); Assert.Equal(3, generatorResult.GeneratedSources.Length); string expectedGeneratedSourceCode = @" // <auto-generated /> #nullable enable using ReactiveUI; namespace WalletWasabi.Fluent.ViewModels { public partial class TestViewModel2 : ReactiveUI.ReactiveObject { public bool TestName { get => _prop1; set => this.RaiseAndSetIfChanged(ref _prop1, value); } public bool Prop2 { get => _prop2; } public bool Prop3 { get => _prop3; set => this.RaiseAndSetIfChanged(ref _prop3, value); } public bool Prop4 { get => _prop4; protected set => this.RaiseAndSetIfChanged(ref _prop4, value); } public bool Prop5 { get => _prop5; private set => this.RaiseAndSetIfChanged(ref _prop5, value); } public bool Prop6 { get => _prop6; internal set => this.RaiseAndSetIfChanged(ref _prop6, value); } } }".Trim(); Assert.Equal(expectedGeneratedSourceCode, generatorResult.GeneratedSources[2].SourceText.ToString()); }
private static void ExecuteGenerator(IEnumerable <AdditionalText> additionalTexts, out Compilation outputCompilation, out GeneratorDriverRunResult runResult) { Compilation inputCompilation = CreateCompilation(); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new FriendlyWordsSourceGenerator() }, additionalTexts); // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls) driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out outputCompilation, out ImmutableArray <Diagnostic> _); runResult = driver.GetRunResult(); }
public abstract void Assert(GeneratorDriverRunResult result);
public void GenerationFormat() { var source = @"using DevExpress.Mvvm.CodeGenerators; namespace Test { [GenerateViewModel(ImplementINotifyPropertyChanging = true, ImplementIDataErrorInfo = true, ImplementISupportServices = true)] partial class Example { [GenerateProperty] [System.ComponentModel.DataAnnotations.Range(0, 1)] int property; [GenerateCommand] public void Method(int arg) { } } public class Program { public static void Main(string[] args) { } } } "; Compilation inputCompilation = CSharpCompilation.Create("MyCompilation", new[] { CSharpSyntaxTree.ParseText(source) }, new[] { MetadataReference.CreateFromFile(typeof(DelegateCommand).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.RangeAttribute).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), MetadataReference.CreateFromFile(typeof(object).Assembly.Location), }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); ViewModelGenerator generator = new ViewModelGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); GeneratorDriverRunResult runResult = driver.GetRunResult(); GeneratorRunResult generatorResult = runResult.Results[0]; var generatedCode = generatorResult.GeneratedSources[1].SourceText.ToString(); var tabs = 0; foreach (var str in generatedCode.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) { if (string.IsNullOrEmpty(str)) { continue; } if (str.Contains("}") && !str.Contains("{")) { if (str.EndsWith("}")) { tabs--; } else { Assert.Fail(); } } var expectedLeadingWhitespaceCount = tabs * 4; var leadingWhitespaceCount = str.Length - str.TrimStart().Length; Assert.AreEqual(expectedLeadingWhitespaceCount, leadingWhitespaceCount); if (str.EndsWith("{")) { tabs++; } } }
public void Test_InterfaceExtensionsGenerator() { // Create the 'input' compilation that the generator will act on Compilation inputCompilation = CreateCompilation(@" using System; namespace MyCode { [UltimateOrb.CodeAnalysis.SourceGenerators.GeneratExtensions] interface IInterfaceA<T> { bool Property1 { get; } internal bool Property2 { set; } public bool Property3 { get; protected set; } static int Method4() { return 123; } public long Method5() { return 123; } void Method6(ref long arg1, in uint? arg2 , in string? arg3, Guid arg4) { return; } protected void Method7(ref ulong arg1, in uint? arg2, in string? arg3, Guid arg4); void Method8(ref nint arg1, in UIntPtr? arg2, in string? arg3, Guid arg4); } } "); // directly create an instance of the generator // (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime) var generator = new InterfaceExtensionsGenerator(); // Create the driver that will control the generation, passing in our generator GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); // Run the generation pass // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls) driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); // We can now assert things about the resulting compilation: Debug.Assert(diagnostics.IsEmpty); // there were no diagnostics created by the generators Debug.Assert(outputCompilation.SyntaxTrees.Count() == 2); // we have two syntax trees, the original 'user' provided one, and the one added by the generator System.Collections.Immutable.ImmutableArray <Diagnostic> diagnostics1 = outputCompilation.GetDiagnostics(); Debug.Assert(diagnostics1.IsEmpty); // verify the compilation with the added source has no diagnostics // Or we can look at the results directly: GeneratorDriverRunResult runResult = driver.GetRunResult(); // The runResult contains the combined results of all generators passed to the driver Debug.Assert(runResult.GeneratedTrees.Length == 1); Debug.Assert(runResult.Diagnostics.IsEmpty); // Or you can access the individual results on a by-generator basis GeneratorRunResult generatorResult = runResult.Results[0]; Debug.Assert(generatorResult.Generator == generator); Debug.Assert(generatorResult.Diagnostics.IsEmpty); Debug.Assert(generatorResult.GeneratedSources.Length == 1); Debug.Assert(generatorResult.Exception is null); }