From 519a8617717ddd88aed57ee5da052043606c0845 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 21:11:35 +0000 Subject: [PATCH] Allow CSharpInvocationStatement to be internally mutated to support method chaining. Co-authored-by: dandrejvv <7236289+dandrejvv@users.noreply.github.com> --- ...ent_ShouldMutateInternalChain.verified.txt | 14 +++++ ...ent_ShouldMutateInternalChain.verified.txt | 14 +++++ .../Builder/MethodStatementTests.cs | 52 ++++++++++++++++ .../Builder/CSharpInvocationStatement.cs | 61 ++++++++++++++++++- 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_CSharpStatement_ShouldMutateInternalChain.verified.txt create mode 100644 Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_ShouldMutateInternalChain.verified.txt diff --git a/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_CSharpStatement_ShouldMutateInternalChain.verified.txt b/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_CSharpStatement_ShouldMutateInternalChain.verified.txt new file mode 100644 index 000000000..ffab23fc4 --- /dev/null +++ b/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_CSharpStatement_ShouldMutateInternalChain.verified.txt @@ -0,0 +1,14 @@ +using System; + +[assembly: DefaultIntentManaged(Mode.Fully)] + +namespace Namespace +{ + public class Class + { + public static void Configure(Service service) + { + services.AddControllers().AddNewtonsoftJsonMergePatch(); + } + } +} \ No newline at end of file diff --git a/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_ShouldMutateInternalChain.verified.txt b/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_ShouldMutateInternalChain.verified.txt new file mode 100644 index 000000000..6a29dd513 --- /dev/null +++ b/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.InvocationStatement_AddStatement_ShouldMutateInternalChain.verified.txt @@ -0,0 +1,14 @@ +using System; + +[assembly: DefaultIntentManaged(Mode.Fully)] + +namespace Namespace +{ + public class Class + { + public static void Configure(Service service) + { + services.AddControllers(opt => {}).AddNewtonsoftJsonMergePatch(newJsonMergePatchOptions()); + } + } +} \ No newline at end of file diff --git a/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.cs b/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.cs index 226cf66bf..dd170d4d1 100644 --- a/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.cs +++ b/Modules/Intent.Modules.Common.CSharp.Tests/Builder/MethodStatementTests.cs @@ -7,6 +7,58 @@ namespace Intent.Modules.Common.CSharp.Tests.Builder; public class MethodStatementTests { + [Fact] + public async Task InvocationStatement_AddStatement_ShouldMutateInternalChain() + { + var fileBuilder = new CSharpFile("Namespace", "RelativeLocation") + .AddUsing("System") + .AddClass("Class", @class => + { + @class.AddMethod("void", "Configure", m => + { + m.Static(); + m.AddParameter("Service", "service"); + + var stmt = new CSharpInvocationStatement("services", "AddControllers") + .AddArgument("opt => {}"); + m.AddStatement(stmt); + + // We now use AddStatement to append to the chain natively + // This pushes AddControllers down into the inner expression, + // and wraps it with AddNewtonsoftJsonMergePatch. + stmt.AddStatement(new CSharpInvocationStatement("AddNewtonsoftJsonMergePatch") + .AddArgument("newJsonMergePatchOptions()")); + }); + }) + .CompleteBuild(); + + await Verifier.Verify(fileBuilder.ToString()); + } + + [Fact] + public async Task InvocationStatement_AddStatement_CSharpStatement_ShouldMutateInternalChain() + { + var fileBuilder = new CSharpFile("Namespace", "RelativeLocation") + .AddUsing("System") + .AddClass("Class", @class => + { + @class.AddMethod("void", "Configure", m => + { + m.Static(); + m.AddParameter("Service", "service"); + + var stmt = new CSharpInvocationStatement("services", "AddControllers"); + m.AddStatement(stmt); + + // Passing a CSharpStatement natively appends to the chain natively + stmt.AddStatement(new CSharpStatement("AddNewtonsoftJsonMergePatch")); + }); + }) + .CompleteBuild(); + + await Verifier.Verify(fileBuilder.ToString()); + } + [Fact] public async Task OneLineTernary() { diff --git a/Modules/Intent.Modules.Common.CSharp/Builder/CSharpInvocationStatement.cs b/Modules/Intent.Modules.Common.CSharp/Builder/CSharpInvocationStatement.cs index f91924473..aeb971e80 100644 --- a/Modules/Intent.Modules.Common.CSharp/Builder/CSharpInvocationStatement.cs +++ b/Modules/Intent.Modules.Common.CSharp/Builder/CSharpInvocationStatement.cs @@ -32,7 +32,7 @@ public CSharpInvocationStatement(ICSharpExpression expression) : this(expression { } - public ICSharpExpression Expression { get; } + public ICSharpExpression Expression { get; private set; } public IList Statements { get; } = new List(); @@ -155,5 +155,64 @@ private string GetAdditionalIndentationIfArgsOnNewLines() : " "; } + + + /// + /// Finds the given expression in the tree and replaces it with the provided new expression. + /// + public CSharpInvocationStatement FindAndReplaceExpression(ICSharpExpression newExpression) + { + this.Expression = newExpression; + return this; + } + + /// + /// Modifies this invocation statement to represent a chain of invocations, pushing the current invocation + /// down into the expression tree and adding the provided statement as the new top-level invocation. + /// This allows mutating an existing invocation statement without losing its reference or metadata. + /// + public CSharpInvocationStatement AddStatement(CSharpStatement statement) + { + var innerInvocation = new CSharpInvocationStatement(this.Expression); + var oldArgs = new System.Collections.Generic.List(this.Statements); + foreach (var arg in oldArgs) + { + innerInvocation.AddArgument(arg); + } + + this.Statements.Clear(); + + innerInvocation.WithoutSemicolon(); + var newExpression = new CSharpAccessMemberStatement(innerInvocation, statement); + FindAndReplaceExpression(newExpression); + return this; + } + + /// + /// Modifies this invocation statement to represent a chain of invocations, pushing the current invocation + /// down into the expression tree and adding the provided statement as the new top-level invocation. + /// This allows mutating an existing invocation statement without losing its reference or metadata. + /// + public CSharpInvocationStatement AddStatement(CSharpInvocationStatement statement) + { + var innerInvocation = new CSharpInvocationStatement(this.Expression); + var oldArgs = new System.Collections.Generic.List(this.Statements); + foreach (var arg in oldArgs) + { + innerInvocation.AddArgument(arg); + } + + this.Statements.Clear(); + + foreach (var arg in statement.Statements) + { + this.AddArgument(arg); + } + innerInvocation.WithoutSemicolon(); + var newExpression = new CSharpAccessMemberStatement(innerInvocation, statement.Expression as CSharpStatement ?? new CSharpStatement(statement.Expression.ToString())); + FindAndReplaceExpression(newExpression); + return this; + } + bool IHasCSharpStatementsActual.IsCodeBlock => false; } \ No newline at end of file