diff --git a/EasySourceGenerators.Examples/PiExample.cs b/EasySourceGenerators.Examples/PiExample.cs index f740cbc..f901bfc 100644 --- a/EasySourceGenerators.Examples/PiExample.cs +++ b/EasySourceGenerators.Examples/PiExample.cs @@ -1,4 +1,8 @@ -using EasySourceGenerators.Abstractions; +// SwitchCase/SwitchDefault attribute-based generation is commented out pending replacement with a data-driven approach. +// See DataMethodBodyBuilders.cs for details on the planned replacement. + +/* +using EasySourceGenerators.Abstractions; // ReSharper disable ConvertClosureToMethodGroup namespace EasySourceGenerators.Examples; @@ -18,6 +22,7 @@ static int GetPiDecimal_Generator_Specialized(int decimalNumber) => [SwitchDefault] static Func GetPiDecimal_Generator_Fallback() => decimalNumber => SlowMath.CalculatePiDecimal(decimalNumber); } +*/ /* This will generate the following method: diff --git a/EasySourceGenerators.GeneratorTests/GeneratesMethodExecutionRuntimeTests.cs b/EasySourceGenerators.GeneratorTests/GeneratesMethodExecutionRuntimeTests.cs index 4ec682f..5f001ca 100644 --- a/EasySourceGenerators.GeneratorTests/GeneratesMethodExecutionRuntimeTests.cs +++ b/EasySourceGenerators.GeneratorTests/GeneratesMethodExecutionRuntimeTests.cs @@ -200,6 +200,9 @@ public static class GenHost Assert.That(result.error, Does.Contain("FormatException")); } + // ExecuteFluentGeneratorMethod with SwitchBodyData is commented out pending replacement + // with the data abstraction layer. See DataMethodBodyBuilders.cs for details. + /* [Test] public void ExecuteFluentGeneratorMethod_CollectsSwitchBodyData() { @@ -246,9 +249,49 @@ private static IMethodBodyGenerator GenerateMethod() Assert.That(result.record.CasePairs.Select(pair => pair.key), Is.EqualTo(new object[] { 1, 2, 3, 4 })); Assert.That(result.record.CasePairs.Select(pair => pair.value), Is.EqualTo(new[] { "2", "4", "6", "8" })); } + */ [Test] - public void ExecuteFluentGeneratorMethod_ReturnsErrorWhenAbstractionsReferenceIsMissing() + public void ExecuteFluentBodyGeneratorMethod_WithBodyReturningConstant_ExtractsReturnValue() + { + CSharpCompilation compilation = CreateCompilation(""" + using EasySourceGenerators.Abstractions; + + namespace TestNamespace; + + public static partial class Target + { + public static partial string GetValue(); + } + + public static class GenHost + { + public static IMethodBodyGenerator Generate() + { + return global::EasySourceGenerators.Abstractions.Generate + .MethodBody() + .ForMethod() + .WithReturnType() + .WithNoParameters() + .BodyReturningConstant(() => "hello fluent"); + } + } + """); + + IMethodSymbol generatorMethod = GetMethodSymbol(compilation, "TestNamespace.GenHost", "Generate"); + IMethodSymbol partialMethod = GetMethodSymbol(compilation, "TestNamespace.Target", "GetValue"); + + (FluentBodyResult? result, string? error) outcome = + GeneratesMethodExecutionRuntime.ExecuteFluentBodyGeneratorMethod(generatorMethod, partialMethod, compilation); + + Assert.That(outcome.error, Is.Null); + Assert.That(outcome.result, Is.Not.Null); + Assert.That(outcome.result!.ReturnValue, Is.EqualTo("hello fluent")); + Assert.That(outcome.result.IsVoid, Is.False); + } + + [Test] + public void ExecuteFluentBodyGeneratorMethod_ReturnsErrorWhenAbstractionsReferenceIsMissing() { CSharpCompilation originalCompilation = CreateCompilation(""" namespace TestNamespace; @@ -279,11 +322,11 @@ public static string Generate() IMethodSymbol generatorMethod = GetMethodSymbol(originalCompilation, "TestNamespace.GenHost", "Generate"); IMethodSymbol partialMethod = GetMethodSymbol(originalCompilation, "TestNamespace.Target", "GetValue"); - (SwitchBodyData? record, string? error) result = - GeneratesMethodExecutionRuntime.ExecuteFluentGeneratorMethod(generatorMethod, partialMethod, compilation); + (FluentBodyResult? result, string? error) outcome = + GeneratesMethodExecutionRuntime.ExecuteFluentBodyGeneratorMethod(generatorMethod, partialMethod, compilation); - Assert.That(result.record, Is.Null); - Assert.That(result.error, Does.StartWith("Compilation failed:")); + Assert.That(outcome.result, Is.Null); + Assert.That(outcome.error, Does.StartWith("Compilation failed:")); } private static CSharpCompilation CreateCompilation( diff --git a/EasySourceGenerators.GeneratorTests/GeneratorDiagnosticsTests.cs b/EasySourceGenerators.GeneratorTests/GeneratorDiagnosticsTests.cs index 7aef025..1548c3a 100644 --- a/EasySourceGenerators.GeneratorTests/GeneratorDiagnosticsTests.cs +++ b/EasySourceGenerators.GeneratorTests/GeneratorDiagnosticsTests.cs @@ -89,8 +89,10 @@ public partial class MyClass // ----------------------------------------------------------------------- // MSGH005 – Generator method has too many parameters (switch pattern) + // SwitchCase-based tests are commented out pending replacement with data-driven approach // ----------------------------------------------------------------------- + /* [Test] public void GeneratesMethod_SwitchCaseWithMoreThanOneParameter_EmitsMSGH005() { @@ -166,6 +168,7 @@ public static partial class MyClass Assert.That(diagnostics.Any(d => d.Id == "MSGH005"), Is.False, "Should not emit MSGH005 for a generator method with zero parameters"); } + */ // ----------------------------------------------------------------------- // MSGH004 – Generator method execution failed (unfinished fluent API) @@ -201,8 +204,10 @@ private static IMethodBodyGenerator GetValue_Generator() => // ----------------------------------------------------------------------- // Partial method called from fluent API / generator execution + // SwitchCase-based tests are commented out pending replacement // ----------------------------------------------------------------------- + /* [Test] public void GeneratesMethod_PartialMethodCalledInsideGenerator_EmitsMSGH004WithHelpfulMessage() { @@ -243,11 +248,14 @@ private static int GetValue_Generator(int key) .Or.Contain("partial method"), "Error message should hint that a partial method was called during generation"); } + */ // ----------------------------------------------------------------------- // MSGH006 – SwitchCase argument type mismatch + // SwitchCase-based tests are commented out pending replacement // ----------------------------------------------------------------------- + /* [Test] public void GeneratesMethod_SwitchCaseWithWrongArgumentType_EmitsMSGH006() { @@ -299,6 +307,7 @@ public static partial class MyClass Assert.That(diagnostics.Any(d => d.Id == "MSGH006"), Is.False, "Should not emit MSGH006 when SwitchCase argument type matches the parameter type"); } + */ // ----------------------------------------------------------------------- // Type mismatch between generator return type and partial method return type @@ -358,6 +367,8 @@ public partial class MyClass "Valid generator configuration should produce no error diagnostics"); } + // SwitchCase-based tests are commented out pending replacement with data-driven approach + /* [Test] public void GeneratesMethod_SwitchCasePattern_ValidConfiguration_ProducesNoDiagnosticErrors() { @@ -382,6 +393,7 @@ public static partial class MyClass Assert.That(diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error), Is.Empty, "Valid switch-case generator configuration should produce no error diagnostics"); } + */ // ----------------------------------------------------------------------- // CompilationReference scenario – simulates Rider's code inspector @@ -452,8 +464,10 @@ public partial class MyClass // ----------------------------------------------------------------------- // Bool switch parameter – valid configuration + // SwitchCase-based tests are commented out pending replacement // ----------------------------------------------------------------------- + /* [Test] public void GeneratesMethod_SwitchCaseWithBoolParameter_ValidConfiguration_ProducesNoDiagnosticErrors() { @@ -483,4 +497,5 @@ public static partial class MyClass Assert.That(diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error), Is.Empty, "Valid bool switch case generator should produce no error diagnostics"); } + */ } diff --git a/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs b/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs index 311c7d9..a1ad3ed 100644 --- a/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs +++ b/EasySourceGenerators.GeneratorTests/MethodBodyBuilderTests.cs @@ -1,77 +1,192 @@ using EasySourceGenerators.Abstractions; -using EasySourceGenerators.Generators; +using EasySourceGenerators.Generators.DataBuilding; namespace EasySourceGenerators.GeneratorTests; [TestFixture] -public class MethodBodyBodyBuilderTests +public class DataMethodBodyBuilderTests { [Test] - public void WithParameter_ReturnsGenericMethodBuilder() + public void ForMethod_ReturnsStage2() { - TrackingGeneratorsFactory factory = new TrackingGeneratorsFactory(); - MethodBodyBuilder bodyBuilder = new MethodBodyBuilder(factory); + DataMethodBodyBuilderStage1 stage1 = new DataMethodBodyBuilderStage1(new BodyGenerationData()); - IMethodBodyBuilder result = bodyBuilder.WithParameter(); - IMethodBodyGenerator implementation = result.WithReturnType(); + IMethodBodyBuilderStage2 result = stage1.ForMethod(); - Assert.That(result, Is.TypeOf>()); - Assert.That(implementation, Is.TypeOf>()); - Assert.That(factory.ArgCreateImplementationCalls, Is.EqualTo(1)); + Assert.That(result, Is.TypeOf()); } [Test] - public void WithReturnType_OnNonGenericBuilder_UsesFactoryCreateImplementation() + public void WithReturnType_ReturnsStage3() { - TrackingGeneratorsFactory factory = new TrackingGeneratorsFactory(); - MethodBodyBuilder bodyBuilder = new MethodBodyBuilder(factory); + DataMethodBodyBuilderStage2 stage2 = new DataMethodBodyBuilderStage2(new BodyGenerationData()); - IMethodBodyGenerator result = bodyBuilder.WithReturnType(); + IMethodBodyBuilderStage3 result = stage2.WithReturnType(); - Assert.That(result, Is.TypeOf>()); - Assert.That(factory.TypedCreateImplementationCalls, Is.EqualTo(1)); + Assert.That(result, Is.TypeOf>()); + Assert.That(((DataMethodBodyBuilderStage3)result).Data.ReturnType, Is.EqualTo(typeof(string))); } [Test] - public void WithReturnType_OnGenericBuilder_UsesFactoryCreateImplementationWithArg() + public void WithVoidReturnType_ReturnsStage3ReturnVoid() { - TrackingGeneratorsFactory factory = new TrackingGeneratorsFactory(); - MethodBodyBodyBuilder bodyBuilder = new MethodBodyBodyBuilder(factory); + DataMethodBodyBuilderStage2 stage2 = new DataMethodBodyBuilderStage2(new BodyGenerationData()); - IMethodBodyGenerator result = bodyBuilder.WithReturnType(); + IMethodBodyBuilderStage3ReturnVoid result = stage2.WithVoidReturnType(); - Assert.That(result, Is.TypeOf>()); - Assert.That(factory.ArgCreateImplementationCalls, Is.EqualTo(1)); + Assert.That(result, Is.TypeOf()); + Assert.That(((DataMethodBodyBuilderStage3ReturnVoid)result).Data.ReturnType, Is.EqualTo(typeof(void))); } - private sealed class TrackingGeneratorsFactory : IMethodBodyGeneratorStage0 + [Test] + public void WithNoParameters_ReturnsStage4NoArg() + { + DataMethodBodyBuilderStage3 stage3 = new DataMethodBodyBuilderStage3(new BodyGenerationData(ReturnType: typeof(int))); + + IMethodBodyBuilderStage4NoArg result = stage3.WithNoParameters(); + + Assert.That(result, Is.TypeOf>()); + DataMethodBodyBuilderStage4NoArg stage4 = (DataMethodBodyBuilderStage4NoArg)result; + Assert.That(stage4.Data.ParametersTypes, Is.Empty); + Assert.That(stage4.Data.ReturnType, Is.EqualTo(typeof(int))); + } + + [Test] + public void WithParameter_ReturnsStage4() + { + DataMethodBodyBuilderStage3 stage3 = new DataMethodBodyBuilderStage3(new BodyGenerationData(ReturnType: typeof(int))); + + IMethodBodyBuilderStage4 result = stage3.WithParameter(); + + Assert.That(result, Is.TypeOf>()); + DataMethodBodyBuilderStage4 stage4 = (DataMethodBodyBuilderStage4)result; + Assert.That(stage4.Data.ParametersTypes, Is.EqualTo(new[] { typeof(string) })); + } + + [Test] + public void BodyReturningConstant_SetsReturnConstantValueFactory() + { + DataMethodBodyBuilderStage4NoArg stage4 = new DataMethodBodyBuilderStage4NoArg( + new BodyGenerationData(ReturnType: typeof(int), ParametersTypes: [])); + + IMethodBodyGenerator result = stage4.BodyReturningConstant(() => 42); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnConstantValueFactory, Is.Not.Null); + Assert.That(generator.Data.RuntimeDelegateBody, Is.Null); + } + + [Test] + public void UseProvidedBody_NoArg_SetsRuntimeDelegateBody() { - public int TypedCreateImplementationCalls { get; private set; } - public int ArgCreateImplementationCalls { get; private set; } - - public IMethodBodyBuilder ForMethod() => new MethodBodyBuilder(this); - - public IMethodBodyGenerator CreateImplementation() - { - TypedCreateImplementationCalls++; - return new TrackingTypedImplementationGenerator(); - } - - public IMethodBodyGenerator CreateImplementation() - { - ArgCreateImplementationCalls++; - return new TrackingArgImplementationGenerator(); - } + DataMethodBodyBuilderStage4NoArg stage4 = new DataMethodBodyBuilderStage4NoArg( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [])); + + IMethodBodyGenerator result = stage4.UseProvidedBody(() => "hello"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + Assert.That(generator.Data.ReturnConstantValueFactory, Is.Null); } - private sealed class TrackingTypedImplementationGenerator : IMethodBodyGenerator + [Test] + public void UseProvidedBody_WithArg_SetsRuntimeDelegateBody() { - public IMethodBodyGeneratorWithNoParameter BodyReturningConstantValue(Func body) => this; + DataMethodBodyBuilderStage4 stage4 = new DataMethodBodyBuilderStage4( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [typeof(int)])); + + IMethodBodyGenerator result = stage4.UseProvidedBody(x => x.ToString()); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); } - private sealed class TrackingArgImplementationGenerator : IMethodBodyGenerator + [Test] + public void BodyReturningConstant_WithArg_SetsReturnConstantValueFactory() + { + DataMethodBodyBuilderStage4 stage4 = new DataMethodBodyBuilderStage4( + new BodyGenerationData(ReturnType: typeof(string), ParametersTypes: [typeof(int)])); + + IMethodBodyGenerator result = stage4.BodyReturningConstant(() => "constant"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnConstantValueFactory, Is.Not.Null); + Assert.That(generator.Data.RuntimeDelegateBody, Is.Null); + } + + [Test] + public void VoidReturnType_WithNoParameters_UseProvidedBody_SetsCorrectData() + { + DataMethodBodyBuilderStage3ReturnVoid stage3 = new DataMethodBodyBuilderStage3ReturnVoid( + new BodyGenerationData(ReturnType: typeof(void))); + + IMethodBodyBuilderStage4ReturnVoidNoArg stage4 = stage3.WithNoParameters(); + IMethodBodyGenerator result = stage4.UseProvidedBody(() => { }); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnType, Is.EqualTo(typeof(void))); + Assert.That(generator.Data.ParametersTypes, Is.Empty); + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + } + + [Test] + public void VoidReturnType_WithParameter_UseProvidedBody_SetsCorrectData() + { + DataMethodBodyBuilderStage3ReturnVoid stage3 = new DataMethodBodyBuilderStage3ReturnVoid( + new BodyGenerationData(ReturnType: typeof(void))); + + IMethodBodyBuilderStage4ReturnVoid stage4 = stage3.WithParameter(); + IMethodBodyGenerator result = stage4.UseProvidedBody(_ => { }); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnType, Is.EqualTo(typeof(void))); + Assert.That(generator.Data.ParametersTypes, Is.EqualTo(new[] { typeof(int) })); + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + } + + [Test] + public void FullFluentChain_BodyReturningConstant_ProducesCorrectData() + { + DataGeneratorsFactory factory = new DataGeneratorsFactory(); + + IMethodBodyGenerator result = factory.StartFluentApiBuilderForBody() + .ForMethod() + .WithReturnType() + .WithNoParameters() + .BodyReturningConstant(() => "hello world"); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnType, Is.EqualTo(typeof(string))); + Assert.That(generator.Data.ParametersTypes, Is.Empty); + Assert.That(generator.Data.ReturnConstantValueFactory, Is.Not.Null); + object? constantValue = generator.Data.ReturnConstantValueFactory!.DynamicInvoke(); + Assert.That(constantValue, Is.EqualTo("hello world")); + } + + [Test] + public void FullFluentChain_UseProvidedBody_ProducesCorrectData() { - public IMethodBodyGeneratorSwitchBody GenerateSwitchBody() => - new MockMethodImplementationGeneratorSwitchBody(); + DataGeneratorsFactory factory = new DataGeneratorsFactory(); + + IMethodBodyGenerator result = factory.StartFluentApiBuilderForBody() + .ForMethod() + .WithReturnType() + .WithNoParameters() + .UseProvidedBody(() => 42); + + Assert.That(result, Is.TypeOf()); + DataMethodBodyGenerator generator = (DataMethodBodyGenerator)result; + Assert.That(generator.Data.ReturnType, Is.EqualTo(typeof(int))); + Assert.That(generator.Data.ParametersTypes, Is.Empty); + Assert.That(generator.Data.RuntimeDelegateBody, Is.Not.Null); + object? bodyValue = generator.Data.RuntimeDelegateBody!.DynamicInvoke(); + Assert.That(bodyValue, Is.EqualTo(42)); } } diff --git a/EasySourceGenerators.GeneratorTests/MocksTests.cs b/EasySourceGenerators.GeneratorTests/MocksTests.cs index 4e78fef..4b06771 100644 --- a/EasySourceGenerators.GeneratorTests/MocksTests.cs +++ b/EasySourceGenerators.GeneratorTests/MocksTests.cs @@ -1,3 +1,8 @@ +// MocksTests have been commented out because the mock types (MockGeneratorsFactory, MockMethodBodyBuilder, etc.) +// were part of the old fluent API and have been replaced by the data abstraction layer +// (DataGeneratorsFactory, DataMethodBodyBuilders). + +/* using EasySourceGenerators.Abstractions; namespace EasySourceGenerators.GeneratorTests; @@ -155,3 +160,4 @@ public void MockMethodBuilderGeneric_WithReturnType_ReturnsArgImplementationGene Assert.That(result, Is.TypeOf>()); } } +*/ diff --git a/EasySourceGenerators.GeneratorTests/RecordingGeneratorsFactoryTests.cs b/EasySourceGenerators.GeneratorTests/RecordingGeneratorsFactoryTests.cs index 035c3f4..1e4f270 100644 --- a/EasySourceGenerators.GeneratorTests/RecordingGeneratorsFactoryTests.cs +++ b/EasySourceGenerators.GeneratorTests/RecordingGeneratorsFactoryTests.cs @@ -1,162 +1,54 @@ using EasySourceGenerators.Abstractions; -using EasySourceGenerators.Generators; +using EasySourceGenerators.Generators.DataBuilding; namespace EasySourceGenerators.GeneratorTests; [TestFixture] -public class RecordingGeneratorsFactoryTests +public class DataGeneratorsFactoryTests { [Test] - public void ForMethod_ReturnsMethodBuilder() + public void StartFluentApiBuilderForBody_ReturnsDataMethodBodyBuilderStage1() { - RecordingGeneratorsFactory factory = new RecordingGeneratorsFactory(); + DataGeneratorsFactory factory = new DataGeneratorsFactory(); - IMethodBodyBuilder result = factory.ForMethod(); + IMethodBodyBuilderStage1 result = factory.StartFluentApiBuilderForBody(); - Assert.That(result, Is.TypeOf()); + Assert.That(result, Is.TypeOf()); } [Test] - public void CreateImplementationTyped_SetsLastRecordAndReturnsTypedGenerator() + public void StartFluentApiBuilderForBody_InitializesEmptyBodyGenerationData() { - RecordingGeneratorsFactory factory = new RecordingGeneratorsFactory(); + DataGeneratorsFactory factory = new DataGeneratorsFactory(); - IMethodBodyGenerator result = factory.CreateImplementation(); + IMethodBodyBuilderStage1 result = factory.StartFluentApiBuilderForBody(); - Assert.That(factory.LastRecord, Is.Not.Null); - Assert.That(result, Is.TypeOf>()); - } - - [Test] - public void CreateImplementationNonGeneric_SetsLastRecordAndReturnsGenerator() - { - RecordingGeneratorsFactory factory = new RecordingGeneratorsFactory(); - - IMethodBodyGeneratorWithNoParameter result = factory.CreateImplementation(); - - Assert.That(factory.LastRecord, Is.Not.Null); - Assert.That(result, Is.TypeOf()); - } - - [Test] - public void CreateImplementationWithArg_SetsLastRecordAndReturnsArgGenerator() - { - RecordingGeneratorsFactory factory = new RecordingGeneratorsFactory(); - - IMethodBodyGenerator result = factory.CreateImplementation(); - - Assert.That(factory.LastRecord, Is.Not.Null); - Assert.That(result, Is.TypeOf>()); - } - - [Test] - public void TypedUseBody_ReturnsSameInstance() - { - RecordingMethodImplementationGeneratorTyped generator = new RecordingMethodImplementationGeneratorTyped(); - - IMethodBodyGeneratorWithNoParameter result = generator.BodyReturningConstantValue(() => "a"); - - Assert.That(result, Is.SameAs(generator)); - } - - [Test] - public void WithSwitchBody_ReturnsSwitchBodyGenerator() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGenerator generator = new RecordingMethodImplementationGenerator(record); - - IMethodBodyGeneratorSwitchBody result = generator.GenerateSwitchBody(); - - Assert.That(result, Is.TypeOf>()); - } - - [Test] - public void ForCases_FlattensAndConvertsCases() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGeneratorSwitchBody switchBody = new RecordingMethodImplementationGeneratorSwitchBody(record); - - switchBody.ForCases(1, new[] { 2, 3 }, "4").ReturnConstantValue(value => $"v{value}"); - - Assert.That(record.CaseKeys, Is.EqualTo(new object[] { 1, 2, 3, 4 })); - Assert.That(record.CaseValues, Is.EqualTo(new object?[] { "v1", "v2", "v3", "v4" })); - } - - [Test] - public void ReturnConstantValue_RecordsCaseValuesAndReturnsNewSwitchBody() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGeneratorSwitchBody switchBody = new RecordingMethodImplementationGeneratorSwitchBody(record); - IMethodBodyGeneratorSwitchBodyCase caseBuilder = switchBody.ForCases(10, 20); - - IMethodBodyGeneratorSwitchBody result = caseBuilder.ReturnConstantValue(value => value + 1); - - Assert.That(result, Is.TypeOf>()); - Assert.That(record.CaseKeys, Is.EqualTo(new object[] { 10, 20 })); - Assert.That(record.CaseValues, Is.EqualTo(new object?[] { 11, 21 })); - } - - [Test] - public void UseBody_RecordsCaseKeysWithNullValues() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGeneratorSwitchBody switchBody = new RecordingMethodImplementationGeneratorSwitchBody(record); - IMethodBodyGeneratorSwitchBodyCase caseBuilder = switchBody.ForCases(5, 6); - - IMethodBodyGeneratorSwitchBody result = caseBuilder.UseProvidedBody(_ => _ => { }); - - Assert.That(result, Is.TypeOf>()); - Assert.That(record.CaseKeys, Is.EqualTo(new object[] { 5, 6 })); - Assert.That(record.CaseValues, Is.EqualTo(new object?[] { null, null })); + DataMethodBodyBuilderStage1 stage1 = (DataMethodBodyBuilderStage1)result; + Assert.That(stage1.Data.ReturnType, Is.Null); + Assert.That(stage1.Data.ParametersTypes, Is.Null); + Assert.That(stage1.Data.RuntimeDelegateBody, Is.Null); + Assert.That(stage1.Data.ReturnConstantValueFactory, Is.Null); } [Test] - public void ReturnConstantValue_WithNullCase_ThrowsInvalidOperationException() + public void StartFluentApiBuilderForMethod_ThrowsNotImplementedException() { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGeneratorSwitchBody switchBody = new RecordingMethodImplementationGeneratorSwitchBody(record); - IMethodBodyGeneratorSwitchBodyCase caseBuilder = switchBody.ForCases((object?)null!); - - TestDelegate action = () => caseBuilder.ReturnConstantValue(_ => 1); - - Assert.That(action, Throws.TypeOf()); - } - - [Test] - public void UseBody_WithNullCase_ThrowsInvalidOperationException() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGeneratorSwitchBody switchBody = new RecordingMethodImplementationGeneratorSwitchBody(record); - IMethodBodyGeneratorSwitchBodyCase caseBuilder = switchBody.ForCases((object?)null!); - - TestDelegate action = () => caseBuilder.UseProvidedBody(_ => _ => { }); - - Assert.That(action, Throws.TypeOf()); - } - - [Test] - public void ForDefaultCase_ReturnConstantValue_SetsDefaultFlag() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGeneratorSwitchBody switchBody = new RecordingMethodImplementationGeneratorSwitchBody(record); - IMethodBodyGeneratorSwitchBodyDefaultCase defaultCase = switchBody.ForDefaultCase(); - - IMethodBodyGenerator result = defaultCase.ReturnConstantValue(value => value + 1); + DataGeneratorsFactory factory = new DataGeneratorsFactory(); - Assert.That(record.HasDefaultCase, Is.True); - Assert.That(result, Is.TypeOf>()); + Assert.Throws(() => factory.StartFluentApiBuilderForMethod(() => "test")); } [Test] - public void ForDefaultCase_UseBody_SetsDefaultFlag() + public void FullChain_ReturnsDataMethodBodyGenerator() { - SwitchBodyRecord record = new SwitchBodyRecord(); - RecordingMethodImplementationGeneratorSwitchBody switchBody = new RecordingMethodImplementationGeneratorSwitchBody(record); - IMethodBodyGeneratorSwitchBodyDefaultCase defaultCase = switchBody.ForDefaultCase(); + DataGeneratorsFactory factory = new DataGeneratorsFactory(); - IMethodBodyGenerator result = defaultCase.UseProvidedBody(_ => () => 1); + IMethodBodyGenerator result = factory.StartFluentApiBuilderForBody() + .ForMethod() + .WithReturnType() + .WithNoParameters() + .BodyReturningConstant(() => 42); - Assert.That(record.HasDefaultCase, Is.True); - Assert.That(result, Is.TypeOf>()); + Assert.That(result, Is.TypeOf()); } } diff --git a/EasySourceGenerators.Generators/Consts.cs b/EasySourceGenerators.Generators/Consts.cs index 0b8ebfc..4619f55 100644 --- a/EasySourceGenerators.Generators/Consts.cs +++ b/EasySourceGenerators.Generators/Consts.cs @@ -1,4 +1,5 @@ using EasySourceGenerators.Abstractions; +using EasySourceGenerators.Generators.DataBuilding; // ReSharper disable InconsistentNaming - names like IFoo are neede here @@ -16,7 +17,8 @@ public static class Consts public const string GeneratesMethodAttributeFullName = $"{AbstractionsNamespace}.{nameof(MethodBodyGenerator)}"; public const string IMethodImplementationGeneratorFullName = $"{AbstractionsNamespace}.{nameof(IMethodBodyGenerator)}"; public const string GenerateTypeFullName = $"{AbstractionsNamespace}.{nameof(Generate)}"; - public const string RecordingGeneratorsFactoryTypeFullName = $"{GeneratorsNamespace}.{nameof(RecordingGeneratorsFactory)}"; + public const string DataGeneratorsFactoryTypeFullName = $"{GeneratorsNamespace}.DataBuilding.{nameof(DataGeneratorsFactory)}"; + public const string DataMethodBodyGeneratorTypeName = nameof(DataMethodBodyGenerator); + public const string BodyGenerationDataPropertyName = "Data"; public const string CurrentGeneratorPropertyName = nameof(Generate.CurrentGenerator); - public const string LastRecordPropertyName = nameof(RecordingGeneratorsFactory.LastRecord); } diff --git a/EasySourceGenerators.Generators/DataBuilding/DataGeneratorsFactory.cs b/EasySourceGenerators.Generators/DataBuilding/DataGeneratorsFactory.cs index 79b79ac..83ba641 100644 --- a/EasySourceGenerators.Generators/DataBuilding/DataGeneratorsFactory.cs +++ b/EasySourceGenerators.Generators/DataBuilding/DataGeneratorsFactory.cs @@ -1,4 +1,5 @@ -using EasySourceGenerators.Abstractions; +using System; +using EasySourceGenerators.Abstractions; using EasySourceGenerators.Abstractions.Method; namespace EasySourceGenerators.Generators.DataBuilding; diff --git a/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs b/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs index 650a5eb..1f7c915 100644 --- a/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs +++ b/EasySourceGenerators.Generators/DataBuilding/DataMethodBodyBuilders.cs @@ -1,4 +1,5 @@ -using EasySourceGenerators.Abstractions; +using System; +using EasySourceGenerators.Abstractions; namespace EasySourceGenerators.Generators.DataBuilding; diff --git a/EasySourceGenerators.Generators/DataBuilding/DataRecords.cs b/EasySourceGenerators.Generators/DataBuilding/DataRecords.cs index 55e0c05..76f2fea 100644 --- a/EasySourceGenerators.Generators/DataBuilding/DataRecords.cs +++ b/EasySourceGenerators.Generators/DataBuilding/DataRecords.cs @@ -1,4 +1,6 @@ -namespace EasySourceGenerators.Generators.DataBuilding; +using System; + +namespace EasySourceGenerators.Generators.DataBuilding; public record BodyGenerationData( Type? ReturnType = null, diff --git a/EasySourceGenerators.Generators/EasySourceGenerators.Generators.csproj b/EasySourceGenerators.Generators/EasySourceGenerators.Generators.csproj index 3560dc3..4b89623 100644 --- a/EasySourceGenerators.Generators/EasySourceGenerators.Generators.csproj +++ b/EasySourceGenerators.Generators/EasySourceGenerators.Generators.csproj @@ -60,8 +60,9 @@ - - + + + diff --git a/EasySourceGenerators.Generators/GeneratesMethodExecutionRuntime.cs b/EasySourceGenerators.Generators/GeneratesMethodExecutionRuntime.cs index 6c047c3..6d429a8 100644 --- a/EasySourceGenerators.Generators/GeneratesMethodExecutionRuntime.cs +++ b/EasySourceGenerators.Generators/GeneratesMethodExecutionRuntime.cs @@ -2,7 +2,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Emit; -using System.Collections; using System.Reflection; using System.Runtime.Loader; using System.Text; @@ -14,6 +13,13 @@ internal sealed record SwitchBodyData( IReadOnlyList<(object key, string value)> CasePairs, bool HasDefaultCase); +/// +/// Result extracted from after executing a fluent body generator method. +/// +internal sealed record FluentBodyResult( + string? ReturnValue, + bool IsVoid); + internal static class GeneratesMethodExecutionRuntime { internal static (string? value, string? error) ExecuteSimpleGeneratorMethod( @@ -25,7 +31,10 @@ internal static (string? value, string? error) ExecuteSimpleGeneratorMethod( return ExecuteGeneratorMethodWithArgs(generatorMethod, allPartials, compilation, null); } - internal static (SwitchBodyData? record, string? error) ExecuteFluentGeneratorMethod( + // SwitchBodyData-based fluent execution has been replaced by the data abstraction layer. + // Use ExecuteFluentBodyGeneratorMethod instead. + + internal static (FluentBodyResult? result, string? error) ExecuteFluentBodyGeneratorMethod( IMethodSymbol generatorMethod, IMethodSymbol partialMethod, Compilation compilation) @@ -113,8 +122,6 @@ reference.Display is not null } else if (csharpCompilationReference.Length > 0) { - // Handle CompilationReference case (e.g., Rider's code inspector provides in-memory compilations). - // The Resolving handler above should have already loaded the abstractions from the pre-emitted bytes. if (capturedAbstractionsAssembly != null) { abstractionsAssembly = capturedAbstractionsAssembly; @@ -135,15 +142,15 @@ reference.Display is not null } Type? generatorStaticType = abstractionsAssembly.GetType(Consts.GenerateTypeFullName); - Type? recordingFactoryType = assembly.GetType(Consts.RecordingGeneratorsFactoryTypeFullName); - if (generatorStaticType == null || recordingFactoryType == null) + Type? dataGeneratorsFactoryType = assembly.GetType(Consts.DataGeneratorsFactoryTypeFullName); + if (generatorStaticType == null || dataGeneratorsFactoryType == null) { - return (null, $"Could not find {Consts.GenerateTypeFullName} or {Consts.RecordingGeneratorsFactoryTypeFullName} types in compiled assembly"); + return (null, $"Could not find {Consts.GenerateTypeFullName} or {Consts.DataGeneratorsFactoryTypeFullName} types in compiled assembly"); } - object? recordingFactory = Activator.CreateInstance(recordingFactoryType); + object? dataGeneratorsFactory = Activator.CreateInstance(dataGeneratorsFactoryType); PropertyInfo? currentGeneratorProperty = generatorStaticType.GetProperty(Consts.CurrentGeneratorPropertyName, BindingFlags.NonPublic | BindingFlags.Static); - currentGeneratorProperty?.SetValue(null, recordingFactory); + currentGeneratorProperty?.SetValue(null, dataGeneratorsFactory); string typeName = generatorMethod.ContainingType.ToDisplayString(); Type? loadedType = assembly.GetType(typeName); @@ -158,16 +165,13 @@ reference.Display is not null return (null, $"Could not find method '{generatorMethod.Name}' in type '{typeName}'"); } - generatorMethodInfo.Invoke(null, null); - - PropertyInfo? lastRecordProperty = recordingFactoryType.GetProperty(Consts.LastRecordPropertyName); - object? lastRecord = lastRecordProperty?.GetValue(recordingFactory); - if (lastRecord == null) + object? methodResult = generatorMethodInfo.Invoke(null, null); + if (methodResult == null) { - return (null, "RecordingGeneratorsFactory did not produce a record"); + return (null, "Fluent body generator method returned null"); } - return (ExtractSwitchBodyData(lastRecord, partialMethod.ReturnType), null); + return (ExtractBodyGenerationData(methodResult, partialMethod.ReturnType), null); } catch (Exception ex) { @@ -282,26 +286,56 @@ internal static IReadOnlyList GetAllUnimplementedPartialMethods(C return new[] { Convert.ChangeType(args[0], parameterType) }; } - private static SwitchBodyData ExtractSwitchBodyData(object lastRecord, ITypeSymbol returnType) + private static FluentBodyResult ExtractBodyGenerationData(object methodResult, ITypeSymbol returnType) { - Type recordType = lastRecord.GetType(); - PropertyInfo? caseKeysProperty = recordType.GetProperty(nameof(SwitchBodyRecord.CaseKeys)); - PropertyInfo? caseValuesProperty = recordType.GetProperty(nameof(SwitchBodyRecord.CaseValues)); - PropertyInfo? hasDefaultProperty = recordType.GetProperty(nameof(SwitchBodyRecord.HasDefaultCase)); + Type resultType = methodResult.GetType(); - IList caseKeys = (caseKeysProperty?.GetValue(lastRecord) as IList) ?? new List(); - IList caseValues = (caseValuesProperty?.GetValue(lastRecord) as IList) ?? new List(); - bool hasDefaultCase = (bool)(hasDefaultProperty?.GetValue(lastRecord) ?? false); + // The result should be a DataMethodBodyGenerator containing a BodyGenerationData Data property + PropertyInfo? dataProperty = resultType.GetProperty(Consts.BodyGenerationDataPropertyName); + if (dataProperty == null) + { + // The method returned something that isn't a DataMethodBodyGenerator. + // This may happen when the fluent chain is incomplete (e.g., user returned an intermediate builder). + return new FluentBodyResult(null, returnType.SpecialType == SpecialType.System_Void); + } - List<(object key, string value)> pairs = new(); - for (int index = 0; index < caseKeys.Count; index++) + object? bodyGenerationData = dataProperty.GetValue(methodResult); + if (bodyGenerationData == null) { - object key = caseKeys[index]!; - string? value = index < caseValues.Count ? caseValues[index]?.ToString() : null; - pairs.Add((key, GeneratesMethodPatternSourceBuilder.FormatValueAsCSharpLiteral(value, returnType))); + return new FluentBodyResult(null, returnType.SpecialType == SpecialType.System_Void); + } + + Type dataType = bodyGenerationData.GetType(); + PropertyInfo? returnTypeProperty = dataType.GetProperty("ReturnType"); + Type? dataReturnType = returnTypeProperty?.GetValue(bodyGenerationData) as Type; + bool isVoid = dataReturnType == typeof(void); + + // Try ReturnConstantValueFactory first + PropertyInfo? constantFactoryProperty = dataType.GetProperty("ReturnConstantValueFactory"); + Delegate? constantFactory = constantFactoryProperty?.GetValue(bodyGenerationData) as Delegate; + if (constantFactory != null) + { + object? constantValue = constantFactory.DynamicInvoke(); + return new FluentBodyResult(constantValue?.ToString(), isVoid); + } + + // Try RuntimeDelegateBody + PropertyInfo? runtimeBodyProperty = dataType.GetProperty("RuntimeDelegateBody"); + Delegate? runtimeBody = runtimeBodyProperty?.GetValue(bodyGenerationData) as Delegate; + if (runtimeBody != null) + { + ParameterInfo[] bodyParams = runtimeBody.Method.GetParameters(); + if (bodyParams.Length == 0) + { + object? bodyResult = runtimeBody.DynamicInvoke(); + return new FluentBodyResult(bodyResult?.ToString(), isVoid); + } + + // For delegates with parameters, we can't invoke at compile time without values + return new FluentBodyResult(null, isVoid); } - return new SwitchBodyData(pairs, hasDefaultCase); + return new FluentBodyResult(null, isVoid); } private static Dictionary EmitCompilationReferences(Compilation compilation) @@ -339,16 +373,18 @@ private static CSharpCompilation BuildExecutionCompilation( Compilation compilation) { string dummySource = BuildDummyImplementation(allPartialMethods); - string methodBuilderSource = ReadEmbeddedResource($"{Consts.GeneratorsAssemblyName}.MethodBodyBuilder.cs"); - string recordingFactorySource = ReadEmbeddedResource($"{Consts.GeneratorsAssemblyName}.RecordingGeneratorsFactory.cs"); + string dataGeneratorsFactorySource = ReadEmbeddedResource($"{Consts.GeneratorsAssemblyName}.DataGeneratorsFactory.cs"); + string dataMethodBodyBuildersSource = ReadEmbeddedResource($"{Consts.GeneratorsAssemblyName}.DataMethodBodyBuilders.cs"); + string dataRecordsSource = ReadEmbeddedResource($"{Consts.GeneratorsAssemblyName}.DataRecords.cs"); CSharpParseOptions parseOptions = compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions ?? CSharpParseOptions.Default; return (CSharpCompilation)compilation .AddSyntaxTrees( CSharpSyntaxTree.ParseText(dummySource, parseOptions), - CSharpSyntaxTree.ParseText(methodBuilderSource, parseOptions), - CSharpSyntaxTree.ParseText(recordingFactorySource, parseOptions)); + CSharpSyntaxTree.ParseText(dataGeneratorsFactorySource, parseOptions), + CSharpSyntaxTree.ParseText(dataMethodBodyBuildersSource, parseOptions), + CSharpSyntaxTree.ParseText(dataRecordsSource, parseOptions)); } private static string ReadEmbeddedResource(string resourceName) diff --git a/EasySourceGenerators.Generators/GeneratesMethodGenerationPipeline.cs b/EasySourceGenerators.Generators/GeneratesMethodGenerationPipeline.cs index 22c8858..493c644 100644 --- a/EasySourceGenerators.Generators/GeneratesMethodGenerationPipeline.cs +++ b/EasySourceGenerators.Generators/GeneratesMethodGenerationPipeline.cs @@ -47,24 +47,15 @@ private static string GenerateSourceForGroup( IReadOnlyList allPartials, Compilation compilation) { - bool hasSwitchCase = methods.Any(method => HasAttribute(method.Symbol, SwitchCaseAttributeFullName)); - bool hasSwitchDefault = methods.Any(method => HasAttribute(method.Symbol, SwitchDefaultAttributeFullName)); + // SwitchCase attribute-based generation is commented out pending replacement with a data-driven approach. + // See DataMethodBodyBuilders.cs for details on the planned replacement. + // bool hasSwitchCase = methods.Any(method => HasAttribute(method.Symbol, SwitchCaseAttributeFullName)); + // bool hasSwitchDefault = methods.Any(method => HasAttribute(method.Symbol, SwitchDefaultAttributeFullName)); bool isFluentPattern = methods.Count == 1 && methods[0].Symbol.ReturnType.ToDisplayString() == IMethodImplementationGeneratorFullName; - if (hasSwitchCase || hasSwitchDefault) - { - return GeneratesMethodPatternSourceBuilder.GenerateFromSwitchAttributes( - context, - methods, - firstMethod.PartialMethod, - firstMethod.ContainingType, - allPartials, - compilation); - } - if (isFluentPattern) { - return GeneratesMethodPatternSourceBuilder.GenerateFromFluent( + return GenerateFromFluentBodyPattern( context, methods[0], firstMethod.PartialMethod, @@ -90,6 +81,34 @@ private static string GenerateSourceForGroup( return GenerateFromSimplePattern(context, firstMethod, compilation); } + private static string GenerateFromFluentBodyPattern( + SourceProductionContext context, + GeneratesMethodGenerationTarget methodInfo, + IMethodSymbol partialMethod, + INamedTypeSymbol containingType, + Compilation compilation) + { + (FluentBodyResult? result, string? error) = GeneratesMethodExecutionRuntime.ExecuteFluentBodyGeneratorMethod( + methodInfo.Symbol, + partialMethod, + compilation); + + if (error != null) + { + context.ReportDiagnostic(Diagnostic.Create( + GeneratesMethodGeneratorDiagnostics.GeneratorMethodExecutionError, + methodInfo.Syntax.GetLocation(), + methodInfo.Symbol.Name, + error)); + return string.Empty; + } + + return GeneratesMethodPatternSourceBuilder.GenerateSimplePartialMethod( + containingType, + partialMethod, + result!.ReturnValue); + } + private static string GenerateFromSimplePattern( SourceProductionContext context, GeneratesMethodGenerationTarget firstMethod, diff --git a/EasySourceGenerators.Generators/GeneratesMethodPatternSourceBuilder.cs b/EasySourceGenerators.Generators/GeneratesMethodPatternSourceBuilder.cs index ab044d9..6d544ad 100644 --- a/EasySourceGenerators.Generators/GeneratesMethodPatternSourceBuilder.cs +++ b/EasySourceGenerators.Generators/GeneratesMethodPatternSourceBuilder.cs @@ -9,6 +9,9 @@ namespace EasySourceGenerators.Generators; internal static class GeneratesMethodPatternSourceBuilder { + // SwitchCase attribute-based and fluent switch generation are commented out pending replacement + // with a data-driven approach. See DataMethodBodyBuilders.cs for details. + /* internal static string GenerateFromSwitchAttributes( SourceProductionContext context, List methods, @@ -143,6 +146,7 @@ internal static string GenerateFromFluent( return GenerateSwitchMethodSource(containingType, partialMethod, switchBodyData.CasePairs, defaultExpression); } + */ internal static string GenerateSimplePartialMethod( INamedTypeSymbol containingType, @@ -163,6 +167,8 @@ internal static string GenerateSimplePartialMethod( return builder.ToString(); } + // Switch-related helper methods are commented out pending replacement with data-driven approach + /* private static string? ExtractDefaultExpressionFromSwitchDefaultMethod(MethodDeclarationSyntax method) { ExpressionSyntax? bodyExpression = method.ExpressionBody?.Expression; @@ -259,6 +265,23 @@ private static string GenerateSwitchMethodSource( return builder.ToString(); } + private static string FormatKeyAsCSharpLiteral(object key, ITypeSymbol? parameterType) + { + if (parameterType?.TypeKind == TypeKind.Enum) + { + return $"{parameterType.ToDisplayString()}.{key}"; + } + + return key switch + { + bool b => b ? "true" : "false", + // SyntaxFactory.Literal handles escaping and quoting (e.g. "hello" → "\"hello\"") + string s => SyntaxFactory.Literal(s).Text, + _ => key.ToString()! + }; + } + */ + private static void AppendNamespaceAndTypeHeader(StringBuilder builder, INamedTypeSymbol containingType, IMethodSymbol partialMethod) { builder.AppendLine("// "); diff --git a/EasySourceGenerators.Generators/RecordingGeneratorsFactory.cs b/EasySourceGenerators.Generators/RecordingGeneratorsFactory.cs index 56f2592..fb9d6c6 100644 --- a/EasySourceGenerators.Generators/RecordingGeneratorsFactory.cs +++ b/EasySourceGenerators.Generators/RecordingGeneratorsFactory.cs @@ -1,134 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using EasySourceGenerators.Abstractions; - -namespace EasySourceGenerators.Generators; - -public class SwitchBodyRecord -{ - public List CaseKeys { get; } = new(); - public List CaseValues { get; } = new(); - public bool HasDefaultCase { get; set; } -} - -public class RecordingGeneratorsFactory : IMethodBodyGeneratorStage0 -{ - public SwitchBodyRecord? LastRecord { get; private set; } - - public IMethodBodyGeneratorWithNoParameter CreateImplementation() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - LastRecord = record; - return new RecordingMethodImplementationGenerator(); - } - - public IMethodBodyBuilder ForMethod() => new MethodBodyBuilder(this); - - public IMethodBodyGenerator CreateImplementation() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - LastRecord = record; - return new RecordingMethodImplementationGeneratorTyped(); - } - - public IMethodBodyGenerator CreateImplementation() - { - SwitchBodyRecord record = new SwitchBodyRecord(); - LastRecord = record; - return new RecordingMethodImplementationGenerator(record); - } -} - -public class RecordingMethodImplementationGenerator : IMethodBodyGeneratorWithNoParameter; - -public class RecordingMethodImplementationGeneratorTyped : IMethodBodyGenerator -{ - public IMethodBodyGeneratorWithNoParameter BodyReturningConstantValue(Func body) => this; -} - -public class RecordingMethodImplementationGenerator(SwitchBodyRecord record) : IMethodBodyGenerator -{ - public IMethodBodyGeneratorSwitchBody GenerateSwitchBody() - { - return new RecordingMethodImplementationGeneratorSwitchBody(record); - } -} - -public class RecordingMethodImplementationGeneratorSwitchBody(SwitchBodyRecord record) - : IMethodBodyGeneratorSwitchBody -{ - public IMethodBodyGeneratorSwitchBodyCase ForCases(params object[] cases) - { - List flatCases = FlattenCases(cases).ToList(); - return new RecordingMethodImplementationGeneratorSwitchBodyCase(record, flatCases); - } - - public IMethodBodyGeneratorSwitchBodyDefaultCase ForDefaultCase() - { - return new RecordingMethodImplementationGeneratorSwitchBodyDefaultCase(record); - } - - private static IEnumerable FlattenCases(object[] cases) - { - foreach (object oneCase in cases) - { - switch (oneCase) - { - case TArg1[] arr: - { - foreach (TArg1 item in arr) - yield return item; - break; - } - case TArg1 val: - yield return val; - break; - default: - yield return (TArg1)Convert.ChangeType(oneCase, typeof(TArg1)); - break; - } - } - } -} - -public class RecordingMethodImplementationGeneratorSwitchBodyCase(SwitchBodyRecord record, List cases) - : IMethodBodyGeneratorSwitchBodyCase -{ - public IMethodBodyGeneratorSwitchBody ReturnConstantValue(Func constantValueFactory) - { - foreach (TArg1? caseValue in cases) - { - TReturnType result = constantValueFactory(caseValue); - record.CaseKeys.Add((object?)caseValue ?? throw new InvalidOperationException("Switch case value cannot be null")); - record.CaseValues.Add(result); - } - return new RecordingMethodImplementationGeneratorSwitchBody(record); - } - - public IMethodBodyGeneratorSwitchBody UseBody(Func> body) - { - foreach (TArg1? caseValue in cases) - { - record.CaseKeys.Add((object?)caseValue ?? throw new InvalidOperationException("Switch case value cannot be null")); - record.CaseValues.Add(null); - } - return new RecordingMethodImplementationGeneratorSwitchBody(record); - } -} - -public class RecordingMethodImplementationGeneratorSwitchBodyDefaultCase(SwitchBodyRecord record) - : IMethodBodyGeneratorSwitchBodyDefaultCase -{ - public IMethodBodyGenerator ReturnConstantValue(Func func) - { - record.HasDefaultCase = true; - return new RecordingMethodImplementationGenerator(record); - } - - public IMethodBodyGenerator UseProvidedBody(Func> func) - { - record.HasDefaultCase = true; - return new RecordingMethodImplementationGenerator(record); - } -} +// RecordingGeneratorsFactory and associated types have been replaced by the data abstraction layer +// (DataGeneratorsFactory, DataMethodBodyBuilders, and DataRecords in the DataBuilding directory). +// Explicit SwitchCase statements will be replaced in the future. +// See DataMethodBodyBuilders.cs for details on the planned replacement. diff --git a/EasySourceGenerators.Tests/BoolSwitchKeyTests.cs b/EasySourceGenerators.Tests/BoolSwitchKeyTests.cs index 02a1ee4..605441d 100644 --- a/EasySourceGenerators.Tests/BoolSwitchKeyTests.cs +++ b/EasySourceGenerators.Tests/BoolSwitchKeyTests.cs @@ -1,3 +1,7 @@ +// SwitchCase/SwitchDefault attribute-based generation is commented out pending replacement with a data-driven approach. +// See DataMethodBodyBuilders.cs for details on the planned replacement. + +/* using EasySourceGenerators.Abstractions; // ReSharper disable ConvertClosureToMethodGroup @@ -62,3 +66,4 @@ public static partial class TestBoolSwitchClass [SwitchDefault] static Func GetBoolLabel_Default() => _ => "Unknown"; } +*/ diff --git a/EasySourceGenerators.Tests/DefaultCaseConstValue.cs b/EasySourceGenerators.Tests/DefaultCaseConstValue.cs index 5bcc0da..c11d9ff 100644 --- a/EasySourceGenerators.Tests/DefaultCaseConstValue.cs +++ b/EasySourceGenerators.Tests/DefaultCaseConstValue.cs @@ -1,3 +1,7 @@ +// SwitchDefault attribute-based generation is commented out pending replacement with a data-driven approach. +// See DataMethodBodyBuilders.cs for details on the planned replacement. + +/* using EasySourceGenerators.Abstractions; // ReSharper disable ConvertClosureToMethodGroup @@ -47,3 +51,4 @@ public static partial class DefaultCaseConstValueClass [SwitchDefault] static Func Foo_Generator_Default() => decimalNumber => 777; } +*/ diff --git a/EasySourceGenerators.Tests/DefaultCaseConstValueFluent.cs b/EasySourceGenerators.Tests/DefaultCaseConstValueFluent.cs index ac0bf83..b0f8126 100644 --- a/EasySourceGenerators.Tests/DefaultCaseConstValueFluent.cs +++ b/EasySourceGenerators.Tests/DefaultCaseConstValueFluent.cs @@ -1,3 +1,8 @@ +// Fluent switch body generation is commented out pending replacement with a data-driven approach. +// Explicit SwitchCase statements will be replaced in the future. +// See DataMethodBodyBuilders.cs for details on the planned replacement. + +/* using EasySourceGenerators.Abstractions; // ReSharper disable ConvertClosureToMethodGroup @@ -50,3 +55,4 @@ static IMethodBodyGenerator Foo_Generator_Default() => .GenerateSwitchBody() .ForDefaultCase().ReturnConstantValue(_ => 888); } +*/ diff --git a/EasySourceGenerators.Tests/DefaultCaseThrowExpressionTests.cs b/EasySourceGenerators.Tests/DefaultCaseThrowExpressionTests.cs index 470aa8c..e899d48 100644 --- a/EasySourceGenerators.Tests/DefaultCaseThrowExpressionTests.cs +++ b/EasySourceGenerators.Tests/DefaultCaseThrowExpressionTests.cs @@ -1,3 +1,7 @@ +// SwitchDefault attribute-based generation is commented out pending replacement with a data-driven approach. +// See DataMethodBodyBuilders.cs for details on the planned replacement. + +/* using System; using EasySourceGenerators.Abstractions; // ReSharper disable ConvertClosureToMethodGroup @@ -45,3 +49,4 @@ public static partial class DefaultCaseThrowExpressionClass [SwitchDefault] private static Func Foo_Generator_Default() => _ => throw new InvalidOperationException("Unsupported input"); } +*/ diff --git a/EasySourceGenerators.Tests/FluentBodyReturningConstantTests.cs b/EasySourceGenerators.Tests/FluentBodyReturningConstantTests.cs new file mode 100644 index 0000000..d8cfd9f --- /dev/null +++ b/EasySourceGenerators.Tests/FluentBodyReturningConstantTests.cs @@ -0,0 +1,85 @@ +using EasySourceGenerators.Abstractions; + +namespace EasySourceGenerators.Tests; + +[TestFixture] +public class FluentBodyReturningConstantTests +{ + [Test] + public void FluentBodyReturningConstant_ProducesExpectedRuntimeOutput() + { + TestFluentConstantClass instance = new TestFluentConstantClass(); + + string result = instance.GetGreeting(); + + Assert.That(result, Is.EqualTo("Hello from fluent API")); + } + + [Test] + public void FluentBodyReturningConstant_ProducesExpectedGeneratedCode() + { + string generatedCode = GeneratedCodeTestHelper.ReadGeneratedCode("TestFluentConstantClass_GetGreeting.g.cs"); + string expectedCode = """ + namespace EasySourceGenerators.Tests; + + partial class TestFluentConstantClass + { + public partial string GetGreeting() + { + return "Hello from fluent API"; + } + } + """.ReplaceLineEndings("\n").TrimEnd(); + + Assert.That(generatedCode, Is.EqualTo(expectedCode)); + } + + [Test] + public void FluentBodyReturningConstant_Int_ProducesExpectedRuntimeOutput() + { + int result = TestFluentConstantIntClass.GetMagicNumber(); + + Assert.That(result, Is.EqualTo(42)); + } + + [Test] + public void FluentBodyReturningConstant_Int_ProducesExpectedGeneratedCode() + { + string generatedCode = GeneratedCodeTestHelper.ReadGeneratedCode("TestFluentConstantIntClass_GetMagicNumber.g.cs"); + string expectedCode = """ + namespace EasySourceGenerators.Tests; + + static partial class TestFluentConstantIntClass + { + public static partial int GetMagicNumber() + { + return 42; + } + } + """.ReplaceLineEndings("\n").TrimEnd(); + + Assert.That(generatedCode, Is.EqualTo(expectedCode)); + } +} + +public partial class TestFluentConstantClass +{ + public partial string GetGreeting(); + + [MethodBodyGenerator(nameof(GetGreeting))] + static IMethodBodyGenerator GetGreeting_Generator() => + Generate + .MethodBody().ForMethod().WithReturnType().WithNoParameters() + .BodyReturningConstant(() => "Hello from fluent API"); +} + +public static partial class TestFluentConstantIntClass +{ + public static partial int GetMagicNumber(); + + [MethodBodyGenerator(nameof(GetMagicNumber))] + static IMethodBodyGenerator GetMagicNumber_Generator() => + Generate + .MethodBody().ForMethod().WithReturnType().WithNoParameters() + .BodyReturningConstant(() => 42); +} diff --git a/EasySourceGenerators.Tests/PiExampleFluentTests.cs b/EasySourceGenerators.Tests/PiExampleFluentTests.cs index d26b339..c86b859 100644 --- a/EasySourceGenerators.Tests/PiExampleFluentTests.cs +++ b/EasySourceGenerators.Tests/PiExampleFluentTests.cs @@ -1,3 +1,8 @@ +// Fluent switch body generation is commented out pending replacement with a data-driven approach. +// Explicit SwitchCase statements will be replaced in the future. +// See DataMethodBodyBuilders.cs for details on the planned replacement. + +/* using EasySourceGenerators.Abstractions; // ReSharper disable ConvertClosureToMethodGroup @@ -171,3 +176,4 @@ static IMethodBodyGenerator MapToMammal_Generator() => .ForCases(2).ReturnConstantValue(_ => "Cat") .ForDefaultCase().UseBody(_ => () => "Unknown"); } +*/ diff --git a/EasySourceGenerators.Tests/PiExampleTests.cs b/EasySourceGenerators.Tests/PiExampleTests.cs index 19a1ab1..435a55a 100644 --- a/EasySourceGenerators.Tests/PiExampleTests.cs +++ b/EasySourceGenerators.Tests/PiExampleTests.cs @@ -1,3 +1,7 @@ +// SwitchCase/SwitchDefault attribute-based generation is commented out pending replacement with a data-driven approach. +// See DataMethodBodyBuilders.cs for details on the planned replacement. + +/* using EasySourceGenerators.Abstractions; // ReSharper disable ConvertClosureToMethodGroup @@ -66,3 +70,4 @@ static int GetPiDecimal_Generator_Specialized(int decimalNumber) => [SwitchDefault] static Func GetPiDecimal_Generator_Fallback() => decimalNumber => TestSlowMath.CalculatePiDecimal(decimalNumber); } +*/