diff --git a/VisitorPatternGenerator.Tests/BaseSourceGeneratorTestContainer.cs b/VisitorPatternGenerator.Tests/BaseSourceGeneratorTestContainer.cs new file mode 100644 index 0000000..ffbfbe1 --- /dev/null +++ b/VisitorPatternGenerator.Tests/BaseSourceGeneratorTestContainer.cs @@ -0,0 +1,15 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +using RoseLynn.Testing; + +using VisitorPatternGenerator.Tests.Verifiers; + +namespace VisitorPatternGenerator.Tests; + +public abstract class BaseSourceGeneratorTestContainer: BaseIncrementalGeneratorTestContainer.Test> + where TGenerator : IIncrementalGenerator, new() +{ + protected override LanguageVersion LanguageVersion => LanguageVersion.CSharp11; +} diff --git a/VisitorPatternGenerator.Tests/RuntimeReferences.cs b/VisitorPatternGenerator.Tests/RuntimeReferences.cs new file mode 100644 index 0000000..7bcac5a --- /dev/null +++ b/VisitorPatternGenerator.Tests/RuntimeReferences.cs @@ -0,0 +1,15 @@ +using System.IO; + +using Microsoft.CodeAnalysis.Testing; + +namespace VisitorPatternGenerator.Tests; + +public static class RuntimeReferences +{ + public static readonly ReferenceAssemblies NET7_0Reference = + new( + "net7.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", "7.0.0"), + Path.Combine("ref", "net7.0")); +} diff --git a/VisitorPatternGenerator.Tests/UnitTest1.cs b/VisitorPatternGenerator.Tests/UnitTest1.cs deleted file mode 100644 index bf9eea0..0000000 --- a/VisitorPatternGenerator.Tests/UnitTest1.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Xunit; - -namespace VisitorPatternGenerator.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} diff --git a/VisitorPatternGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs b/VisitorPatternGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs new file mode 100644 index 0000000..8bdf2d6 --- /dev/null +++ b/VisitorPatternGenerator.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs @@ -0,0 +1,18 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +using RoseLynn.Testing; + +namespace VisitorPatternGenerator.Tests.Verifiers; + +public static class CSharpSourceGeneratorVerifier + where TGenerator : IIncrementalGenerator, new() +{ + public class Test: CSharpIncrementalGeneratorTestEx + { + public Test() + { + this.ReferenceAssemblies = RuntimeReferences.NET7_0Reference; + } + } +} diff --git a/VisitorPatternGenerator.Tests/VisitorPatternGenerator.Tests.csproj b/VisitorPatternGenerator.Tests/VisitorPatternGenerator.Tests.csproj index 3d58fed..95bbe3c 100644 --- a/VisitorPatternGenerator.Tests/VisitorPatternGenerator.Tests.csproj +++ b/VisitorPatternGenerator.Tests/VisitorPatternGenerator.Tests.csproj @@ -1,8 +1,9 @@ - + - net6.0 + net7.0 $(MSBuildProjectName) + false @@ -10,23 +11,30 @@ - + + + - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - runtime; build; native; contentfiles; analyzers; buildtransitive all + + + + + + + + + + Analyzer - fales + true diff --git a/VisitorPatternGenerator.Tests/VisitorPatternGeneratorTests.cs b/VisitorPatternGenerator.Tests/VisitorPatternGeneratorTests.cs new file mode 100644 index 0000000..dd8441e --- /dev/null +++ b/VisitorPatternGenerator.Tests/VisitorPatternGeneratorTests.cs @@ -0,0 +1,311 @@ +using NUnit.Framework; + +namespace VisitorPatternGenerator.Tests; + +public sealed class VisitorPatternGeneratorTests: BaseSourceGeneratorTestContainer +{ + [Test] + public void SimpleCase() + { + var source = """ + using VisitorPatternGenerator; + + namespace SurgicalThing.Tools; + + public struct Unit { } + + public interface ITool + { + bool IsAvailable { get; } + bool IsDisabled { get; set; } + } + + public abstract class Tool : ITool + { + public bool IsDisabled { get; set; } + + public virtual bool IsAvailable => !IsDisabled; + } + + public interface IConsumableTool : ITool + { + double CurrentDurability { get; set; } + double MaxDurability { get; set; } + + void ResetDurability(); + } + + public abstract class ConsumableTool : Tool, IConsumableTool + { + public double CurrentDurability { get; set; } + public double MaxDurability { get; set; } + + public override bool IsAvailable => CurrentDurability > 0 && base.IsAvailable; + + protected ConsumableTool(double maxDurability) + { + MaxDurability = maxDurability; + CurrentDurability = maxDurability; + } + + public void ResetDurability() + { + CurrentDurability = MaxDurability; + } + } + + public partial interface IMedication : IConsumableTool { } + + [Acceptor] + public abstract partial class Medication : ConsumableTool + { + protected Medication(double maxDurability) + : base(maxDurability) { } + } + + public partial interface IGeneralMedication { } + + [Acceptor] + [Acceptor] + public abstract partial class GeneralMedication : Medication + { + protected GeneralMedication(double maxDurability) + : base(maxDurability) { } + } + public partial interface ISpecializedMedication { } + + [Acceptor] + [Acceptor] + public abstract partial class SpecializedMedication : Medication + { + protected SpecializedMedication(double maxDurability) + : base(maxDurability) { } + } + + [Acceptor] + [Acceptor] + public sealed partial class Stabilizer : GeneralMedication + { + public Stabilizer(double maxDurability) + : base(maxDurability) { } + } + + // Visitors + [Visitor] + public partial interface IGeneratedMedicationVisitor { } + [Visitor] + public partial interface IGeneratedGeneralMedicationVisitor { } + [Visitor] + public partial interface IGeneratedSpecializedMedicationVisitor { } + [Visitor] + public partial interface IGeneratedToolVisitor { } + """; + + var annotationsTemplate = """ + // + #nullable enable + + namespace VisitorPatternGenerator; + + [System.Flags] + internal enum AcceptorOptions + { + None = 0, + MessagePackUnion = 0x100, + } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Interface | System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + internal sealed class AcceptorAttribute: System.Attribute + { + public AcceptorOptions Options { get; } + + public AcceptorAttribute(AcceptorOptions options = AcceptorOptions.None) { this.Options = options; } + } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class AcceptorAttribute: System.Attribute where TAcceptor : class { } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class AcceptorAttribute: System.Attribute where TAcceptor : class where TSelf : class { } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class AcceptorAttribute: System.Attribute where TAcceptor : class where TSelf : class { } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + internal sealed class VisitorAttribute: System.Attribute where TAcceptor : class + { + public bool VoidReturn { get; } + + public VisitorAttribute(bool voidReturn = false) { this.VoidReturn = voidReturn; } + } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + internal sealed class VisitorAttribute: System.Attribute where TAcceptor : class { } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + internal sealed class TaskVisitorAttribute: System.Attribute where TAcceptor : class + { + public bool VoidReturn { get; } + + public TaskVisitorAttribute(bool voidReturn = false) { this.VoidReturn = voidReturn; } + } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + internal sealed class TaskVisitorAttribute: System.Attribute where TAcceptor : class { } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + internal sealed class ValueTaskVisitorAttribute: System.Attribute where TAcceptor : class + { + public bool VoidReturn { get; } + + public ValueTaskVisitorAttribute(bool voidReturn = false) { this.VoidReturn = voidReturn; } + } + + [System.Diagnostics.Conditional("VISITOR_PATTERN_GENERATOR_PRESERVE_ANNOTATION")] + [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + internal sealed class ValueTaskVisitorAttribute: System.Attribute where TAcceptor : class { } + + + """; + + var generatedMedicineVisitor = """ + // + + namespace SurgicalThing.Tools + { + partial interface IMedication + { + SurgicalThing.Tools.Unit Accept(SurgicalThing.Tools.IGeneratedMedicationVisitor visitor); + } + } + + namespace SurgicalThing.Tools + { + partial interface IGeneratedMedicationVisitor + { + SurgicalThing.Tools.Unit Visit(SurgicalThing.Tools.Medication value); + SurgicalThing.Tools.Unit Visit(SurgicalThing.Tools.GeneralMedication value); + SurgicalThing.Tools.Unit Visit(SurgicalThing.Tools.SpecializedMedication value); + SurgicalThing.Tools.Unit Visit(SurgicalThing.Tools.Stabilizer value); + } + } + + namespace SurgicalThing.Tools + { + partial class Medication: SurgicalThing.Tools.IMedication + { + SurgicalThing.Tools.Unit SurgicalThing.Tools.IMedication.Accept(SurgicalThing.Tools.IGeneratedMedicationVisitor visitor) => visitor.Visit(this); + } + } + + namespace SurgicalThing.Tools + { + partial class GeneralMedication: SurgicalThing.Tools.IMedication + { + SurgicalThing.Tools.Unit SurgicalThing.Tools.IMedication.Accept(SurgicalThing.Tools.IGeneratedMedicationVisitor visitor) => visitor.Visit(this); + } + } + + namespace SurgicalThing.Tools + { + partial class SpecializedMedication: SurgicalThing.Tools.IMedication + { + SurgicalThing.Tools.Unit SurgicalThing.Tools.IMedication.Accept(SurgicalThing.Tools.IGeneratedMedicationVisitor visitor) => visitor.Visit(this); + } + } + + namespace SurgicalThing.Tools + { + partial class Stabilizer: SurgicalThing.Tools.IMedication + { + SurgicalThing.Tools.Unit SurgicalThing.Tools.IMedication.Accept(SurgicalThing.Tools.IGeneratedMedicationVisitor visitor) => visitor.Visit(this); + } + } + + """; + var generatedGeneralMedicationVisitor = """ + // + + namespace SurgicalThing.Tools + { + partial interface IGeneralMedication + { + SurgicalThing.Tools.Unit Accept(SurgicalThing.Tools.IGeneratedGeneralMedicationVisitor visitor); + } + } + + namespace SurgicalThing.Tools + { + partial interface IGeneratedGeneralMedicationVisitor + { + SurgicalThing.Tools.Unit Visit(SurgicalThing.Tools.GeneralMedication value); + SurgicalThing.Tools.Unit Visit(SurgicalThing.Tools.Stabilizer value); + } + } + + namespace SurgicalThing.Tools + { + partial class GeneralMedication: SurgicalThing.Tools.IGeneralMedication + { + SurgicalThing.Tools.Unit SurgicalThing.Tools.IGeneralMedication.Accept(SurgicalThing.Tools.IGeneratedGeneralMedicationVisitor visitor) => visitor.Visit(this); + } + } + + namespace SurgicalThing.Tools + { + partial class Stabilizer: SurgicalThing.Tools.IGeneralMedication + { + SurgicalThing.Tools.Unit SurgicalThing.Tools.IGeneralMedication.Accept(SurgicalThing.Tools.IGeneratedGeneralMedicationVisitor visitor) => visitor.Visit(this); + } + } + + """; + var generatedSpecializedMedicationVisitor = """ + // + + namespace SurgicalThing.Tools + { + partial interface ISpecializedMedication + { + SurgicalThing.Tools.Unit Accept(SurgicalThing.Tools.IGeneratedSpecializedMedicationVisitor visitor); + } + } + + namespace SurgicalThing.Tools + { + partial interface IGeneratedSpecializedMedicationVisitor + { + SurgicalThing.Tools.Unit Visit(SurgicalThing.Tools.SpecializedMedication value); + } + } + + namespace SurgicalThing.Tools + { + partial class SpecializedMedication: SurgicalThing.Tools.ISpecializedMedication + { + SurgicalThing.Tools.Unit SurgicalThing.Tools.ISpecializedMedication.Accept(SurgicalThing.Tools.IGeneratedSpecializedMedicationVisitor visitor) => visitor.Visit(this); + } + } + + """; + + var mappings = new (string fileName, string source)[] { + ("AnnotationsTemplate.cs", annotationsTemplate), + + ("SurgicalThing.Tools.IGeneratedMedicationVisitor.cs", generatedMedicineVisitor), + ("SurgicalThing.Tools.IGeneratedGeneralMedicationVisitor.cs", generatedGeneralMedicationVisitor), + ("SurgicalThing.Tools.IGeneratedSpecializedMedicationVisitor.cs", generatedSpecializedMedicationVisitor), + }; + + this.VerifyAsync(source, mappings, default).Wait(); + } +} diff --git a/VisitorPatternGenerator/VisitorPatternGenerator.cs b/VisitorPatternGenerator/VisitorPatternGenerator.cs index 8e43116..21fb626 100644 --- a/VisitorPatternGenerator/VisitorPatternGenerator.cs +++ b/VisitorPatternGenerator/VisitorPatternGenerator.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -82,6 +81,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(visitorProvider, static (ctx, e) => { var ((visitor, acceptors), rootNamespace) = e; ctx.CancellationToken.ThrowIfCancellationRequested(); + + if (acceptors.IsDefaultOrEmpty) { + return; + } + _AddVisitorSource(ctx, rootNamespace, visitor, acceptors); }); }