From f0c1c40a00767cd9bce9df49e4ac75cae4570451 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 16:21:45 +0100 Subject: [PATCH 01/11] Add support for projectable constructors --- .../ProjectableAttribute.cs | 4 +- .../AnalyzerReleases.Shipped.md | 1 + .../ConstructorBodyConverter.cs | 408 +++++++++++ .../Diagnostics.cs | 8 + .../ProjectableInterpreter.cs | 240 +++++- .../Services/ProjectableExpressionReplacer.cs | 34 + .../Services/ProjectionExpressionResolver.cs | 11 +- ...pertyInDerivedBody.DotNet10_0.verified.txt | 2 + ...opertyInDerivedBody.DotNet8_0.verified.txt | 2 + ...opertyInDerivedBody.DotNet9_0.verified.txt | 2 + ...cingBasePropertyInDerivedBody.verified.txt | 2 + ...lyAssignedProperty.DotNet10_0.verified.txt | 2 + ...slyAssignedProperty.DotNet8_0.verified.txt | 2 + ...slyAssignedProperty.DotNet9_0.verified.txt | 2 + ...ingPreviouslyAssignedProperty.verified.txt | 2 + ...gStaticConstMember.DotNet10_0.verified.txt | 2 + ...ngStaticConstMember.DotNet8_0.verified.txt | 2 + ...ngStaticConstMember.DotNet9_0.verified.txt | 2 + ...rReferencingStaticConstMember.verified.txt | 2 + ...itializerAndIfElse.DotNet10_0.verified.txt | 5 + ...nitializerAndIfElse.DotNet9_0.verified.txt | 5 + ...rWithBaseInitializerAndIfElse.verified.txt | 5 + ...tializerExpression.DotNet10_0.verified.txt | 2 + ...itializerExpression.DotNet9_0.verified.txt | 2 + ...WithBaseInitializerExpression.verified.txt | 2 + ...torWithIfElseLogic.DotNet10_0.verified.txt | 5 + ...ctorWithIfElseLogic.DotNet9_0.verified.txt | 5 + ...ct_ConstructorWithIfElseLogic.verified.txt | 5 + ...rWithLocalVariable.DotNet10_0.verified.txt | 2 + ...orWithLocalVariable.DotNet9_0.verified.txt | 2 + ..._ConstructorWithLocalVariable.verified.txt | 2 + ...ithBaseConstructor.DotNet10_0.verified.txt | 2 + ...WithBaseConstructor.DotNet9_0.verified.txt | 2 + ...DerivedDtoWithBaseConstructor.verified.txt | 2 + ...ntityInstanceToDto.DotNet10_0.verified.txt | 2 + ...EntityInstanceToDto.DotNet9_0.verified.txt | 2 + ...ts.Select_EntityInstanceToDto.verified.txt | 2 + ...ctor_WithThreeArgs.DotNet10_0.verified.txt | 2 + ...uctor_WithThreeArgs.DotNet9_0.verified.txt | 2 + ...adedConstructor_WithThreeArgs.verified.txt | 2 + ...ructor_WithTwoArgs.DotNet10_0.verified.txt | 2 + ...tructor_WithTwoArgs.DotNet9_0.verified.txt | 2 + ...loadedConstructor_WithTwoArgs.verified.txt | 2 + ..._ScalarFieldsToDto.DotNet10_0.verified.txt | 2 + ...t_ScalarFieldsToDto.DotNet9_0.verified.txt | 2 + ...ests.Select_ScalarFieldsToDto.verified.txt | 2 + ...ChainedThisAndBase.DotNet10_0.verified.txt | 2 + ..._ChainedThisAndBase.DotNet8_0.verified.txt | 2 + ..._ChainedThisAndBase.DotNet9_0.verified.txt | 2 + ...isOverload_ChainedThisAndBase.verified.txt | 2 + ...oad_SimpleDelegate.DotNet10_0.verified.txt | 2 + ...load_SimpleDelegate.DotNet8_0.verified.txt | 2 + ...load_SimpleDelegate.DotNet9_0.verified.txt | 2 + ...t_ThisOverload_SimpleDelegate.verified.txt | 2 + ...odyAfterDelegation.DotNet10_0.verified.txt | 2 + ...BodyAfterDelegation.DotNet8_0.verified.txt | 2 + ...BodyAfterDelegation.DotNet9_0.verified.txt | 2 + ...rload_WithBodyAfterDelegation.verified.txt | 2 + ...hIfElseInDelegated.DotNet10_0.verified.txt | 5 + ...thIfElseInDelegated.DotNet8_0.verified.txt | 5 + ...thIfElseInDelegated.DotNet9_0.verified.txt | 5 + ...verload_WithIfElseInDelegated.verified.txt | 5 + ...PropertyNotInQuery.DotNet10_0.verified.txt | 2 + ...dPropertyNotInQuery.DotNet9_0.verified.txt | 2 + ..._UnassignedPropertyNotInQuery.verified.txt | 2 + .../ProjectableConstructorTests.cs | 567 ++++++++++++++ ...leConstructor_BodyAssignments.verified.txt | 20 + ...jectableConstructor_Overloads.verified.txt | 20 + ...opertyInDerivedBody.DotNet8_0.verified.txt | 20 + ...cingBasePropertyInDerivedBody.verified.txt | 20 + ...yAssignedInBaseCtor.DotNet8_0.verified.txt | 21 + ...gPreviouslyAssignedInBaseCtor.verified.txt | 21 + ...slyAssignedProperty.DotNet8_0.verified.txt | 21 + ...ingPreviouslyAssignedProperty.verified.txt | 21 + ...ngStaticConstMember.DotNet8_0.verified.txt | 19 + ..._ReferencingStaticConstMember.verified.txt | 19 + ..._ChainedThisAndBase.DotNet8_0.verified.txt | 20 + ...nitializer_ChainedThisAndBase.verified.txt | 20 + ...slyAssignedProperty.DotNet8_0.verified.txt | 21 + ...ingPreviouslyAssignedProperty.verified.txt | 21 + ...izer_SimpleOverload.DotNet8_0.verified.txt | 20 + ...hisInitializer_SimpleOverload.verified.txt | 20 + ...lizer_WithBodyAfter.DotNet8_0.verified.txt | 21 + ...ThisInitializer_WithBodyAfter.verified.txt | 21 + ...thIfElseInDelegated.DotNet8_0.verified.txt | 20 + ...ializer_WithIfElseInDelegated.verified.txt | 20 + ...nstructor_WithBaseInitializer.verified.txt | 20 + ...nitializerAndIfElse.DotNet8_0.verified.txt | 20 + ..._WithBaseInitializerAndIfElse.verified.txt | 20 + ...itializerExpression.DotNet8_0.verified.txt | 20 + ...WithBaseInitializerExpression.verified.txt | 20 + ...Constructor_WithClassArgument.verified.txt | 20 + ...onstructor_Succeeds.DotNet8_0.verified.txt | 19 + ...meterlessConstructor_Succeeds.verified.txt | 19 + ...tor_WithIfElseLogic.DotNet8_0.verified.txt | 20 + ...leConstructor_WithIfElseLogic.verified.txt | 20 + ...ructor_WithIfNoElse.DotNet8_0.verified.txt | 19 + ...tableConstructor_WithIfNoElse.verified.txt | 19 + ...r_WithLocalVariable.DotNet8_0.verified.txt | 19 + ...Constructor_WithLocalVariable.verified.txt | 19 + ...or_WithMultipleClassArguments.verified.txt | 20 + .../ProjectionExpressionGeneratorTests.cs | 692 ++++++++++++++++++ 102 files changed, 2804 insertions(+), 7 deletions(-) create mode 100644 src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt diff --git a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs index 94b63b2..6edbee0 100644 --- a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs +++ b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs @@ -1,10 +1,10 @@ namespace EntityFrameworkCore.Projectables { /// - /// Declares this property or method to be Projectable. + /// Declares this property, method or constructor to be Projectable. /// A companion Expression tree will be generated /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, Inherited = true, AllowMultiple = false)] public sealed class ProjectableAttribute : Attribute { /// diff --git a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md index 253db78..249da29 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md +++ b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md @@ -8,6 +8,7 @@ EFP0003 | Design | Warning | Unsupported statement in block-bodied method EFP0004 | Design | Error | Statement with side effects in block-bodied method EFP0005 | Design | Warning | Potential side effect in block-bodied method EFP0006 | Design | Error | Method or property should expose a body definition (block or expression) +EFP0007 | Design | Error | Target class is missing a parameterless constructor ### Changed Rules diff --git a/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs new file mode 100644 index 0000000..15618b0 --- /dev/null +++ b/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace EntityFrameworkCore.Projectables.Generator +{ + /// + /// Converts constructor body statements into a dictionary of property-name → expression + /// pairs that are used to build a member-init expression for EF Core projections. + /// Supports simple assignments, local variable declarations, and if/else statements. + /// Previously-assigned properties (including those from a delegated base/this ctor) are + /// inlined when referenced in subsequent assignments. + /// + public class ConstructorBodyConverter + { + private readonly SourceProductionContext _context; + private readonly Func _rewrite; + private readonly Dictionary _paramSubstitutions; + private readonly Dictionary _localVariables = new(); + + /// Creates a converter for the main constructor body. + public ConstructorBodyConverter( + SourceProductionContext context, + ExpressionSyntaxRewriter expressionRewriter) + { + _context = context; + _rewrite = expr => (ExpressionSyntax)expressionRewriter.Visit(expr); + _paramSubstitutions = new Dictionary(); + } + + /// Creates a converter for a delegated (base/this) constructor body. + public ConstructorBodyConverter( + SourceProductionContext context, + Dictionary paramSubstitutions) + { + _context = context; + _rewrite = expr => expr; + _paramSubstitutions = paramSubstitutions; + } + + public Dictionary? TryConvertBody( + IEnumerable statements, + string memberName, + IReadOnlyDictionary? initialContext = null) + { + var assignments = new Dictionary(); + if (!TryProcessBlock(statements, assignments, memberName, outerContext: initialContext)) + { + return null; + } + + return assignments; + } + + private bool TryProcessBlock( + IEnumerable statements, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? outerContext) + { + foreach (var statement in statements) + { + // Everything accumulated so far (from outer scope + this block) is visible. + var visible = BuildVisible(outerContext, assignments); + if (!TryProcessStatement(statement, assignments, memberName, visible)) + { + return false; + } + } + return true; + } + + private bool TryProcessStatement( + StatementSyntax statement, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? visibleContext) + { + switch (statement) + { + case LocalDeclarationStatementSyntax localDecl: + return TryProcessLocalDeclaration(localDecl, memberName, visibleContext); + + case ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax assignment } + when assignment.IsKind(SyntaxKind.SimpleAssignmentExpression): + return TryProcessAssignment(assignment, assignments, memberName, visibleContext); + + case IfStatementSyntax ifStmt: + return TryProcessIfStatement(ifStmt, assignments, memberName, visibleContext); + + case BlockSyntax block: + return TryProcessBlock(block.Statements, assignments, memberName, visibleContext); + + default: + ReportUnsupported(statement, memberName, + $"Statement type '{statement.GetType().Name}' is not supported in a [Projectable] constructor body. " + + "Only assignments, local variable declarations, and if/else statements are supported."); + return false; + } + } + + private bool TryProcessLocalDeclaration( + LocalDeclarationStatementSyntax localDecl, + string memberName, + IReadOnlyDictionary? visibleContext) + { + foreach (var variable in localDecl.Declaration.Variables) + { + if (variable.Initializer == null) + { + ReportUnsupported(localDecl, memberName, "Local variables must have an initializer"); + return false; + } + + var rewritten = _rewrite(variable.Initializer.Value); + rewritten = ApplySubstitutions(rewritten, visibleContext); + _localVariables[variable.Identifier.Text] = rewritten; + } + return true; + } + + private bool TryProcessAssignment( + AssignmentExpressionSyntax assignment, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? visibleContext) + { + var targetMember = GetTargetMember(assignment.Left); + if (targetMember is null) + { + ReportUnsupported(assignment, memberName, + $"Unsupported assignment target '{assignment.Left}'. " + + "Only 'PropertyName = ...' or 'this.PropertyName = ...' are supported."); + return false; + } + + var rewritten = _rewrite(assignment.Right); + rewritten = ApplySubstitutions(rewritten, visibleContext); + assignments[targetMember.Identifier.Text] = rewritten; + return true; + } + + private bool TryProcessIfStatement( + IfStatementSyntax ifStmt, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? visibleContext) + { + var condition = _rewrite(ifStmt.Condition); + condition = ApplySubstitutions(condition, visibleContext); + + // Each branch starts empty but can see the pre-if accumulated props (visibleContext). + var thenAssignments = new Dictionary(); + if (!TryProcessBlock(GetStatements(ifStmt.Statement), thenAssignments, memberName, visibleContext)) + { + return false; + } + + var elseAssignments = new Dictionary(); + if (ifStmt.Else != null) + { + if (!TryProcessBlock(GetStatements(ifStmt.Else.Statement), elseAssignments, memberName, visibleContext)) + { + return false; + } + } + + foreach (var thenKvp in thenAssignments) + { + var prop = thenKvp.Key; + var thenExpr = thenKvp.Value; + + ExpressionSyntax elseExpr; + if (elseAssignments.TryGetValue(prop, out var elseVal)) + { + elseExpr = elseVal; + } + else if (assignments.TryGetValue(prop, out var existing)) + { + elseExpr = existing; + } + else + { + elseExpr = DefaultLiteral(); + } + + assignments[prop] = SyntaxFactory.ConditionalExpression(condition, thenExpr, elseExpr); + } + + foreach (var elseKvp in elseAssignments) + { + var prop = elseKvp.Key; + var elseExpr = elseKvp.Value; + + if (thenAssignments.ContainsKey(prop)) + { + continue; + } + + ExpressionSyntax thenExpr; + if (assignments.TryGetValue(prop, out var existing)) + { + thenExpr = existing; + } + else + { + thenExpr = DefaultLiteral(); + } + + assignments[prop] = SyntaxFactory.ConditionalExpression(condition, thenExpr, elseExpr); + } + + return true; + } + + private static IEnumerable GetStatements(StatementSyntax statement) => + statement is BlockSyntax block + ? block.Statements + : new StatementSyntax[] { statement }; + + private static IdentifierNameSyntax? GetTargetMember(ExpressionSyntax left) => + left switch + { + MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax, Name: IdentifierNameSyntax name } => name, + IdentifierNameSyntax ident => ident, + _ => null + }; + + /// + /// Merges outer (parent scope) and local (current block) accumulated dictionaries + /// into a single read-only view for use as a substitution context. + /// Local entries take priority over outer entries. + /// Returns null when both are empty (avoids unnecessary allocations). + /// + private static IReadOnlyDictionary? BuildVisible( + IReadOnlyDictionary? outer, + Dictionary local) + { + bool outerEmpty = outer == null || outer.Count == 0; + var localEmpty = local.Count == 0; + + if (outerEmpty && localEmpty) + { + return null; + } + + if (outerEmpty) + { + return local; + } + + if (localEmpty) + { + return outer; + } + + var merged = new Dictionary(); + foreach (var kvp in outer!) + { + merged[kvp.Key] = kvp.Value; + } + + foreach (var kvp in local) + { + merged[kvp.Key] = kvp.Value; + } + + return merged; + } + + private ExpressionSyntax ApplySubstitutions( + ExpressionSyntax expr, + IReadOnlyDictionary? visibleContext) + { + if (_paramSubstitutions.Count > 0) + { + expr = ParameterSubstitutor.Substitute(expr, _paramSubstitutions); + } + + if (_localVariables.Count > 0) + { + expr = LocalVariableSubstitutor.Substitute(expr, _localVariables); + } + + if (visibleContext != null && visibleContext.Count > 0) + { + expr = AssignedPropertySubstitutor.Substitute(expr, visibleContext); + } + + return expr; + } + + private static ExpressionSyntax DefaultLiteral() => + SyntaxFactory.LiteralExpression( + SyntaxKind.DefaultLiteralExpression, + SyntaxFactory.Token(SyntaxKind.DefaultKeyword)); + + private void ReportUnsupported(SyntaxNode node, string memberName, string reason) + { + _context.ReportDiagnostic(Diagnostic.Create( + Diagnostics.UnsupportedStatementInBlockBody, + node.GetLocation(), + memberName, + reason)); + } + + /// + /// Replaces parameter-name identifier references with call-site argument expressions + /// (used when inlining a delegated base/this constructor body). + /// + internal sealed class ParameterSubstitutor : CSharpSyntaxRewriter + { + private readonly Dictionary _map; + private ParameterSubstitutor(Dictionary map) => _map = map; + + public static ExpressionSyntax Substitute(ExpressionSyntax expr, Dictionary map) + => (ExpressionSyntax)new ParameterSubstitutor(map).Visit(expr); + + public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) + => _map.TryGetValue(node.Identifier.Text, out var replacement) + ? replacement.WithTriviaFrom(node) + : base.VisitIdentifierName(node); + } + + /// + /// Replaces local-variable identifier references with their inlined (parenthesised) + /// initializer expressions. + /// + private sealed class LocalVariableSubstitutor : CSharpSyntaxRewriter + { + private readonly Dictionary _locals; + private LocalVariableSubstitutor(Dictionary locals) => _locals = locals; + + public static ExpressionSyntax Substitute(ExpressionSyntax expr, Dictionary locals) + => (ExpressionSyntax)new LocalVariableSubstitutor(locals).Visit(expr); + + public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) + => _locals.TryGetValue(node.Identifier.Text, out var replacement) + ? SyntaxFactory.ParenthesizedExpression(replacement.WithoutTrivia()).WithTriviaFrom(node) + : base.VisitIdentifierName(node); + } + + /// + /// Replaces references to previously-assigned properties with the expression that was + /// assigned to them, so that EF Core sees a fully-inlined projection. + /// + /// Handles two syntactic forms: + /// + /// @this.PropName — produced by for + /// instance-member references in the main constructor body. + /// Bare PropName identifier — appears in delegated (base/this) constructor + /// bodies where the identity rewriter is used. + /// + /// + /// + private sealed class AssignedPropertySubstitutor : CSharpSyntaxRewriter + { + private readonly IReadOnlyDictionary _accumulated; + + private AssignedPropertySubstitutor(IReadOnlyDictionary accumulated) + => _accumulated = accumulated; + + public static ExpressionSyntax Substitute( + ExpressionSyntax expr, + IReadOnlyDictionary accumulated) + => (ExpressionSyntax)new AssignedPropertySubstitutor(accumulated).Visit(expr); + + // Catches @this.PropName → inline the accumulated expression for PropName. + public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + { + if (node.Expression is IdentifierNameSyntax thisRef && + (thisRef.Identifier.Text == "@this" || thisRef.Identifier.ValueText == "this") && + node.Name is IdentifierNameSyntax propName && + _accumulated.TryGetValue(propName.Identifier.Text, out var replacement)) + { + return SyntaxFactory.ParenthesizedExpression(replacement.WithoutTrivia()) + .WithTriviaFrom(node); + } + + return base.VisitMemberAccessExpression(node); + } + + // Catches bare PropName → inline accumulated expression (delegated ctor case). + // Params and locals have already been substituted before this runs, so any remaining + // bare identifier that matches an accumulated property key is a property reference. + public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) + { + // Do not substitute special identifiers (@this, type keywords, etc.) + var text = node.Identifier.Text; + if (text.StartsWith("@") || text == "default" || text == "null" || text == "true" || text == "false") + { + return base.VisitIdentifierName(node); + } + + if (_accumulated.TryGetValue(text, out var replacement)) + { + return SyntaxFactory.ParenthesizedExpression(replacement.WithoutTrivia()) + .WithTriviaFrom(node); + } + + return base.VisitIdentifierName(node); + } + } + } +} diff --git a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs index 70e2964..5e88014 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs @@ -52,5 +52,13 @@ public static class Diagnostics DiagnosticSeverity.Error, isEnabledByDefault: true); + public static readonly DiagnosticDescriptor MissingParameterlessConstructor = new DiagnosticDescriptor( + id: "EFP0007", + title: "Target class is missing a parameterless constructor", + messageFormat: "Class '{0}' must have a parameterless constructor to be used with a [Projectable] constructor. The generated projection uses 'new {0}() {{ ... }}' (object-initializer syntax), which requires a publicly accessible parameterless constructor.", + category: "Design", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + } } diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs index 3037fb1..bda392f 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs @@ -188,19 +188,24 @@ x is IPropertySymbol xProperty && ? memberSymbol.ContainingType.ContainingType : memberSymbol.ContainingType; + var methodSymbol = memberSymbol as IMethodSymbol; + + // Sanitize constructor name (.ctor / .cctor are not valid C# identifiers, use _ctor) + var memberName = methodSymbol?.MethodKind is MethodKind.Constructor or MethodKind.StaticConstructor + ? "_ctor" + : memberSymbol.Name; + var descriptor = new ProjectableDescriptor { UsingDirectives = member.SyntaxTree.GetRoot().DescendantNodes().OfType(), ClassName = classForNaming.Name, ClassNamespace = classForNaming.ContainingNamespace.IsGlobalNamespace ? null : classForNaming.ContainingNamespace.ToDisplayString(), - MemberName = memberSymbol.Name, + MemberName = memberName, NestedInClassNames = isExtensionMember ? GetNestedInClassPathForExtensionMember(memberSymbol.ContainingType) : GetNestedInClassPath(memberSymbol.ContainingType), ParametersList = SyntaxFactory.ParameterList() }; - - var methodSymbol = memberSymbol as IMethodSymbol; // Collect parameter type names for method overload disambiguation if (methodSymbol is not null) @@ -288,7 +293,7 @@ x is IPropertySymbol xProperty && ) ); } - else if (!member.Modifiers.Any(SyntaxKind.StaticKeyword)) + else if (!member.Modifiers.Any(SyntaxKind.StaticKeyword) && member is not ConstructorDeclarationSyntax) { descriptor.ParametersList = descriptor.ParametersList.AddParameters( SyntaxFactory.Parameter( @@ -452,6 +457,116 @@ x is IPropertySymbol xProperty && ? bodyExpression : (ExpressionSyntax)expressionSyntaxRewriter.Visit(bodyExpression); } + // Projectable constructors + else if (memberBody is ConstructorDeclarationSyntax constructorDeclarationSyntax) + { + var containingType = memberSymbol.ContainingType; + var fullTypeName = containingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + descriptor.ReturnTypeName = fullTypeName; + + // Add the constructor's own parameters to the lambda parameter list + foreach (var additionalParameter in ((ParameterListSyntax)declarationSyntaxRewriter.Visit(constructorDeclarationSyntax.ParameterList)).Parameters) + { + descriptor.ParametersList = descriptor.ParametersList.AddParameters(additionalParameter); + } + + // Accumulated property-name → expression map (later converted to member-init) + var accumulatedAssignments = new Dictionary(); + + // 1. Process base/this initializer: propagate property assignments from the + // delegated constructor so callers don't have to duplicate them in the body. + if (constructorDeclarationSyntax.Initializer is { } initializer) + { + var initializerSymbol = semanticModel.GetSymbolInfo(initializer).Symbol as IMethodSymbol; + if (initializerSymbol is not null) + { + var delegatedAssignments = CollectDelegatedConstructorAssignments( + initializerSymbol, + initializer.ArgumentList.Arguments, + expressionSyntaxRewriter, + context, + memberSymbol.Name); + + if (delegatedAssignments is null) + { + return null; + } + + foreach (var kvp in delegatedAssignments) + { + accumulatedAssignments[kvp.Key] = kvp.Value; + } + } + } + + // 2. Process this constructor's body (supports assignments, locals, if/else). + // Pass the already-accumulated base/this initializer assignments as the initial + // visible context so that references to those properties are correctly inlined. + if (constructorDeclarationSyntax.Body is { } body) + { + var bodyConverter = new ConstructorBodyConverter(context, expressionSyntaxRewriter); + IReadOnlyDictionary? initialCtx = + accumulatedAssignments.Count > 0 ? accumulatedAssignments : null; + var bodyAssignments = bodyConverter.TryConvertBody(body.Statements, memberSymbol.Name, initialCtx); + + if (bodyAssignments is null) + { + return null; + } + + // Body assignments override anything set by the base/this initializer + foreach (var kvp in bodyAssignments) + { + accumulatedAssignments[kvp.Key] = kvp.Value; + } + } + + if (accumulatedAssignments.Count == 0) + { + var diag = Diagnostic.Create(Diagnostics.RequiresBodyDefinition, + constructorDeclarationSyntax.GetLocation(), memberSymbol.Name); + context.ReportDiagnostic(diag); + return null; + } + + // Verify the containing type has a parameterless (instance) constructor. + // The generated projection is: new T() { Prop = ... }, which requires one. + // INamedTypeSymbol.Constructors covers all partial declarations and also + // the implicit parameterless constructor that the compiler synthesizes when + // no constructors are explicitly defined. + var hasParameterlessConstructor = containingType.Constructors + .Any(c => !c.IsStatic && c.Parameters.IsEmpty); + + if (!hasParameterlessConstructor) + { + context.ReportDiagnostic(Diagnostic.Create( + Diagnostics.MissingParameterlessConstructor, + constructorDeclarationSyntax.GetLocation(), + containingType.Name)); + return null; + } + + var initExpressions = accumulatedAssignments + .Select(kvp => (ExpressionSyntax)SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.IdentifierName(kvp.Key), + kvp.Value)) + .ToList(); + + var memberInit = SyntaxFactory.InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SyntaxFactory.SeparatedList(initExpressions)); + + // Use a parameterless constructor + object initializer so EF Core only + // projects columns explicitly listed in the member-init bindings. + descriptor.ExpressionBody = SyntaxFactory.ObjectCreationExpression( + SyntaxFactory.Token(SyntaxKind.NewKeyword).WithTrailingTrivia(SyntaxFactory.Space), + SyntaxFactory.ParseTypeName(fullTypeName), + SyntaxFactory.ArgumentList(), + memberInit + ); + } else { return null; @@ -460,6 +575,123 @@ x is IPropertySymbol xProperty && return descriptor; } + /// + /// Collects the property-assignment expressions that the delegated constructor (base/this) + /// would perform, substituting its parameters with the actual call-site argument expressions. + /// Supports if/else logic inside the delegated constructor body, and follows the chain of + /// base/this initializers recursively. + /// Returns null when an unsupported statement is encountered (diagnostics reported). + /// + private static Dictionary? CollectDelegatedConstructorAssignments( + IMethodSymbol delegatedCtor, + SeparatedSyntaxList callerArgs, + ExpressionSyntaxRewriter expressionSyntaxRewriter, + SourceProductionContext context, + string memberName, + bool argsAlreadyRewritten = false) + { + // Only process constructors whose source is available in this compilation + var syntax = delegatedCtor.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType() + .FirstOrDefault(); + + if (syntax is null) + { + return new Dictionary(); + } + + // Build a mapping: delegated-param-name → caller argument expression. + // First-level args come from the original syntax tree and must be visited by the + // ExpressionSyntaxRewriter. Recursive-level args are already-substituted detached + // nodes and must NOT be visited (doing so throws "node not in syntax tree"). + var paramToArg = new Dictionary(); + for (var i = 0; i < callerArgs.Count && i < delegatedCtor.Parameters.Length; i++) + { + var paramName = delegatedCtor.Parameters[i].Name; + var argExpr = argsAlreadyRewritten + ? callerArgs[i].Expression + : (ExpressionSyntax)expressionSyntaxRewriter.Visit(callerArgs[i].Expression); + paramToArg[paramName] = argExpr; + } + + // The accumulated assignments start from the delegated ctor's own initializer (if any), + // so that base/this chains are followed recursively. + var accumulated = new Dictionary(); + + if (syntax.Initializer is { } delegatedInitializer) + { + // The delegated ctor's initializer is part of the original syntax tree, + // so we can safely use the semantic model to resolve its symbol. + var semanticModel = expressionSyntaxRewriter.GetSemanticModel(); + var delegatedInitializerSymbol = + semanticModel.GetSymbolInfo(delegatedInitializer).Symbol as IMethodSymbol; + + if (delegatedInitializerSymbol is not null) + { + // Substitute the delegated ctor's initializer arguments using our paramToArg map, + // so that e.g. `: base(id)` becomes `: base()`. + var substitutedInitArgs = SubstituteArguments( + delegatedInitializer.ArgumentList.Arguments, paramToArg); + + var chainedAssignments = CollectDelegatedConstructorAssignments( + delegatedInitializerSymbol, + substitutedInitArgs, + expressionSyntaxRewriter, + context, + memberName, + argsAlreadyRewritten: true); // args are now detached substituted nodes + + if (chainedAssignments is null) + return null; + + foreach (var kvp in chainedAssignments) + accumulated[kvp.Key] = kvp.Value; + } + } + + if (syntax.Body is null) + return accumulated; + + // Use ConstructorBodyConverter (identity rewriter + param substitutions) so that + // if/else, local variables and simple assignments in the delegated ctor are all handled. + // Pass the already-accumulated chained assignments as the initial visible context. + IReadOnlyDictionary? initialCtx = + accumulated.Count > 0 ? accumulated : null; + var converter = new ConstructorBodyConverter(context, paramToArg); + var bodyAssignments = converter.TryConvertBody(syntax.Body.Statements, memberName, initialCtx); + + if (bodyAssignments is null) + return null; + + foreach (var kvp in bodyAssignments) + accumulated[kvp.Key] = kvp.Value; + + return accumulated; + } + + /// + /// Substitutes identifiers in using the + /// mapping. This is used to forward the outer caller's arguments through a chain of + /// base/this initializer calls. + /// + private static SeparatedSyntaxList SubstituteArguments( + SeparatedSyntaxList args, + Dictionary paramToArg) + { + if (paramToArg.Count == 0) + return args; + + var result = new List(); + foreach (var arg in args) + { + var substituted = ConstructorBodyConverter.ParameterSubstitutor.Substitute( + arg.Expression, paramToArg); + result.Add(arg.WithExpression(substituted)); + } + return SyntaxFactory.SeparatedList(result); + } + private static TypeConstraintSyntax MakeTypeConstraint(string constraint) => SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName(constraint)); } } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index 8f9f4a1..6d815e2 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -15,6 +15,7 @@ public sealed class ProjectableExpressionReplacer : ExpressionVisitor private readonly IProjectionExpressionResolver _resolver; private readonly ExpressionArgumentReplacer _expressionArgumentReplacer = new(); private readonly Dictionary _projectableMemberCache = new(); + private readonly HashSet _expandingConstructors = new(); private IQueryProvider? _currentQueryProvider; private bool _disableRootRewrite = false; private readonly bool _trackingByDefault; @@ -203,6 +204,39 @@ protected override Expression VisitMethodCall(MethodCallExpression node) return base.VisitMethodCall(node); } + protected override Expression VisitNew(NewExpression node) + { + var constructor = node.Constructor; + if (constructor is not null && + !_expandingConstructors.Contains(constructor) && + TryGetReflectedExpression(constructor, out var reflectedExpression)) + { + _expandingConstructors.Add(constructor); + try + { + for (var parameterIndex = 0; parameterIndex < reflectedExpression.Parameters.Count; parameterIndex++) + { + var parameterExpression = reflectedExpression.Parameters[parameterIndex]; + if (parameterIndex < node.Arguments.Count) + { + _expressionArgumentReplacer.ParameterArgumentMapping.Add(parameterExpression, node.Arguments[parameterIndex]); + } + } + + var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body); + _expressionArgumentReplacer.ParameterArgumentMapping.Clear(); + + return base.Visit(updatedBody); + } + finally + { + _expandingConstructors.Remove(constructor); + } + } + + return base.VisitNew(node); + } + protected override Expression VisitMember(MemberExpression node) { // Evaluate captured variables in closures that contain EF queries to inline them into the main query diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs index b6ded59..7e26d69 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs @@ -77,6 +77,7 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo // Use the same format as Roslyn's SymbolDisplayFormat.FullyQualifiedFormat // which uses C# keywords for primitive types (int, string, etc.) string[]? parameterTypeNames = null; + string memberLookupName = projectableMemberInfo.Name; if (projectableMemberInfo is MethodInfo method) { // For generic methods, use the generic definition to get parameter types @@ -87,8 +88,16 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo .Select(p => GetFullTypeName(p.ParameterType)) .ToArray(); } + else if (projectableMemberInfo is ConstructorInfo ctor) + { + // Constructors are stored under the synthetic name "_ctor" + memberLookupName = "_ctor"; + parameterTypeNames = ctor.GetParameters() + .Select(p => GetFullTypeName(p.ParameterType)) + .ToArray(); + } - var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name, parameterTypeNames); + var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), memberLookupName, parameterTypeNames); var expressionFactoryType = declaringType.Assembly.GetType(generatedContainingTypeName); diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt new file mode 100644 index 0000000..2f9967b --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt @@ -0,0 +1,5 @@ +SELECT CASE + WHEN [p].[Id] < 0 THEN 0 + ELSE [p].[Id] +END AS [Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt new file mode 100644 index 0000000..2f9967b --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt @@ -0,0 +1,5 @@ +SELECT CASE + WHEN [p].[Id] < 0 THEN 0 + ELSE [p].[Id] +END AS [Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt new file mode 100644 index 0000000..2f9967b --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt @@ -0,0 +1,5 @@ +SELECT CASE + WHEN [p].[Id] < 0 THEN 0 + ELSE [p].[Id] +END AS [Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt new file mode 100644 index 0000000..0c0f115 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT UPPER([p].[LastName]) AS [Code], [p].[FirstName] AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt new file mode 100644 index 0000000..0c0f115 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT UPPER([p].[LastName]) AS [Code], [p].[FirstName] AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt new file mode 100644 index 0000000..0c0f115 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt @@ -0,0 +1,2 @@ +SELECT UPPER([p].[LastName]) AS [Code], [p].[FirstName] AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt new file mode 100644 index 0000000..68ebfbf --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Id], CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt new file mode 100644 index 0000000..68ebfbf --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Id], CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt new file mode 100644 index 0000000..68ebfbf --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Id], CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt new file mode 100644 index 0000000..dee2833 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt new file mode 100644 index 0000000..dee2833 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs new file mode 100644 index 0000000..af6d9ea --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs @@ -0,0 +1,567 @@ +using System.Linq; +using System.Threading.Tasks; +using EntityFrameworkCore.Projectables.FunctionalTests.Helpers; +using Microsoft.EntityFrameworkCore; +using VerifyXunit; +using Xunit; + +#nullable disable + +namespace EntityFrameworkCore.Projectables.FunctionalTests +{ + [UsesVerify] + public class ProjectableConstructorTests + { + public class PersonEntity + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public int Score { get; set; } + } + + /// DTO built from scalar entity fields. + public class PersonSummaryDto + { + public int Id { get; set; } + public string FullName { get; set; } + + public PersonSummaryDto() { } // required: EF Core uses the parameterless ctor + + [Projectable] + public PersonSummaryDto(int id, string firstName, string lastName) + { + Id = id; + FullName = firstName + " " + lastName; + } + } + + /// DTO built by passing the whole entity instance as the constructor argument. + public class PersonFromEntityDto + { + public int Id { get; set; } + public string FullName { get; set; } + + public PersonFromEntityDto() { } // required: EF Core uses the parameterless ctor + + [Projectable] + public PersonFromEntityDto(PersonEntity entity) + { + Id = entity.Id; + FullName = entity.FirstName + " " + entity.LastName; + } + } + + public class BaseDto + { + public int Id { get; set; } + + public BaseDto() { } // required + public BaseDto(int id) { Id = id; } + } + + public class DerivedDto : BaseDto + { + public string FullName { get; set; } + + public DerivedDto() { } // required: EF Core uses the parameterless ctor + + /// + /// Id is automatically included from : base(id) — no need to repeat it here. + /// + [Projectable] + public DerivedDto(int id, string firstName, string lastName) : base(id) + { + FullName = firstName + " " + lastName; + } + } + + public class PersonOverloadedDto + { + public int Id { get; set; } + public string FullName { get; set; } + + public PersonOverloadedDto() { } // required: EF Core uses the parameterless ctor + + [Projectable] + public PersonOverloadedDto(int id, string firstName, string lastName) + { + Id = id; + FullName = firstName + " " + lastName; + } + + [Projectable] + public PersonOverloadedDto(string firstName, string lastName) + { + // Id deliberately not set → should not appear as a DB column in the query + FullName = firstName + " " + lastName; + } + } + + // ── Partial / unmapped property ─────────────────────────────────────────── + + /// + /// DTO with a Nickname property that is intentionally NOT assigned + /// in the [Projectable] constructor body. + /// The generated SQL must NOT include a column for Nickname. + /// + public class PersonPartialDto + { + public int Id { get; set; } + public string FullName { get; set; } + public string Nickname { get; set; } // intentionally unmapped in the constructor + + public PersonPartialDto() { } // required + + [Projectable] + public PersonPartialDto(int id, string firstName, string lastName) + { + Id = id; + FullName = firstName + " " + lastName; + // Nickname is intentionally NOT assigned here + } + } + + /// DTO with if/else logic in the constructor body. + public class PersonGradeDto + { + public int Id { get; set; } + public string Grade { get; set; } + + public PersonGradeDto() { } + + [Projectable] + public PersonGradeDto(int id, int score) + { + Id = id; + if (score >= 90) + { + Grade = "A"; + } + else + { + Grade = "B"; + } + } + } + + /// DTO using a local variable in the constructor body. + public class PersonLocalVarDto + { + public string FullName { get; set; } + + public PersonLocalVarDto() { } + + [Projectable] + public PersonLocalVarDto(string first, string last) + { + var full = first + " " + last; + FullName = full; + } + } + + public class PersonBaseWithExprDto + { + public string Code { get; set; } + + public PersonBaseWithExprDto() { } + public PersonBaseWithExprDto(string code) { Code = code; } + } + + public class PersonDerivedWithExprDto : PersonBaseWithExprDto + { + public string Name { get; set; } + + public PersonDerivedWithExprDto() { } + + [Projectable] + public PersonDerivedWithExprDto(string name, string rawCode) : base(rawCode.ToUpper()) + { + Name = name; + } + } + + public class PersonBaseWithLogicDto + { + public int Id { get; set; } + + public PersonBaseWithLogicDto() { } + public PersonBaseWithLogicDto(int id) + { + if (id < 0) + { + Id = 0; + } + else + { + Id = id; + } + } + } + + public class PersonDerivedWithBaseLogicDto : PersonBaseWithLogicDto + { + public string FullName { get; set; } + + public PersonDerivedWithBaseLogicDto() { } + + [Projectable] + public PersonDerivedWithBaseLogicDto(int id, string firstName, string lastName) : base(id) + { + FullName = firstName + " " + lastName; + } + } + + // ── Referencing previously-assigned property ────────────────────────────── + + public class PersonWithCompositeDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonWithCompositeDto() { } + + [Projectable] + public PersonWithCompositeDto(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + FullName = FirstName + " " + LastName; // references previously-assigned props + } + } + + // ── Referencing base property in derived body ───────────────────────────── + + public class PersonBaseCodeDto + { + public string Code { get; set; } + public PersonBaseCodeDto() { } + public PersonBaseCodeDto(string code) { Code = code; } + } + + public class PersonDerivedLabelDto : PersonBaseCodeDto + { + public string Label { get; set; } + + public PersonDerivedLabelDto() { } + + [Projectable] + public PersonDerivedLabelDto(string code) : base(code) + { + Label = "[" + Code + "]"; // Code was set by base ctor → should inline it + } + } + + // ── Static / const member ───────────────────────────────────────────────── + + public class PersonWithConstSeparatorDto + { + internal const string Separator = " - "; + + public string FullName { get; set; } + + public PersonWithConstSeparatorDto() { } + + [Projectable] + public PersonWithConstSeparatorDto(string firstName, string lastName) + { + FullName = firstName + Separator + lastName; + } + } + + // ── this() overload – simple delegation ─────────────────────────────────── + + public class PersonThisSimpleDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonThisSimpleDto() { } + + public PersonThisSimpleDto(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + } + + /// Delegates to the 2-arg ctor using a split on the full name. + [Projectable] + public PersonThisSimpleDto(string fullName) : this(fullName, "") + { + } + } + + // ── this() overload – with additional body after delegation ─────────────── + + public class PersonThisBodyAfterDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonThisBodyAfterDto() { } + + public PersonThisBodyAfterDto(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonThisBodyAfterDto(string firstName, string lastName, bool upper) : this(firstName, lastName) + { + FullName = upper ? (FirstName + " " + LastName).ToUpper() : FirstName + " " + LastName; + } + } + + // ── this() overload – if/else logic in the delegated constructor ────────── + + public class PersonThisIfElseDto + { + public string Grade { get; set; } + public int Score { get; set; } + + public PersonThisIfElseDto() { } + + public PersonThisIfElseDto(int score) + { + Score = score; + if (score >= 90) + Grade = "A"; + else + Grade = "B"; + } + + [Projectable] + public PersonThisIfElseDto(int score, string prefix) : this(score) + { + Grade = prefix + Grade; + } + } + + // ── this() → base() chain ───────────────────────────────────────────────── + + public class PersonChainBase + { + public int Id { get; set; } + public PersonChainBase() { } + public PersonChainBase(int id) { Id = id; } + } + + public class PersonChainChild : PersonChainBase + { + public string Name { get; set; } + + public PersonChainChild() { } + + public PersonChainChild(int id, string name) : base(id) + { + Name = name; + } + + [Projectable] + public PersonChainChild(int id, string name, string suffix) : this(id, name) + { + Name = Name + suffix; + } + } + + [Fact] + public Task Select_ScalarFieldsToDto() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonSummaryDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_EntityInstanceToDto() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonFromEntityDto(p)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_DerivedDtoWithBaseConstructor() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new DerivedDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_OverloadedConstructor_WithThreeArgs() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonOverloadedDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_OverloadedConstructor_WithTwoArgs() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonOverloadedDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + /// + /// Verifies that a property not assigned in the [Projectable] constructor body + /// does NOT appear as a column in the generated SQL query. + /// + [Fact] + public Task Select_UnassignedPropertyNotInQuery() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonPartialDto(p.Id, p.FirstName, p.LastName)); + + var sql = query.ToQueryString(); + + // Nickname is not assigned in the constructor → must not appear in SQL + Assert.DoesNotContain("Nickname", sql, System.StringComparison.OrdinalIgnoreCase); + + return Verifier.Verify(sql); + } + + [Fact] + public Task Select_ConstructorWithIfElseLogic() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonGradeDto(p.Id, p.Score)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorWithLocalVariable() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonLocalVarDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorWithBaseInitializerExpression() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonDerivedWithExprDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorWithBaseInitializerAndIfElse() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonDerivedWithBaseLogicDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorReferencingPreviouslyAssignedProperty() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonWithCompositeDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorReferencingBasePropertyInDerivedBody() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonDerivedLabelDto(p.FirstName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorReferencingStaticConstMember() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonWithConstSeparatorDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_SimpleDelegate() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonThisSimpleDto(p.FirstName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_WithBodyAfterDelegation() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonThisBodyAfterDto(p.FirstName, p.LastName, false)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_WithIfElseInDelegated() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonThisIfElseDto(p.Score, "Grade:")); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_ChainedThisAndBase() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonChainChild(p.Id, p.FirstName, "-" + p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + } +} + diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt new file mode 100644 index 0000000..e8cf46e --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PointDto__ctor_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int x, int y) => new global::Foo.PointDto() + { + X = x, + Y = y + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt new file mode 100644 index 0000000..bd7f45c --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName, string lastName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = lastName + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt new file mode 100644 index 0000000..c2beff8 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string code) => new global::Foo.Child() + { + Code = code, + Label = "[" + (code) + "]" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt new file mode 100644 index 0000000..c2beff8 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string code) => new global::Foo.Child() + { + Code = code, + Label = "[" + (code) + "]" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt new file mode 100644 index 0000000..ea81d87 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int a, int b) => new global::Foo.Child() + { + X = a, + Y = a + b, + Sum = (a) + (a + b) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt new file mode 100644 index 0000000..ea81d87 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int a, int b) => new global::Foo.Child() + { + X = a, + Y = a + b, + Sum = (a) + (a + b) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt new file mode 100644 index 0000000..ab88351 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName, string lastName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = lastName, + FullName = (firstName) + " " + (lastName) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt new file mode 100644 index 0000000..ab88351 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName, string lastName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = lastName, + FullName = (firstName) + " " + (lastName) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt new file mode 100644 index 0000000..1bd1677 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = first + global::Foo.PersonDto.Separator + last + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt new file mode 100644 index 0000000..1bd1677 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = first + global::Foo.PersonDto.Separator + last + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt new file mode 100644 index 0000000..26be6b2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string_P2_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name, string suffix) => new global::Foo.Child() + { + Id = id, + Name = (name) + suffix + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt new file mode 100644 index 0000000..26be6b2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string_P2_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name, string suffix) => new global::Foo.Child() + { + Id = id, + Name = (name) + suffix + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt new file mode 100644 index 0000000..34cc6fd --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = "Doe", + FullName = (firstName) + " " + ("Doe") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt new file mode 100644 index 0000000..34cc6fd --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = "Doe", + FullName = (firstName) + " " + ("Doe") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt new file mode 100644 index 0000000..a03695d --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fullName) => new global::Foo.PersonDto() + { + FirstName = fullName.Split(' ')[0], + LastName = fullName.Split(' ')[1] + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt new file mode 100644 index 0000000..a03695d --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fullName) => new global::Foo.PersonDto() + { + FirstName = fullName.Split(' ')[0], + LastName = fullName.Split(' ')[1] + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt new file mode 100644 index 0000000..84c7f49 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fn, string ln, bool upper) => new global::Foo.PersonDto() + { + FirstName = fn, + LastName = ln, + FullName = upper ? ((fn) + " " + (ln)).ToUpper() : (fn) + " " + (ln) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt new file mode 100644 index 0000000..84c7f49 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fn, string ln, bool upper) => new global::Foo.PersonDto() + { + FirstName = fn, + LastName = ln, + FullName = upper ? ((fn) + " " + (ln)).ToUpper() : (fn) + " " + (ln) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt new file mode 100644 index 0000000..e5e1f53 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score, string prefix) => new global::Foo.PersonDto() + { + Score = score, + Label = prefix + (score >= 90 ? "A" : "B") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt new file mode 100644 index 0000000..e5e1f53 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score, string prefix) => new global::Foo.PersonDto() + { + Score = score, + Label = prefix + (score >= 90 ? "A" : "B") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt new file mode 100644 index 0000000..43c164c --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name) => new global::Foo.Child() + { + Id = id, + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt new file mode 100644 index 0000000..640a785 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name) => new global::Foo.Child() + { + Id = id < 0 ? 0 : id, + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt new file mode 100644 index 0000000..640a785 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name) => new global::Foo.Child() + { + Id = id < 0 ? 0 : id, + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt new file mode 100644 index 0000000..4079aac --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name, string rawCode) => new global::Foo.Child() + { + Code = rawCode.ToUpper(), + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt new file mode 100644 index 0000000..4079aac --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name, string rawCode) => new global::Foo.Child() + { + Code = rawCode.ToUpper(), + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt new file mode 100644 index 0000000..b91f020 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_Foo_SourceEntity + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.SourceEntity source) => new global::Foo.PersonDto() + { + Id = source.Id, + Name = source.Name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt new file mode 100644 index 0000000..1d391d0 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name) => new global::Foo.PersonDto() + { + Name = name + }; + } + } +} diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt new file mode 100644 index 0000000..59417ac --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name) => new global::Foo.PersonDto() + { + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt new file mode 100644 index 0000000..09491e5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Score = score, + Label = score >= 90 ? "A" : "B" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt new file mode 100644 index 0000000..09491e5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Score = score, + Label = score >= 90 ? "A" : "B" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt new file mode 100644 index 0000000..b485526 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Label = score >= 90 ? "A" : "none" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt new file mode 100644 index 0000000..b485526 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Label = score >= 90 ? "A" : "none" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt new file mode 100644 index 0000000..ac1d676 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = (first + " " + last) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt new file mode 100644 index 0000000..ac1d676 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = (first + " " + last) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt new file mode 100644 index 0000000..fa98281 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_Foo_NamePart_P1_Foo_NamePart + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.NamePart first, global::Foo.NamePart last) => new global::Foo.PersonDto() + { + FirstName = first.Value, + LastName = last.Value + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index 9ca78b1..a604861 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -3326,6 +3326,698 @@ public record Entity return Verifier.Verify(result.GeneratedTrees[0].ToString()); } + [Fact] + public Task ProjectableConstructor_BodyAssignments() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PointDto { + public int X { get; set; } + public int Y { get; set; } + + public PointDto() { } + + [Projectable] + public PointDto(int x, int y) { + X = x; + Y = y; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithBaseInitializer() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int Id { get; set; } + public Base(int id) { Id = id; } + + protected Base() { } + } + + class Child : Base { + public string Name { get; set; } + + public Child() { } + + [Projectable] + public Child(int id, string name) : base(id) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_Overloads() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonDto(string fullName) { + FirstName = fullName; + LastName = string.Empty; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Equal(2, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithClassArgument() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class SourceEntity { + public int Id { get; set; } + public string Name { get; set; } + } + + class PersonDto { + public int Id { get; set; } + public string Name { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(SourceEntity source) { + Id = source.Id; + Name = source.Name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithMultipleClassArguments() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class NamePart { + public string Value { get; set; } + } + + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(NamePart first, NamePart last) { + FirstName = first.Value; + LastName = last.Value; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithIfElseLogic() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Label { get; set; } + public int Score { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(int score) { + Score = score; + if (score >= 90) { + Label = ""A""; + } else { + Label = ""B""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithLocalVariable() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FullName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string first, string last) { + var full = first + "" "" + last; + FullName = full; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithBaseInitializerExpression() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public string Code { get; set; } + public Base(string code) { Code = code; } + + protected Base() { } + } + + class Child : Base { + public string Name { get; set; } + + public Child() { } + + [Projectable] + public Child(string name, string rawCode) : base(rawCode.ToUpper()) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithBaseInitializerAndIfElse() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int Id { get; set; } + public Base(int id) { + if (id < 0) { + Id = 0; + } else { + Id = id; + } + } + + protected Base() { } + } + + class Child : Base { + public string Name { get; set; } + + public Child() { } + + [Projectable] + public Child(int id, string name) : base(id) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithIfNoElse() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Label { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(int score) { + Label = ""none""; + if (score >= 90) { + Label = ""A""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingPreviouslyAssignedProperty() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + FullName = FirstName + "" "" + LastName; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingBasePropertyInDerivedBody() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public string Code { get; set; } + public Base(string code) { Code = code; } + + protected Base() { } + } + + class Child : Base { + public string Label { get; set; } + + public Child() { } + + [Projectable] + public Child(string code) : base(code) { + Label = ""["" + Code + ""]""; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingStaticConstMember() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + internal const string Separator = "" - ""; + public string FullName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string first, string last) { + FullName = first + Separator + last; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int X { get; set; } + public int Y { get; set; } + public Base(int x, int y) { + X = x; + Y = x + y; // Y depends on X's assigned value (x) + } + + protected Base() { } + } + + class Child : Base { + public int Sum { get; set; } + + public Child() { } + + [Projectable] + public Child(int a, int b) : base(a, b) { + Sum = X + Y; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_SimpleOverload() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonDto() { } + + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonDto(string fullName) : this(fullName.Split(' ')[0], fullName.Split(' ')[1]) { + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_WithBodyAfter() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonDto() { } + + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonDto(string fn, string ln, bool upper) : this(fn, ln) { + FullName = upper ? (FirstName + "" "" + LastName).ToUpper() : FirstName + "" "" + LastName; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_WithIfElseInDelegated() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Label { get; set; } + public int Score { get; set; } + + public PersonDto() { } + + public PersonDto(int score) { + Score = score; + if (score >= 90) { + Label = ""A""; + } else { + Label = ""B""; + } + } + + [Projectable] + public PersonDto(int score, string prefix) : this(score) { + Label = prefix + Label; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_ChainedThisAndBase() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int Id { get; set; } + public Base(int id) { Id = id; } + } + + class Child : Base { + public string Name { get; set; } + + public Child() : base(0) { } + + public Child(int id, string name) : base(id) { + Name = name; + } + + [Projectable] + public Child(int id, string name, string suffix) : this(id, name) { + Name = Name + suffix; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonDto() { } + + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + FullName = FirstName + "" "" + LastName; + } + + [Projectable] + public PersonDto(string firstName) : this(firstName, ""Doe"") { + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public void ProjectableConstructor_WithoutParameterlessConstructor_EmitsDiagnostic() + { + // A class that only exposes a parameterized constructor (no parameterless one). + // The generator must emit EFP0007 and produce no code because the object-initializer + // pattern requires a parameterless constructor. + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Name { get; set; } + + // No parameterless constructor – only the one marked [Projectable]. + [Projectable] + public PersonDto(string name) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal("EFP0007", diagnostic.Id); + Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); + Assert.Empty(result.GeneratedTrees); + } + + [Fact] + public Task ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds() + { + // A class that explicitly defines a parameterless constructor alongside the + // [Projectable] one – the generator should succeed and produce code. + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Name { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string name) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + [Fact] public Task ExplicitInterfaceMember() { From 84071058f009e87c40bbde554e51a720df07e4c1 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 16:37:02 +0100 Subject: [PATCH 02/11] Improve local variables handling --- .../ConstructorBodyConverter.cs | 70 ++++++++++++------- .../ProjectableInterpreter.cs | 35 +++++++--- 2 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs index 15618b0..41c524d 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs @@ -19,7 +19,6 @@ public class ConstructorBodyConverter private readonly SourceProductionContext _context; private readonly Func _rewrite; private readonly Dictionary _paramSubstitutions; - private readonly Dictionary _localVariables = new(); /// Creates a converter for the main constructor body. public ConstructorBodyConverter( @@ -47,7 +46,7 @@ public ConstructorBodyConverter( IReadOnlyDictionary? initialContext = null) { var assignments = new Dictionary(); - if (!TryProcessBlock(statements, assignments, memberName, outerContext: initialContext)) + if (!TryProcessBlock(statements, assignments, memberName, outerContext: initialContext, inheritedLocals: null)) { return null; } @@ -59,13 +58,21 @@ private bool TryProcessBlock( IEnumerable statements, Dictionary assignments, string memberName, - IReadOnlyDictionary? outerContext) + IReadOnlyDictionary? outerContext, + IReadOnlyDictionary? inheritedLocals = null) { + // Create a fresh local-variable scope for this block. + // Inherited locals (from parent scopes) are copied in so they remain visible here, + // but any new locals added inside this block won't escape to the caller's scope. + var blockLocals = inheritedLocals is { Count: > 0 } + ? inheritedLocals.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) + : new Dictionary(); + foreach (var statement in statements) { // Everything accumulated so far (from outer scope + this block) is visible. var visible = BuildVisible(outerContext, assignments); - if (!TryProcessStatement(statement, assignments, memberName, visible)) + if (!TryProcessStatement(statement, assignments, memberName, visible, blockLocals)) { return false; } @@ -77,22 +84,24 @@ private bool TryProcessStatement( StatementSyntax statement, Dictionary assignments, string memberName, - IReadOnlyDictionary? visibleContext) + IReadOnlyDictionary? visibleContext, + Dictionary currentLocals) { switch (statement) { case LocalDeclarationStatementSyntax localDecl: - return TryProcessLocalDeclaration(localDecl, memberName, visibleContext); + return TryProcessLocalDeclaration(localDecl, memberName, visibleContext, currentLocals); case ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax assignment } when assignment.IsKind(SyntaxKind.SimpleAssignmentExpression): - return TryProcessAssignment(assignment, assignments, memberName, visibleContext); + return TryProcessAssignment(assignment, assignments, memberName, visibleContext, currentLocals); case IfStatementSyntax ifStmt: - return TryProcessIfStatement(ifStmt, assignments, memberName, visibleContext); + return TryProcessIfStatement(ifStmt, assignments, memberName, visibleContext, currentLocals); case BlockSyntax block: - return TryProcessBlock(block.Statements, assignments, memberName, visibleContext); + // Nested block: inherit current locals but new locals declared inside won't escape. + return TryProcessBlock(block.Statements, assignments, memberName, visibleContext, currentLocals); default: ReportUnsupported(statement, memberName, @@ -105,7 +114,8 @@ when assignment.IsKind(SyntaxKind.SimpleAssignmentExpression): private bool TryProcessLocalDeclaration( LocalDeclarationStatementSyntax localDecl, string memberName, - IReadOnlyDictionary? visibleContext) + IReadOnlyDictionary? visibleContext, + Dictionary currentLocals) { foreach (var variable in localDecl.Declaration.Variables) { @@ -116,8 +126,9 @@ private bool TryProcessLocalDeclaration( } var rewritten = _rewrite(variable.Initializer.Value); - rewritten = ApplySubstitutions(rewritten, visibleContext); - _localVariables[variable.Identifier.Text] = rewritten; + rewritten = ApplySubstitutions(rewritten, visibleContext, currentLocals); + // Store in the current block scope; won't be visible outside this block. + currentLocals[variable.Identifier.Text] = rewritten; } return true; } @@ -126,7 +137,8 @@ private bool TryProcessAssignment( AssignmentExpressionSyntax assignment, Dictionary assignments, string memberName, - IReadOnlyDictionary? visibleContext) + IReadOnlyDictionary? visibleContext, + IReadOnlyDictionary currentLocals) { var targetMember = GetTargetMember(assignment.Left); if (targetMember is null) @@ -138,7 +150,7 @@ private bool TryProcessAssignment( } var rewritten = _rewrite(assignment.Right); - rewritten = ApplySubstitutions(rewritten, visibleContext); + rewritten = ApplySubstitutions(rewritten, visibleContext, currentLocals); assignments[targetMember.Identifier.Text] = rewritten; return true; } @@ -147,14 +159,17 @@ private bool TryProcessIfStatement( IfStatementSyntax ifStmt, Dictionary assignments, string memberName, - IReadOnlyDictionary? visibleContext) + IReadOnlyDictionary? visibleContext, + IReadOnlyDictionary currentLocals) { var condition = _rewrite(ifStmt.Condition); - condition = ApplySubstitutions(condition, visibleContext); + condition = ApplySubstitutions(condition, visibleContext, currentLocals); - // Each branch starts empty but can see the pre-if accumulated props (visibleContext). + // Each branch gets currentLocals as inherited context. TryProcessBlock will create + // its own block-scoped copy, so locals declared inside a branch won't leak into the + // sibling branch or into code that follows the if-statement. var thenAssignments = new Dictionary(); - if (!TryProcessBlock(GetStatements(ifStmt.Statement), thenAssignments, memberName, visibleContext)) + if (!TryProcessBlock(GetStatements(ifStmt.Statement), thenAssignments, memberName, visibleContext, currentLocals)) { return false; } @@ -162,7 +177,7 @@ private bool TryProcessIfStatement( var elseAssignments = new Dictionary(); if (ifStmt.Else != null) { - if (!TryProcessBlock(GetStatements(ifStmt.Else.Statement), elseAssignments, memberName, visibleContext)) + if (!TryProcessBlock(GetStatements(ifStmt.Else.Statement), elseAssignments, memberName, visibleContext, currentLocals)) { return false; } @@ -239,7 +254,7 @@ statement is BlockSyntax block IReadOnlyDictionary? outer, Dictionary local) { - bool outerEmpty = outer == null || outer.Count == 0; + var outerEmpty = outer == null || outer.Count == 0; var localEmpty = local.Count == 0; if (outerEmpty && localEmpty) @@ -273,16 +288,17 @@ statement is BlockSyntax block private ExpressionSyntax ApplySubstitutions( ExpressionSyntax expr, - IReadOnlyDictionary? visibleContext) + IReadOnlyDictionary? visibleContext, + IReadOnlyDictionary? currentLocals = null) { if (_paramSubstitutions.Count > 0) { expr = ParameterSubstitutor.Substitute(expr, _paramSubstitutions); } - if (_localVariables.Count > 0) + if (currentLocals != null && currentLocals.Count > 0) { - expr = LocalVariableSubstitutor.Substitute(expr, _localVariables); + expr = LocalVariableSubstitutor.Substitute(expr, currentLocals); } if (visibleContext != null && visibleContext.Count > 0) @@ -311,7 +327,7 @@ private void ReportUnsupported(SyntaxNode node, string memberName, string reason /// Replaces parameter-name identifier references with call-site argument expressions /// (used when inlining a delegated base/this constructor body). /// - internal sealed class ParameterSubstitutor : CSharpSyntaxRewriter + sealed internal class ParameterSubstitutor : CSharpSyntaxRewriter { private readonly Dictionary _map; private ParameterSubstitutor(Dictionary map) => _map = map; @@ -331,10 +347,10 @@ public static ExpressionSyntax Substitute(ExpressionSyntax expr, Dictionary private sealed class LocalVariableSubstitutor : CSharpSyntaxRewriter { - private readonly Dictionary _locals; - private LocalVariableSubstitutor(Dictionary locals) => _locals = locals; + private readonly IReadOnlyDictionary _locals; + private LocalVariableSubstitutor(IReadOnlyDictionary locals) => _locals = locals; - public static ExpressionSyntax Substitute(ExpressionSyntax expr, Dictionary locals) + public static ExpressionSyntax Substitute(ExpressionSyntax expr, IReadOnlyDictionary locals) => (ExpressionSyntax)new LocalVariableSubstitutor(locals).Visit(expr); public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs index bda392f..58550cd 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs @@ -486,7 +486,8 @@ x is IPropertySymbol xProperty && initializer.ArgumentList.Arguments, expressionSyntaxRewriter, context, - memberSymbol.Name); + memberSymbol.Name, + compilation); if (delegatedAssignments is null) { @@ -530,15 +531,22 @@ x is IPropertySymbol xProperty && return null; } - // Verify the containing type has a parameterless (instance) constructor. + // Verify the containing type has an accessible parameterless (instance) constructor. // The generated projection is: new T() { Prop = ... }, which requires one. // INamedTypeSymbol.Constructors covers all partial declarations and also // the implicit parameterless constructor that the compiler synthesizes when // no constructors are explicitly defined. - var hasParameterlessConstructor = containingType.Constructors - .Any(c => !c.IsStatic && c.Parameters.IsEmpty); - - if (!hasParameterlessConstructor) + // We also check accessibility: a private/protected parameterless ctor cannot be + // used in the generated projection class, so we require at least public, internal, + // or protected-internal visibility. + var hasAccessibleParameterlessConstructor = containingType.Constructors + .Any(c => !c.IsStatic + && c.Parameters.IsEmpty + && c.DeclaredAccessibility is Accessibility.Public + or Accessibility.Internal + or Accessibility.ProtectedOrInternal); + + if (!hasAccessibleParameterlessConstructor) { context.ReportDiagnostic(Diagnostic.Create( Diagnostics.MissingParameterlessConstructor, @@ -588,6 +596,7 @@ x is IPropertySymbol xProperty && ExpressionSyntaxRewriter expressionSyntaxRewriter, SourceProductionContext context, string memberName, + Compilation compilation, bool argsAlreadyRewritten = false) { // Only process constructors whose source is available in this compilation @@ -621,11 +630,12 @@ x is IPropertySymbol xProperty && if (syntax.Initializer is { } delegatedInitializer) { - // The delegated ctor's initializer is part of the original syntax tree, - // so we can safely use the semantic model to resolve its symbol. - var semanticModel = expressionSyntaxRewriter.GetSemanticModel(); + // Use the semantic model for the delegated constructor's own SyntaxTree. + // The original member's semantic model is bound to a different tree and + // would throw if the delegated/base ctor is declared in another file. + var delegatedCtorSemanticModel = compilation.GetSemanticModel(syntax.SyntaxTree); var delegatedInitializerSymbol = - semanticModel.GetSymbolInfo(delegatedInitializer).Symbol as IMethodSymbol; + delegatedCtorSemanticModel.GetSymbolInfo(delegatedInitializer).Symbol as IMethodSymbol; if (delegatedInitializerSymbol is not null) { @@ -640,13 +650,18 @@ x is IPropertySymbol xProperty && expressionSyntaxRewriter, context, memberName, + compilation, argsAlreadyRewritten: true); // args are now detached substituted nodes if (chainedAssignments is null) + { return null; + } foreach (var kvp in chainedAssignments) + { accumulated[kvp.Key] = kvp.Value; + } } } From fe32619b16d4a80cc1f15c3ead84d561730fb155 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 16:41:32 +0100 Subject: [PATCH 03/11] Fix filename too long --- .../ProjectionExpressionGeneratorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index a604861..bf74ae4 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -3927,7 +3927,7 @@ public Child(int id, string name, string suffix) : this(id, name) { } [Fact] - public Task ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty() + public Task ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty() { var compilation = CreateCompilation(@" using EntityFrameworkCore.Projectables; From 370fc5490b7ecf1b6fbfc3e7a971dc83d530cf82 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 16:43:28 +0100 Subject: [PATCH 04/11] Fix filename too long --- ...efPreviouslyAssignedProperty.verified.txt} | 2 +- ...ingPreviouslyAssignedProperty.verified.txt | 21 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) rename tests/EntityFrameworkCore.Projectables.Generator.Tests/{ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt => ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt} (96%) delete mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt similarity index 96% rename from tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt rename to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt index 34cc6fd..1a0b29d 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt deleted file mode 100644 index 34cc6fd..0000000 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt +++ /dev/null @@ -1,21 +0,0 @@ -// -#nullable disable -using EntityFrameworkCore.Projectables; -using Foo; - -namespace EntityFrameworkCore.Projectables.Generated -{ - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_PersonDto__ctor_P0_string - { - static global::System.Linq.Expressions.Expression> Expression() - { - return (string firstName) => new global::Foo.PersonDto() - { - FirstName = firstName, - LastName = "Doe", - FullName = (firstName) + " " + ("Doe") - }; - } - } -} \ No newline at end of file From bfc1f1419ab3937200dbf53ce62b9d41e8fb6afe Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 18:33:19 +0100 Subject: [PATCH 05/11] Increment diagnostic number --- .../AnalyzerReleases.Shipped.md | 3 ++- .../AnalyzerReleases.Unshipped.md | 7 +------ .../ProjectionExpressionGeneratorTests.cs | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md index 249da29..10e4476 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md +++ b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md @@ -8,7 +8,8 @@ EFP0003 | Design | Warning | Unsupported statement in block-bodied method EFP0004 | Design | Error | Statement with side effects in block-bodied method EFP0005 | Design | Warning | Potential side effect in block-bodied method EFP0006 | Design | Error | Method or property should expose a body definition (block or expression) -EFP0007 | Design | Error | Target class is missing a parameterless constructor +EFP0007 | Design | Error | Unsupported pattern in projectable expression +EFP0008 | Design | Error | Target class is missing a parameterless constructor ### Changed Rules diff --git a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md index 5f7773d..5f28270 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md +++ b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Unshipped.md @@ -1,6 +1 @@ -### New Rules - -Rule ID | Category | Severity | Notes ---------|----------|----------|----------------------------------------------- -EFP0007 | Design | Error | Unsupported pattern in projectable expression - + \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index 3176cd4..13862ee 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -4325,7 +4325,7 @@ public PersonDto(string firstName) : this(firstName, ""Doe"") { public void ProjectableConstructor_WithoutParameterlessConstructor_EmitsDiagnostic() { // A class that only exposes a parameterized constructor (no parameterless one). - // The generator must emit EFP0007 and produce no code because the object-initializer + // The generator must emit EFP0008 and produce no code because the object-initializer // pattern requires a parameterless constructor. var compilation = CreateCompilation(@" using EntityFrameworkCore.Projectables; @@ -4345,7 +4345,7 @@ public PersonDto(string name) { var result = RunGenerator(compilation); var diagnostic = Assert.Single(result.Diagnostics); - Assert.Equal("EFP0007", diagnostic.Id); + Assert.Equal("EFP0008", diagnostic.Id); Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); Assert.Empty(result.GeneratedTrees); } From 547cecb15444f858d8e1ecfc121003e0fe1c56d8 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 18:36:59 +0100 Subject: [PATCH 06/11] Apply review suggestions --- src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs | 2 +- .../Services/ProjectableExpressionReplacer.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs index adb71bc..6c765ca 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs @@ -63,7 +63,7 @@ public static class Diagnostics public static readonly DiagnosticDescriptor MissingParameterlessConstructor = new DiagnosticDescriptor( id: "EFP0008", title: "Target class is missing a parameterless constructor", - messageFormat: "Class '{0}' must have a parameterless constructor to be used with a [Projectable] constructor. The generated projection uses 'new {0}() {{ ... }}' (object-initializer syntax), which requires a publicly accessible parameterless constructor.", + messageFormat: "Class '{0}' must have a parameterless constructor to be used with a [Projectable] constructor. The generated projection uses 'new {0}() {{ ... }}' (object-initializer syntax), which requires an accessible parameterless constructor.", category: "Design", DiagnosticSeverity.Error, isEnabledByDefault: true); diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index 6d815e2..16e547a 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -224,12 +224,11 @@ protected override Expression VisitNew(NewExpression node) } var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body); - _expressionArgumentReplacer.ParameterArgumentMapping.Clear(); - return base.Visit(updatedBody); } finally { + _expressionArgumentReplacer.ParameterArgumentMapping.Clear(); _expandingConstructors.Remove(constructor); } } From 166389fdaeefd908704ffa3f77fe218f945b5aa4 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 19:05:32 +0100 Subject: [PATCH 07/11] Improve tests and better handle external constructors --- .../AnalyzerReleases.Shipped.md | 1 + .../Diagnostics.cs | 8 ++++++ .../ProjectableInterpreter.cs | 13 ++++++++- ...jectableConstructor_Overloads.verified.txt | 27 +++++++++++++++++-- .../ProjectionExpressionGeneratorTests.cs | 2 +- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md index 10e4476..75d4862 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md +++ b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md @@ -10,6 +10,7 @@ EFP0005 | Design | Warning | Potential side effect in block-bodied method EFP0006 | Design | Error | Method or property should expose a body definition (block or expression) EFP0007 | Design | Error | Unsupported pattern in projectable expression EFP0008 | Design | Error | Target class is missing a parameterless constructor +EFP0009 | Design | Error | Delegated constructor cannot be analyzed for projection ### Changed Rules diff --git a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs index 6c765ca..b09c3bc 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs @@ -68,5 +68,13 @@ public static class Diagnostics DiagnosticSeverity.Error, isEnabledByDefault: true); + public static readonly DiagnosticDescriptor NoSourceAvailableForDelegatedConstructor = new DiagnosticDescriptor( + id: "EFP0009", + title: "Delegated constructor cannot be analyzed for projection", + messageFormat: "The delegated constructor '{0}' in type '{1}' has no source available and cannot be analyzed. Base/this initializer in member '{2}' will not be projected.", + category: "Design", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + } } diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs index 58550cd..87702fd 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs @@ -607,7 +607,18 @@ or Accessibility.Internal if (syntax is null) { - return new Dictionary(); + // The delegated constructor is not available in source, so we cannot analyze + // its body or any assignments it performs. Treat this as an unsupported + // initializer: report a diagnostic and return null so callers can react. + var location = delegatedCtor.Locations.FirstOrDefault() ?? Location.None; + context.ReportDiagnostic( + Diagnostic.Create( + Diagnostics.NoSourceAvailableForDelegatedConstructor, + location, + delegatedCtor.ToDisplayString(), + delegatedCtor.ContainingType?.ToDisplayString() ?? "", + memberName)); + return null; } // Build a mapping: delegated-param-name → caller argument expression. diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt index bd7f45c..de2ed11 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt @@ -1,4 +1,5 @@ -// +[ +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; @@ -17,4 +18,26 @@ namespace EntityFrameworkCore.Projectables.Generated }; } } -} \ No newline at end of file +} + +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fullName) => new global::Foo.PersonDto() + { + FirstName = fullName, + LastName = string.Empty + }; + } + } +} +] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index 13862ee..00c5457 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -3782,7 +3782,7 @@ public PersonDto(string fullName) { Assert.Empty(result.Diagnostics); Assert.Equal(2, result.GeneratedTrees.Length); - return Verifier.Verify(result.GeneratedTrees[0].ToString()); + return Verifier.Verify(result.GeneratedTrees.OrderBy(t => t.FilePath).Select(t => t.ToString())); } [Fact] From 8784ba464f063f3be70ad8a113d03386323d0966 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 19:32:46 +0100 Subject: [PATCH 08/11] Add original source code in generated constructors, as comment --- .../ProjectionExpressionGenerator.cs | 33 +++++++++- ...leConstructor_BodyAssignments.verified.txt | 11 ++++ ...nstructor_Overloads.DotNet8_0.verified.txt | 65 +++++++++++++++++++ ...jectableConstructor_Overloads.verified.txt | 22 +++++++ ...opertyInDerivedBody.DotNet8_0.verified.txt | 12 +++- ...cingBasePropertyInDerivedBody.verified.txt | 10 +++ ...yAssignedInBaseCtor.DotNet8_0.verified.txt | 12 +++- ...gPreviouslyAssignedInBaseCtor.verified.txt | 10 +++ ...slyAssignedProperty.DotNet8_0.verified.txt | 14 +++- ...ingPreviouslyAssignedProperty.verified.txt | 12 ++++ ...ngStaticConstMember.DotNet8_0.verified.txt | 12 +++- ..._ReferencingStaticConstMember.verified.txt | 10 +++ ..._ChainedThisAndBase.DotNet8_0.verified.txt | 12 +++- ...nitializer_ChainedThisAndBase.verified.txt | 10 +++ ...slyAssignedProperty.DotNet8_0.verified.txt | 30 +++++++++ ...RefPreviouslyAssignedProperty.verified.txt | 9 +++ ...izer_SimpleOverload.DotNet8_0.verified.txt | 11 +++- ...hisInitializer_SimpleOverload.verified.txt | 9 +++ ...lizer_WithBodyAfter.DotNet8_0.verified.txt | 12 +++- ...ThisInitializer_WithBodyAfter.verified.txt | 10 +++ ...thIfElseInDelegated.DotNet8_0.verified.txt | 12 +++- ...ializer_WithIfElseInDelegated.verified.txt | 10 +++ ...WithBaseInitializer.DotNet8_0.verified.txt | 30 +++++++++ ...nstructor_WithBaseInitializer.verified.txt | 10 +++ ...nitializerAndIfElse.DotNet8_0.verified.txt | 12 +++- ..._WithBaseInitializerAndIfElse.verified.txt | 10 +++ ...itializerExpression.DotNet8_0.verified.txt | 12 +++- ...WithBaseInitializerExpression.verified.txt | 10 +++ ...r_WithClassArgument.DotNet8_0.verified.txt | 31 +++++++++ ...Constructor_WithClassArgument.verified.txt | 11 ++++ ...onstructor_Succeeds.DotNet8_0.verified.txt | 12 +++- ...meterlessConstructor_Succeeds.verified.txt | 10 +++ ...tor_WithIfElseLogic.DotNet8_0.verified.txt | 20 +++++- ...leConstructor_WithIfElseLogic.verified.txt | 18 +++++ ...ructor_WithIfNoElse.DotNet8_0.verified.txt | 16 ++++- ...tableConstructor_WithIfNoElse.verified.txt | 14 ++++ ...r_WithLocalVariable.DotNet8_0.verified.txt | 13 +++- ...Constructor_WithLocalVariable.verified.txt | 11 ++++ ...tipleClassArguments.DotNet8_0.verified.txt | 31 +++++++++ ...or_WithMultipleClassArguments.verified.txt | 11 ++++ 40 files changed, 615 insertions(+), 15 deletions(-) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs index abc89bd..ec2d137 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs @@ -1,4 +1,4 @@ -using EntityFrameworkCore.Projectables.Services; +using EntityFrameworkCore.Projectables.Services; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -49,6 +49,36 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (spc, source) => Execute(source.Item1, source.Item2, spc)); } + static SyntaxTriviaList BuildSourceDocComment(MemberDeclarationSyntax member) + { + var lines = new List(); + + void AddLine(string text) + { + lines.Add(Comment(text)); + lines.Add(CarriageReturnLineFeed); + } + + AddLine("/// "); + AddLine("/// Generated from:"); + AddLine("/// "); + + var originalSource = member.NormalizeWhitespace().ToFullString(); + foreach (var rawLine in originalSource.Split('\n')) + { + var lineText = rawLine.TrimEnd('\r') + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">"); + AddLine($"/// {lineText}"); + } + + AddLine("/// "); + AddLine("/// "); + + return TriviaList(lines); + } + static void Execute(MemberDeclarationSyntax member, Compilation compilation, SourceProductionContext context) { var projectable = ProjectableInterpreter.GetDescriptor(compilation, member, context); @@ -74,6 +104,7 @@ static void Execute(MemberDeclarationSyntax member, Compilation compilation, Sou AttributeList() .AddAttributes(_editorBrowsableAttribute) ) + .WithLeadingTrivia(member is ConstructorDeclarationSyntax ? BuildSourceDocComment(member) : TriviaList()) .AddMembers( MethodDeclaration( GenericName( diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt index e8cf46e..071c629 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt @@ -5,6 +5,17 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PointDto(int x, int y) + /// { + /// X = x; + /// Y = y; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PointDto__ctor_P0_int_P1_int { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.DotNet8_0.verified.txt new file mode 100644 index 0000000..5e9c8af --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.DotNet8_0.verified.txt @@ -0,0 +1,65 @@ +[ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName, string lastName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = lastName + }; + } + } +} + +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string fullName) + /// { + /// FirstName = fullName; + /// LastName = string.Empty; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fullName) => new global::Foo.PersonDto() + { + FirstName = fullName, + LastName = string.Empty + }; + } + } +} +] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt index de2ed11..5e9c8af 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt @@ -6,6 +6,17 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string { @@ -27,6 +38,17 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string fullName) + /// { + /// FirstName = fullName; + /// LastName = string.Empty; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt index c2beff8..ff76f6d 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(string code) : base(code) + /// { + /// Label = "[" + Code + "]"; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt index c2beff8..d1a635e 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(string code) : base(code) + /// { + /// Label = "[" + Code + "]"; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt index ea81d87..86e5cfd 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int a, int b) : base(a, b) + /// { + /// Sum = X + Y; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_int { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt index ea81d87..18009b5 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int a, int b) : base(a, b) + /// { + /// Sum = X + Y; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_int { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt index ab88351..e35d8f3 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -1,10 +1,22 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// FullName = FirstName + " " + LastName; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt index ab88351..c3af8ba 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt @@ -5,6 +5,18 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// FullName = FirstName + " " + LastName; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt index 1bd1677..527d55f 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string first, string last) + /// { + /// FullName = first + Separator + last; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt index 1bd1677..bb1416a 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string first, string last) + /// { + /// FullName = first + Separator + last; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt index 26be6b2..bec01a3 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int id, string name, string suffix) : this(id, name) + /// { + /// Name = Name + suffix; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string_P2_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt index 26be6b2..8b11a75 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int id, string name, string suffix) : this(id, name) + /// { + /// Name = Name + suffix; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string_P2_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt new file mode 100644 index 0000000..bc701e7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -0,0 +1,30 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string firstName) : this(firstName, "Doe") + /// { + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = "Doe", + FullName = (firstName) + " " + ("Doe") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt index 1a0b29d..bc701e7 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt @@ -5,6 +5,15 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string firstName) : this(firstName, "Doe") + /// { + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt index a03695d..f459847 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt @@ -1,10 +1,19 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string fullName) : this(fullName.Split(' ')[0], fullName.Split(' ')[1]) + /// { + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt index a03695d..1452911 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt @@ -5,6 +5,15 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string fullName) : this(fullName.Split(' ')[0], fullName.Split(' ')[1]) + /// { + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt index 84c7f49..8ffa1cf 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string fn, string ln, bool upper) : this(fn, ln) + /// { + /// FullName = upper ? (FirstName + " " + LastName).ToUpper() : FirstName + " " + LastName; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt index 84c7f49..cfa54d2 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string fn, string ln, bool upper) : this(fn, ln) + /// { + /// FullName = upper ? (FirstName + " " + LastName).ToUpper() : FirstName + " " + LastName; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt index e5e1f53..234aaea 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(int score, string prefix) : this(score) + /// { + /// Label = prefix + Label; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt index e5e1f53..2117c14 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(int score, string prefix) : this(score) + /// { + /// Label = prefix + Label; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt new file mode 100644 index 0000000..0a07935 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt @@ -0,0 +1,30 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int id, string name) : base(id) + /// { + /// Name = name; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name) => new global::Foo.Child() + { + Id = id, + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt index 43c164c..0a07935 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int id, string name) : base(id) + /// { + /// Name = name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt index 640a785..d1143d6 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int id, string name) : base(id) + /// { + /// Name = name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt index 640a785..0832096 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(int id, string name) : base(id) + /// { + /// Name = name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt index 4079aac..0dd5b8c 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt @@ -1,10 +1,20 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(string name, string rawCode) : base(rawCode.ToUpper()) + /// { + /// Name = name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt index 4079aac..99231b1 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public Child(string name, string rawCode) : base(rawCode.ToUpper()) + /// { + /// Name = name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt new file mode 100644 index 0000000..7c778d3 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(SourceEntity source) + /// { + /// Id = source.Id; + /// Name = source.Name; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_Foo_SourceEntity + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.SourceEntity source) => new global::Foo.PersonDto() + { + Id = source.Id, + Name = source.Name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt index b91f020..7c778d3 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt @@ -5,6 +5,17 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(SourceEntity source) + /// { + /// Id = source.Id; + /// Name = source.Name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_Foo_SourceEntity { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt index 1d391d0..c96479e 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string name) + /// { + /// Name = name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string { @@ -16,4 +26,4 @@ namespace EntityFrameworkCore.Projectables.Generated }; } } -} +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt index 59417ac..c96479e 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt @@ -5,6 +5,16 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string name) + /// { + /// Name = name; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt index 09491e5..5eeaf75 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt @@ -1,10 +1,28 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(int score) + /// { + /// Score = score; + /// if (score >= 90) + /// { + /// Label = "A"; + /// } + /// else + /// { + /// Label = "B"; + /// } + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt index 09491e5..02c183c 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt @@ -5,6 +5,24 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(int score) + /// { + /// Score = score; + /// if (score >= 90) + /// { + /// Label = "A"; + /// } + /// else + /// { + /// Label = "B"; + /// } + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt index b485526..3a469f0 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt @@ -1,10 +1,24 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(int score) + /// { + /// Label = "none"; + /// if (score >= 90) + /// { + /// Label = "A"; + /// } + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt index b485526..24a3e55 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt @@ -5,6 +5,20 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(int score) + /// { + /// Label = "none"; + /// if (score >= 90) + /// { + /// Label = "A"; + /// } + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt index ac1d676..d233058 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt @@ -1,10 +1,21 @@ -// +// #nullable disable using EntityFrameworkCore.Projectables; using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string first, string last) + /// { + /// var full = first + " " + last; + /// FullName = full; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt index ac1d676..bf93d90 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt @@ -5,6 +5,17 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(string first, string last) + /// { + /// var full = first + " " + last; + /// FullName = full; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt new file mode 100644 index 0000000..1838085 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(NamePart first, NamePart last) + /// { + /// FirstName = first.Value; + /// LastName = last.Value; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_Foo_NamePart_P1_Foo_NamePart + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.NamePart first, global::Foo.NamePart last) => new global::Foo.PersonDto() + { + FirstName = first.Value, + LastName = last.Value + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt index fa98281..1838085 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt @@ -5,6 +5,17 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { + /// + /// Generated from: + /// + /// [Projectable] + /// public PersonDto(NamePart first, NamePart last) + /// { + /// FirstName = first.Value; + /// LastName = last.Value; + /// } + /// + /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_Foo_NamePart_P1_Foo_NamePart { From 13a185a8bcb9a7e7e8358a1a98d6e2818c8a7862 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 19:41:50 +0100 Subject: [PATCH 09/11] Add all constructor chain --- .../ProjectionExpressionGenerator.cs | 63 +++++++++++++++---- ...opertyInDerivedBody.DotNet8_0.verified.txt | 6 ++ ...cingBasePropertyInDerivedBody.verified.txt | 6 ++ ...yAssignedInBaseCtor.DotNet8_0.verified.txt | 7 +++ ...gPreviouslyAssignedInBaseCtor.verified.txt | 7 +++ ..._ChainedThisAndBase.DotNet8_0.verified.txt | 12 ++++ ...nitializer_ChainedThisAndBase.verified.txt | 12 ++++ ...slyAssignedProperty.DotNet8_0.verified.txt | 8 +++ ...RefPreviouslyAssignedProperty.verified.txt | 8 +++ ...izer_SimpleOverload.DotNet8_0.verified.txt | 7 +++ ...hisInitializer_SimpleOverload.verified.txt | 7 +++ ...lizer_WithBodyAfter.DotNet8_0.verified.txt | 7 +++ ...ThisInitializer_WithBodyAfter.verified.txt | 7 +++ ...thIfElseInDelegated.DotNet8_0.verified.txt | 14 +++++ ...ializer_WithIfElseInDelegated.verified.txt | 14 +++++ ...WithBaseInitializer.DotNet8_0.verified.txt | 6 ++ ...nstructor_WithBaseInitializer.verified.txt | 6 ++ ...nitializerAndIfElse.DotNet8_0.verified.txt | 13 ++++ ..._WithBaseInitializerAndIfElse.verified.txt | 13 ++++ ...itializerExpression.DotNet8_0.verified.txt | 6 ++ ...WithBaseInitializerExpression.verified.txt | 6 ++ 21 files changed, 223 insertions(+), 12 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs index ec2d137..5d82c80 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs @@ -1,4 +1,4 @@ -using EntityFrameworkCore.Projectables.Services; +using EntityFrameworkCore.Projectables.Services; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -49,8 +49,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (spc, source) => Execute(source.Item1, source.Item2, spc)); } - static SyntaxTriviaList BuildSourceDocComment(MemberDeclarationSyntax member) + static SyntaxTriviaList BuildSourceDocComment(ConstructorDeclarationSyntax ctor, Compilation compilation) { + var chain = CollectConstructorChain(ctor, compilation); + var lines = new List(); void AddLine(string text) @@ -61,24 +63,61 @@ void AddLine(string text) AddLine("/// "); AddLine("/// Generated from:"); - AddLine("/// "); - var originalSource = member.NormalizeWhitespace().ToFullString(); - foreach (var rawLine in originalSource.Split('\n')) + foreach (var ctorSyntax in chain) { - var lineText = rawLine.TrimEnd('\r') - .Replace("&", "&") - .Replace("<", "<") - .Replace(">", ">"); - AddLine($"/// {lineText}"); + AddLine("/// "); + var originalSource = ctorSyntax.NormalizeWhitespace().ToFullString(); + foreach (var rawLine in originalSource.Split('\n')) + { + var lineText = rawLine.TrimEnd('\r') + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">"); + AddLine($"/// {lineText}"); + } + AddLine("/// "); } - AddLine("/// "); AddLine("/// "); return TriviaList(lines); } + /// + /// Collects the constructor and every constructor it delegates to via this(...) or + /// base(...), in declaration order (annotated constructor first, then its delegate, + /// then its delegate's delegate, …). Stops when a delegated constructor has no source + /// available in the compilation (e.g. a compiler-synthesised parameterless constructor). + /// + static IReadOnlyList CollectConstructorChain( + ConstructorDeclarationSyntax ctor, Compilation compilation) + { + var result = new List { ctor }; + var visited = new HashSet() { ctor }; + + var current = ctor; + while (current.Initializer is { } initializer) + { + var semanticModel = compilation.GetSemanticModel(current.SyntaxTree); + if (semanticModel.GetSymbolInfo(initializer).Symbol is not IMethodSymbol delegated) + break; + + var delegatedSyntax = delegated.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType() + .FirstOrDefault(); + + if (delegatedSyntax is null || !visited.Add(delegatedSyntax)) + break; + + result.Add(delegatedSyntax); + current = delegatedSyntax; + } + + return result; + } + static void Execute(MemberDeclarationSyntax member, Compilation compilation, SourceProductionContext context) { var projectable = ProjectableInterpreter.GetDescriptor(compilation, member, context); @@ -104,7 +143,7 @@ static void Execute(MemberDeclarationSyntax member, Compilation compilation, Sou AttributeList() .AddAttributes(_editorBrowsableAttribute) ) - .WithLeadingTrivia(member is ConstructorDeclarationSyntax ? BuildSourceDocComment(member) : TriviaList()) + .WithLeadingTrivia(member is ConstructorDeclarationSyntax ctor ? BuildSourceDocComment(ctor, compilation) : TriviaList()) .AddMembers( MethodDeclaration( GenericName( diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt index ff76f6d..e6e8efe 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt @@ -14,6 +14,12 @@ namespace EntityFrameworkCore.Projectables.Generated /// Label = "[" + Code + "]"; /// } /// + /// + /// public Base(string code) + /// { + /// Code = code; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt index d1a635e..6fe422b 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt @@ -14,6 +14,12 @@ namespace EntityFrameworkCore.Projectables.Generated /// Label = "[" + Code + "]"; /// } /// + /// + /// public Base(string code) + /// { + /// Code = code; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt index 86e5cfd..fc89a79 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt @@ -14,6 +14,13 @@ namespace EntityFrameworkCore.Projectables.Generated /// Sum = X + Y; /// } /// + /// + /// public Base(int x, int y) + /// { + /// X = x; + /// Y = x + y; // Y depends on X's assigned value (x) + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_int diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt index 18009b5..61fa3b3 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt @@ -14,6 +14,13 @@ namespace EntityFrameworkCore.Projectables.Generated /// Sum = X + Y; /// } /// + /// + /// public Base(int x, int y) + /// { + /// X = x; + /// Y = x + y; // Y depends on X's assigned value (x) + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_int diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt index bec01a3..9a7a460 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt @@ -14,6 +14,18 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = Name + suffix; /// } /// + /// + /// public Child(int id, string name) : base(id) + /// { + /// Name = name; + /// } + /// + /// + /// public Base(int id) + /// { + /// Id = id; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string_P2_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt index 8b11a75..28552c7 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt @@ -14,6 +14,18 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = Name + suffix; /// } /// + /// + /// public Child(int id, string name) : base(id) + /// { + /// Name = name; + /// } + /// + /// + /// public Base(int id) + /// { + /// Id = id; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string_P2_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt index bc701e7..b2fc637 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -13,6 +13,14 @@ namespace EntityFrameworkCore.Projectables.Generated /// { /// } /// + /// + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// FullName = FirstName + " " + LastName; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt index bc701e7..b2fc637 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_RefPreviouslyAssignedProperty.verified.txt @@ -13,6 +13,14 @@ namespace EntityFrameworkCore.Projectables.Generated /// { /// } /// + /// + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// FullName = FirstName + " " + LastName; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt index f459847..9d3ba77 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt @@ -13,6 +13,13 @@ namespace EntityFrameworkCore.Projectables.Generated /// { /// } /// + /// + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt index 1452911..3a246ec 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt @@ -13,6 +13,13 @@ namespace EntityFrameworkCore.Projectables.Generated /// { /// } /// + /// + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt index 8ffa1cf..95f8667 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt @@ -14,6 +14,13 @@ namespace EntityFrameworkCore.Projectables.Generated /// FullName = upper ? (FirstName + " " + LastName).ToUpper() : FirstName + " " + LastName; /// } /// + /// + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt index cfa54d2..ec11879 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt @@ -14,6 +14,13 @@ namespace EntityFrameworkCore.Projectables.Generated /// FullName = upper ? (FirstName + " " + LastName).ToUpper() : FirstName + " " + LastName; /// } /// + /// + /// public PersonDto(string firstName, string lastName) + /// { + /// FirstName = firstName; + /// LastName = lastName; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt index 234aaea..562bb9d 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt @@ -14,6 +14,20 @@ namespace EntityFrameworkCore.Projectables.Generated /// Label = prefix + Label; /// } /// + /// + /// public PersonDto(int score) + /// { + /// Score = score; + /// if (score >= 90) + /// { + /// Label = "A"; + /// } + /// else + /// { + /// Label = "B"; + /// } + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int_P1_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt index 2117c14..8051ed6 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt @@ -14,6 +14,20 @@ namespace EntityFrameworkCore.Projectables.Generated /// Label = prefix + Label; /// } /// + /// + /// public PersonDto(int score) + /// { + /// Score = score; + /// if (score >= 90) + /// { + /// Label = "A"; + /// } + /// else + /// { + /// Label = "B"; + /// } + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_PersonDto__ctor_P0_int_P1_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt index 0a07935..19736a6 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt @@ -14,6 +14,12 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = name; /// } /// + /// + /// public Base(int id) + /// { + /// Id = id; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt index 0a07935..19736a6 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt @@ -14,6 +14,12 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = name; /// } /// + /// + /// public Base(int id) + /// { + /// Id = id; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt index d1143d6..4ab74e1 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt @@ -14,6 +14,19 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = name; /// } /// + /// + /// public Base(int id) + /// { + /// if (id < 0) + /// { + /// Id = 0; + /// } + /// else + /// { + /// Id = id; + /// } + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt index 0832096..dad38a9 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt @@ -14,6 +14,19 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = name; /// } /// + /// + /// public Base(int id) + /// { + /// if (id < 0) + /// { + /// Id = 0; + /// } + /// else + /// { + /// Id = id; + /// } + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_int_P1_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt index 0dd5b8c..8856d78 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt @@ -14,6 +14,12 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = name; /// } /// + /// + /// public Base(string code) + /// { + /// Code = code; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string_P1_string diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt index 99231b1..d207be7 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt @@ -14,6 +14,12 @@ namespace EntityFrameworkCore.Projectables.Generated /// Name = name; /// } /// + /// + /// public Base(string code) + /// { + /// Code = code; + /// } + /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] static class Foo_Child__ctor_P0_string_P1_string From 6ed71e3b9e2d03a77dcb8968aef95553054c4ee5 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 21:04:52 +0100 Subject: [PATCH 10/11] Add more tests --- ...itializerExpression.DotNet8_0.verified.txt | 36 - ...lizer_AndIfElse_InDerivedBody.verified.txt | 45 ++ ...r_WithClassArgument.DotNet8_0.verified.txt | 31 - ...eConstructor_WithDeepNestedIf.verified.txt | 50 ++ ...eConstructor_WithElseIfChain.verified.txt} | 24 +- ...ithIfInsideLocalScope_AndElse.verified.txt | 42 ++ ...r_WithLocalVariable.DotNet8_0.verified.txt | 30 - ...hLocalVariableUsedInCondition.verified.txt | 42 ++ ...tipleClassArguments.DotNet8_0.verified.txt | 31 - ...or_WithMultipleLocalVariables.verified.txt | 35 + ...eConstructor_WithNestedIfElse.verified.txt | 43 ++ ...nstructor_WithNullCoalescing.verified.txt} | 18 +- ...ructor_WithNullableParameter.verified.txt} | 21 +- ...onstructor_WithSequentialIfs.verified.txt} | 29 +- ...structor_WithSwitchExpression.verified.txt | 37 ++ ...chExpression_AndExtraProperty.verified.txt | 39 ++ ...ructor_WithTernaryAssignment.verified.txt} | 14 +- ...isInitializer_AndElseIfInBody.verified.txt | 49 ++ .../ProjectionExpressionGeneratorTests.cs | 619 ++++++++++++++++++ 19 files changed, 1053 insertions(+), 182 deletions(-) delete mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer_AndIfElse_InDerivedBody.verified.txt delete mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithDeepNestedIf.verified.txt rename tests/EntityFrameworkCore.Projectables.Generator.Tests/{ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt => ProjectionExpressionGeneratorTests.ProjectableConstructor_WithElseIfChain.verified.txt} (54%) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfInsideLocalScope_AndElse.verified.txt delete mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariableUsedInCondition.verified.txt delete mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNestedIfElse.verified.txt rename tests/EntityFrameworkCore.Projectables.Generator.Tests/{ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt => ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullCoalescing.verified.txt} (53%) rename tests/EntityFrameworkCore.Projectables.Generator.Tests/{ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt => ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullableParameter.verified.txt} (56%) rename tests/EntityFrameworkCore.Projectables.Generator.Tests/{ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt => ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSequentialIfs.verified.txt} (51%) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression.verified.txt create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt rename tests/EntityFrameworkCore.Projectables.Generator.Tests/{ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt => ProjectionExpressionGeneratorTests.ProjectableConstructor_WithTernaryAssignment.verified.txt} (54%) create mode 100644 tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithThisInitializer_AndElseIfInBody.verified.txt diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt deleted file mode 100644 index 8856d78..0000000 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt +++ /dev/null @@ -1,36 +0,0 @@ -// -#nullable disable -using EntityFrameworkCore.Projectables; -using Foo; - -namespace EntityFrameworkCore.Projectables.Generated -{ - /// - /// Generated from: - /// - /// [Projectable] - /// public Child(string name, string rawCode) : base(rawCode.ToUpper()) - /// { - /// Name = name; - /// } - /// - /// - /// public Base(string code) - /// { - /// Code = code; - /// } - /// - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_Child__ctor_P0_string_P1_string - { - static global::System.Linq.Expressions.Expression> Expression() - { - return (string name, string rawCode) => new global::Foo.Child() - { - Code = rawCode.ToUpper(), - Name = name - }; - } - } -} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer_AndIfElse_InDerivedBody.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer_AndIfElse_InDerivedBody.verified.txt new file mode 100644 index 0000000..543c302 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer_AndIfElse_InDerivedBody.verified.txt @@ -0,0 +1,45 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public Pet(string species, string name, bool useShortName) : base(species) + /// { + /// Name = name; + /// if (useShortName) + /// { + /// Nickname = name.Length > 3 ? name.Substring(0, 3) : name; + /// } + /// else + /// { + /// Nickname = name; + /// } + /// } + /// + /// + /// public Animal(string species) + /// { + /// Species = species; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Pet__ctor_P0_string_P1_string_P2_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string species, string name, bool useShortName) => new global::Foo.Pet() + { + Species = species, + Name = name, + Nickname = useShortName ? name.Length > 3 ? name.Substring(0, 3) : name : name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt deleted file mode 100644 index 7c778d3..0000000 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.DotNet8_0.verified.txt +++ /dev/null @@ -1,31 +0,0 @@ -// -#nullable disable -using EntityFrameworkCore.Projectables; -using Foo; - -namespace EntityFrameworkCore.Projectables.Generated -{ - /// - /// Generated from: - /// - /// [Projectable] - /// public PersonDto(SourceEntity source) - /// { - /// Id = source.Id; - /// Name = source.Name; - /// } - /// - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_PersonDto__ctor_P0_Foo_SourceEntity - { - static global::System.Linq.Expressions.Expression> Expression() - { - return (global::Foo.SourceEntity source) => new global::Foo.PersonDto() - { - Id = source.Id, - Name = source.Name - }; - } - } -} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithDeepNestedIf.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithDeepNestedIf.verified.txt new file mode 100644 index 0000000..f0c4ffe --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithDeepNestedIf.verified.txt @@ -0,0 +1,50 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public AccessDto(bool isLoggedIn, bool isVerified, bool isAdmin) + /// { + /// if (isLoggedIn) + /// { + /// if (isVerified) + /// { + /// if (isAdmin) + /// { + /// Access = "Full"; + /// } + /// else + /// { + /// Access = "Verified"; + /// } + /// } + /// else + /// { + /// Access = "Unverified"; + /// } + /// } + /// else + /// { + /// Access = "Guest"; + /// } + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_AccessDto__ctor_P0_bool_P1_bool_P2_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (bool isLoggedIn, bool isVerified, bool isAdmin) => new global::Foo.AccessDto() + { + Access = isLoggedIn ? isVerified ? isAdmin ? "Full" : "Verified" : "Unverified" : "Guest" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithElseIfChain.verified.txt similarity index 54% rename from tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt rename to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithElseIfChain.verified.txt index 5eeaf75..2d3dcd8 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithElseIfChain.verified.txt @@ -9,29 +9,35 @@ namespace EntityFrameworkCore.Projectables.Generated /// Generated from: /// /// [Projectable] - /// public PersonDto(int score) + /// public GradeDto(int score) /// { - /// Score = score; /// if (score >= 90) /// { - /// Label = "A"; + /// Grade = "A"; + /// } + /// else if (score >= 75) + /// { + /// Grade = "B"; + /// } + /// else if (score >= 60) + /// { + /// Grade = "C"; /// } /// else /// { - /// Label = "B"; + /// Grade = "F"; /// } /// } /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_PersonDto__ctor_P0_int + static class Foo_GradeDto__ctor_P0_int { - static global::System.Linq.Expressions.Expression> Expression() + static global::System.Linq.Expressions.Expression> Expression() { - return (int score) => new global::Foo.PersonDto() + return (int score) => new global::Foo.GradeDto() { - Score = score, - Label = score >= 90 ? "A" : "B" + Grade = score >= 90 ? "A" : score >= 75 ? "B" : score >= 60 ? "C" : "F" }; } } diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfInsideLocalScope_AndElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfInsideLocalScope_AndElse.verified.txt new file mode 100644 index 0000000..1b5357a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfInsideLocalScope_AndElse.verified.txt @@ -0,0 +1,42 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public OrderDto(int amount, bool flagged) + /// { + /// if (flagged) + /// { + /// Status = "Flagged"; + /// Note = "Requires manual review"; + /// NeedsReview = true; + /// } + /// else + /// { + /// Status = amount > 1000 ? "Large" : "Normal"; + /// Note = string.Empty; + /// NeedsReview = false; + /// } + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_OrderDto__ctor_P0_int_P1_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int amount, bool flagged) => new global::Foo.OrderDto() + { + Status = flagged ? "Flagged" : amount > 1000 ? "Large" : "Normal", + Note = flagged ? "Requires manual review" : string.Empty, + NeedsReview = flagged ? true : false + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt deleted file mode 100644 index d233058..0000000 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt +++ /dev/null @@ -1,30 +0,0 @@ -// -#nullable disable -using EntityFrameworkCore.Projectables; -using Foo; - -namespace EntityFrameworkCore.Projectables.Generated -{ - /// - /// Generated from: - /// - /// [Projectable] - /// public PersonDto(string first, string last) - /// { - /// var full = first + " " + last; - /// FullName = full; - /// } - /// - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_PersonDto__ctor_P0_string_P1_string - { - static global::System.Linq.Expressions.Expression> Expression() - { - return (string first, string last) => new global::Foo.PersonDto() - { - FullName = (first + " " + last) - }; - } - } -} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariableUsedInCondition.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariableUsedInCondition.verified.txt new file mode 100644 index 0000000..8406207 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariableUsedInCondition.verified.txt @@ -0,0 +1,42 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public RangeDto(int a, int b) + /// { + /// var lo = a < b ? a : b; + /// var hi = a < b ? b : a; + /// Min = lo; + /// Max = hi; + /// if (hi - lo > 0) + /// { + /// IsValid = true; + /// } + /// else + /// { + /// IsValid = false; + /// } + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_RangeDto__ctor_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int a, int b) => new global::Foo.RangeDto() + { + Min = (a < b ? a : b), + Max = (a < b ? b : a), + IsValid = (a < b ? b : a) - (a < b ? a : b) > 0 ? true : false + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt deleted file mode 100644 index 1838085..0000000 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.DotNet8_0.verified.txt +++ /dev/null @@ -1,31 +0,0 @@ -// -#nullable disable -using EntityFrameworkCore.Projectables; -using Foo; - -namespace EntityFrameworkCore.Projectables.Generated -{ - /// - /// Generated from: - /// - /// [Projectable] - /// public PersonDto(NamePart first, NamePart last) - /// { - /// FirstName = first.Value; - /// LastName = last.Value; - /// } - /// - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_PersonDto__ctor_P0_Foo_NamePart_P1_Foo_NamePart - { - static global::System.Linq.Expressions.Expression> Expression() - { - return (global::Foo.NamePart first, global::Foo.NamePart last) => new global::Foo.PersonDto() - { - FirstName = first.Value, - LastName = last.Value - }; - } - } -} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt new file mode 100644 index 0000000..d156bca --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleLocalVariables.verified.txt @@ -0,0 +1,35 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public AddressDto(string street, string city, string country) + /// { + /// var trimmedStreet = street.Trim(); + /// var trimmedCity = city.Trim(); + /// Street = trimmedStreet; + /// City = trimmedCity; + /// Full = trimmedStreet + ", " + trimmedCity + ", " + country; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_AddressDto__ctor_P0_string_P1_string_P2_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string street, string city, string country) => new global::Foo.AddressDto() + { + Street = (street.Trim()), + City = (city.Trim()), + Full = (street.Trim()) + ", " + (city.Trim()) + ", " + country + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNestedIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNestedIfElse.verified.txt new file mode 100644 index 0000000..244a128 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNestedIfElse.verified.txt @@ -0,0 +1,43 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public StatusDto(bool isActive, bool isPremium) + /// { + /// if (isActive) + /// { + /// if (isPremium) + /// { + /// Status = "Active Premium"; + /// } + /// else + /// { + /// Status = "Active Free"; + /// } + /// } + /// else + /// { + /// Status = "Inactive"; + /// } + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_StatusDto__ctor_P0_bool_P1_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (bool isActive, bool isPremium) => new global::Foo.StatusDto() + { + Status = isActive ? isPremium ? "Active Premium" : "Active Free" : "Inactive" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullCoalescing.verified.txt similarity index 53% rename from tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt rename to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullCoalescing.verified.txt index 3a469f0..4845059 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullCoalescing.verified.txt @@ -9,24 +9,22 @@ namespace EntityFrameworkCore.Projectables.Generated /// Generated from: /// /// [Projectable] - /// public PersonDto(int score) + /// public ProductDto(string name, string description) /// { - /// Label = "none"; - /// if (score >= 90) - /// { - /// Label = "A"; - /// } + /// Name = name ?? "Unnamed"; + /// Description = description ?? string.Empty; /// } /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_PersonDto__ctor_P0_int + static class Foo_ProductDto__ctor_P0_string_P1_string { - static global::System.Linq.Expressions.Expression> Expression() + static global::System.Linq.Expressions.Expression> Expression() { - return (int score) => new global::Foo.PersonDto() + return (string name, string description) => new global::Foo.ProductDto() { - Label = score >= 90 ? "A" : "none" + Name = name ?? "Unnamed", + Description = description ?? string.Empty }; } } diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullableParameter.verified.txt similarity index 56% rename from tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt rename to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullableParameter.verified.txt index 19736a6..d4fa6b8 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithNullableParameter.verified.txt @@ -9,27 +9,22 @@ namespace EntityFrameworkCore.Projectables.Generated /// Generated from: /// /// [Projectable] - /// public Child(int id, string name) : base(id) + /// public MeasurementDto(double? value, string unit) /// { - /// Name = name; - /// } - /// - /// - /// public Base(int id) - /// { - /// Id = id; + /// Value = value ?? 0.0; + /// Unit = unit ?? "m"; /// } /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_Child__ctor_P0_int_P1_string + static class Foo_MeasurementDto__ctor_P0_double__P1_string { - static global::System.Linq.Expressions.Expression> Expression() + static global::System.Linq.Expressions.Expression> Expression() { - return (int id, string name) => new global::Foo.Child() + return (double? value, string unit) => new global::Foo.MeasurementDto() { - Id = id, - Name = name + Value = value ?? 0.0, + Unit = unit ?? "m" }; } } diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSequentialIfs.verified.txt similarity index 51% rename from tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt rename to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSequentialIfs.verified.txt index 4ab74e1..60ad67d 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSequentialIfs.verified.txt @@ -9,34 +9,31 @@ namespace EntityFrameworkCore.Projectables.Generated /// Generated from: /// /// [Projectable] - /// public Child(int id, string name) : base(id) + /// public FlagDto(string role, bool verified) /// { - /// Name = name; - /// } - /// - /// - /// public Base(int id) - /// { - /// if (id < 0) + /// Tag = role; + /// if (verified) /// { - /// Id = 0; + /// IsVerified = true; /// } - /// else + /// + /// if (role == "admin") /// { - /// Id = id; + /// IsAdmin = true; /// } /// } /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_Child__ctor_P0_int_P1_string + static class Foo_FlagDto__ctor_P0_string_P1_bool { - static global::System.Linq.Expressions.Expression> Expression() + static global::System.Linq.Expressions.Expression> Expression() { - return (int id, string name) => new global::Foo.Child() + return (string role, bool verified) => new global::Foo.FlagDto() { - Id = id < 0 ? 0 : id, - Name = name + Tag = role, + IsVerified = verified ? true : default, + IsAdmin = role == "admin" ? true : default }; } } diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression.verified.txt new file mode 100644 index 0000000..e46d106 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression.verified.txt @@ -0,0 +1,37 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public SeasonDto(int month) + /// { + /// Name = month switch + /// { + /// 12 or 1 or 2 => "Winter", + /// 3 or 4 or 5 => "Spring", + /// 6 or 7 or 8 => "Summer", + /// _ => "Autumn" + /// }; + /// Description = "Month: " + month; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_SeasonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int month) => new global::Foo.SeasonDto() + { + Name = month == 12 || month == 1 || month == 2 ? "Winter" : month == 3 || month == 4 || month == 5 ? "Spring" : month == 6 || month == 7 || month == 8 ? "Summer" : "Autumn", + Description = "Month: " + month + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt new file mode 100644 index 0000000..b3f3495 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithSwitchExpression_AndExtraProperty.verified.txt @@ -0,0 +1,39 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public ShapeDto(int sides) + /// { + /// Sides = sides; + /// ShapeType = sides switch + /// { + /// 3 => "Triangle", + /// 4 => "Rectangle", + /// 5 => "Pentagon", + /// _ => "Polygon" + /// }; + /// Description = ShapeType + " with " + sides + " sides"; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_ShapeDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int sides) => new global::Foo.ShapeDto() + { + Sides = sides, + ShapeType = sides == 3 ? "Triangle" : sides == 4 ? "Rectangle" : sides == 5 ? "Pentagon" : "Polygon", + Description = (sides == 3 ? "Triangle" : sides == 4 ? "Rectangle" : sides == 5 ? "Pentagon" : "Polygon") + " with " + sides + " sides" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithTernaryAssignment.verified.txt similarity index 54% rename from tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt rename to tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithTernaryAssignment.verified.txt index c96479e..1303eb1 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithTernaryAssignment.verified.txt @@ -9,20 +9,22 @@ namespace EntityFrameworkCore.Projectables.Generated /// Generated from: /// /// [Projectable] - /// public PersonDto(string name) + /// public LabelDto(string name, bool uppercase) /// { - /// Name = name; + /// Label = name; + /// Display = uppercase ? name.ToUpper() : name.ToLower(); /// } /// /// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_PersonDto__ctor_P0_string + static class Foo_LabelDto__ctor_P0_string_P1_bool { - static global::System.Linq.Expressions.Expression> Expression() + static global::System.Linq.Expressions.Expression> Expression() { - return (string name) => new global::Foo.PersonDto() + return (string name, bool uppercase) => new global::Foo.LabelDto() { - Name = name + Label = name, + Display = uppercase ? name.ToUpper() : name.ToLower() }; } } diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithThisInitializer_AndElseIfInBody.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithThisInitializer_AndElseIfInBody.verified.txt new file mode 100644 index 0000000..e16a5f3 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithThisInitializer_AndElseIfInBody.verified.txt @@ -0,0 +1,49 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + /// + /// Generated from: + /// + /// [Projectable] + /// public EventDto(string title, string tag, int urgency) : this(title, tag) + /// { + /// if (urgency >= 10) + /// { + /// Priority = "Critical"; + /// } + /// else if (urgency >= 5) + /// { + /// Priority = "High"; + /// } + /// else + /// { + /// Priority = "Normal"; + /// } + /// } + /// + /// + /// public EventDto(string title, string tag) + /// { + /// Title = title; + /// Tag = tag; + /// } + /// + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_EventDto__ctor_P0_string_P1_string_P2_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string title, string tag, int urgency) => new global::Foo.EventDto() + { + Title = title, + Tag = tag, + Priority = urgency >= 10 ? "Critical" : urgency >= 5 ? "High" : "Normal" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index 00c5457..e6a2af4 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -4379,6 +4379,625 @@ public PersonDto(string name) { return Verifier.Verify(result.GeneratedTrees[0].ToString()); } + // ----------------------------------------------------------------------- + // Additional constructor tests – if/else, early return, nested, etc. + // ----------------------------------------------------------------------- + + [Fact] + public Task ProjectableConstructor_WithElseIfChain() + { + // if / else-if / else – three branches + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class GradeDto { + public string Grade { get; set; } + + public GradeDto() { } + + [Projectable] + public GradeDto(int score) { + if (score >= 90) { + Grade = ""A""; + } else if (score >= 75) { + Grade = ""B""; + } else if (score >= 60) { + Grade = ""C""; + } else { + Grade = ""F""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithNestedIfElse() + { + // if inside else – nested branching + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class StatusDto { + public string Status { get; set; } + + public StatusDto() { } + + [Projectable] + public StatusDto(bool isActive, bool isPremium) { + if (isActive) { + if (isPremium) { + Status = ""Active Premium""; + } else { + Status = ""Active Free""; + } + } else { + Status = ""Inactive""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public void ProjectableConstructor_WithEarlyReturn_GuardClause_EmitsDiagnostic() + { + // Return statements inside a [Projectable] constructor body are not supported. + // The generator must emit EFP0003 and skip code generation for this member. + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class ItemDto { + public string Name { get; set; } + public string Category { get; set; } + + public ItemDto() { } + + [Projectable] + public ItemDto(string name, string category) { + Name = name; + if (string.IsNullOrEmpty(category)) { + Category = ""Unknown""; + return; + } + Category = category; + } + } +} +"); + var result = RunGenerator(compilation); + + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal("EFP0003", diagnostic.Id); + Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); + } + + [Fact] + public void ProjectableConstructor_WithMultipleEarlyReturns_EmitsDiagnostic() + { + // Multiple return statements inside a [Projectable] constructor body are not supported. + // The generator must emit EFP0003 on the first unsupported return encountered. + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PriorityDto { + public string Level { get; set; } + + public PriorityDto() { } + + [Projectable] + public PriorityDto(int value) { + if (value < 0) { + Level = ""Invalid""; + return; + } + if (value == 0) { + Level = ""None""; + return; + } + if (value <= 5) { + Level = ""Low""; + return; + } + Level = ""High""; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.NotEmpty(result.Diagnostics); + Assert.All(result.Diagnostics, d => Assert.Equal("EFP0003", d.Id)); + Assert.All(result.Diagnostics, d => Assert.Equal(DiagnosticSeverity.Warning, d.Severity)); + } + + [Fact] + public Task ProjectableConstructor_WithSequentialIfs() + { + // Multiple independent (non-else) if blocks that each may set a different property + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class FlagDto { + public string Tag { get; set; } + public bool IsVerified { get; set; } + public bool IsAdmin { get; set; } + + public FlagDto() { } + + [Projectable] + public FlagDto(string role, bool verified) { + Tag = role; + if (verified) { + IsVerified = true; + } + if (role == ""admin"") { + IsAdmin = true; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithTernaryAssignment() + { + // Ternary (conditional) expression used directly in property assignment + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class LabelDto { + public string Label { get; set; } + public string Display { get; set; } + + public LabelDto() { } + + [Projectable] + public LabelDto(string name, bool uppercase) { + Label = name; + Display = uppercase ? name.ToUpper() : name.ToLower(); + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithNullCoalescing() + { + // Null-coalescing operator (??) in property assignment + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class ProductDto { + public string Name { get; set; } + public string Description { get; set; } + + public ProductDto() { } + + [Projectable] + public ProductDto(string name, string description) { + Name = name ?? ""Unnamed""; + Description = description ?? string.Empty; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithSwitchExpression() + { + // Switch expression in property assignment + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class SeasonDto { + public string Name { get; set; } + public string Description { get; set; } + + public SeasonDto() { } + + [Projectable] + public SeasonDto(int month) { + Name = month switch { + 12 or 1 or 2 => ""Winter"", + 3 or 4 or 5 => ""Spring"", + 6 or 7 or 8 => ""Summer"", + _ => ""Autumn"" + }; + Description = ""Month: "" + month; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithMultipleLocalVariables() + { + // Several local variables used to build up property values + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class AddressDto { + public string Street { get; set; } + public string City { get; set; } + public string Full { get; set; } + + public AddressDto() { } + + [Projectable] + public AddressDto(string street, string city, string country) { + var trimmedStreet = street.Trim(); + var trimmedCity = city.Trim(); + Street = trimmedStreet; + City = trimmedCity; + Full = trimmedStreet + "", "" + trimmedCity + "", "" + country; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithNullableParameter() + { + // Nullable value-type parameter with null-coalescing default + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class MeasurementDto { + public double Value { get; set; } + public string Unit { get; set; } + + public MeasurementDto() { } + + [Projectable] + public MeasurementDto(double? value, string unit) { + Value = value ?? 0.0; + Unit = unit ?? ""m""; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithLocalVariableUsedInCondition() + { + // Local variable computed from params, then used inside an if + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class RangeDto { + public int Min { get; set; } + public int Max { get; set; } + public bool IsValid { get; set; } + + public RangeDto() { } + + [Projectable] + public RangeDto(int a, int b) { + var lo = a < b ? a : b; + var hi = a < b ? b : a; + Min = lo; + Max = hi; + if (hi - lo > 0) { + IsValid = true; + } else { + IsValid = false; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithBaseInitializer_AndIfElse_InDerivedBody() + { + // Derived constructor calls base AND has its own if/else logic + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Animal { + public string Species { get; set; } + public Animal(string species) { Species = species; } + protected Animal() { } + } + + class Pet : Animal { + public string Name { get; set; } + public string Nickname { get; set; } + + public Pet() { } + + [Projectable] + public Pet(string species, string name, bool useShortName) : base(species) { + Name = name; + if (useShortName) { + Nickname = name.Length > 3 ? name.Substring(0, 3) : name; + } else { + Nickname = name; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public void ProjectableConstructor_WithBaseInitializer_AndEarlyReturn_EmitsDiagnostic() + { + // A derived constructor that delegates to base() but uses an early return in its + // own body. Return statements are not supported → EFP0003 warning expected. + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Vehicle { + public string Type { get; set; } + public Vehicle(string type) { Type = type; } + protected Vehicle() { } + } + + class Car : Vehicle { + public string Model { get; set; } + public int Year { get; set; } + + public Car() { } + + [Projectable] + public Car(string model, int year) : base(""Car"") { + Model = model; + if (year <= 0) { + Year = 2000; + return; + } + Year = year; + } + } +} +"); + var result = RunGenerator(compilation); + + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal("EFP0003", diagnostic.Id); + Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); + } + + [Fact] + public Task ProjectableConstructor_WithDeepNestedIf() + { + // Three levels of nesting + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class AccessDto { + public string Access { get; set; } + + public AccessDto() { } + + [Projectable] + public AccessDto(bool isLoggedIn, bool isVerified, bool isAdmin) { + if (isLoggedIn) { + if (isVerified) { + if (isAdmin) { + Access = ""Full""; + } else { + Access = ""Verified""; + } + } else { + Access = ""Unverified""; + } + } else { + Access = ""Guest""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithIfInsideLocalScope_AndElse() + { + // if/else where both branches assign multiple properties + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class OrderDto { + public string Status { get; set; } + public string Note { get; set; } + public bool NeedsReview { get; set; } + + public OrderDto() { } + + [Projectable] + public OrderDto(int amount, bool flagged) { + if (flagged) { + Status = ""Flagged""; + Note = ""Requires manual review""; + NeedsReview = true; + } else { + Status = amount > 1000 ? ""Large"" : ""Normal""; + Note = string.Empty; + NeedsReview = false; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithThisInitializer_AndElseIfInBody() + { + // this() delegation followed by else-if logic in own body + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class EventDto { + public string Title { get; set; } + public string Tag { get; set; } + public string Priority { get; set; } + + public EventDto() { } + + public EventDto(string title, string tag) { + Title = title; + Tag = tag; + } + + [Projectable] + public EventDto(string title, string tag, int urgency) : this(title, tag) { + if (urgency >= 10) { + Priority = ""Critical""; + } else if (urgency >= 5) { + Priority = ""High""; + } else { + Priority = ""Normal""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithSwitchExpression_AndExtraProperty() + { + // Switch expression for one property, regular assignment for another + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class ShapeDto { + public string ShapeType { get; set; } + public int Sides { get; set; } + public string Description { get; set; } + + public ShapeDto() { } + + [Projectable] + public ShapeDto(int sides) { + Sides = sides; + ShapeType = sides switch { + 3 => ""Triangle"", + 4 => ""Rectangle"", + 5 => ""Pentagon"", + _ => ""Polygon"" + }; + Description = ShapeType + "" with "" + sides + "" sides""; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + [Fact] public Task ExplicitInterfaceMember() { From ff58238b177e942bec7abd2288331bb9141821a5 Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Sun, 1 Mar 2026 22:01:18 +0100 Subject: [PATCH 11/11] Remove BOM --- .../ProjectionExpressionGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs index 5d82c80..440caa5 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs @@ -1,4 +1,4 @@ -using EntityFrameworkCore.Projectables.Services; +using EntityFrameworkCore.Projectables.Services; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax;