Skip to content

feat(domain): add structured call semantics to CodeCall#45

Merged
phodal merged 1 commit intomasterfrom
feat/structured-call-semantics
Jan 19, 2026
Merged

feat(domain): add structured call semantics to CodeCall#45
phodal merged 1 commit intomasterfrom
feat/structured-call-semantics

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Jan 19, 2026

Summary

Motivation

This addresses the call semantics issues described in #41:

  • TS promise chains like axios.get().then().catch() can now be properly represented
  • Optional chaining (obj?.method()) is now trackable via IsOptional
  • Go/Rust fluent APIs can preserve the full call chain structure
  • Receiver types can be tracked for better type analysis

Changes

Field Type Description
ReceiverExpr String Original receiver expression text
ReceiverType CodeTypeRef? Resolved type of the receiver
IsOptional Boolean Whether this is optional chaining (TS ?.())
Chain List<String> Method names in chained calls
ChainArguments List<List<CodeProperty>> Arguments for each chain method
ReturnType CodeTypeRef? Return type of the call

Backward Compatibility

  • All new fields have default values
  • Legacy fields NodeName and OriginNodeName are preserved with documentation
  • Existing JSON serialization is not affected

Test plan

  • Added 9 new test cases covering TypeScript, Go, Rust patterns
  • All 16 tests pass
  • No linter errors

Refs #41

Summary by CodeRabbit

  • New Features

    • Enhanced support for method chaining and fluent API calls with improved receiver and return type tracking
    • Added optional chaining syntax detection capability
    • New utility methods for analyzing and navigating chained method calls
  • Tests

    • Added comprehensive test coverage for chained methods, optional chaining, and fluent APIs across multiple programming languages

✏️ Tip: You can customize this high-level summary in your review settings.

Add new fields to CodeCall for better cross-language call representation:
- ReceiverExpr: original receiver expression text
- ReceiverType: resolved type of the receiver (CodeTypeRef)
- IsOptional: support for TypeScript optional chaining (?.)
- Chain: method names in chained calls (fluent APIs, promise chains)
- ChainArguments: arguments for each method in the chain
- ReturnType: return type of the call

Add helper methods:
- allMethodNames(): get all method names including chain
- isChainedCall(): check if call has chain
- lastMethodName(): get last method in chain
- chainLength(): total number of method calls
- buildChainString(): string representation of full chain

This implements issue #41 section E (调用语义结构化) while maintaining
backward compatibility with existing NodeName and OriginNodeName fields.

Refs #41
Copilot AI review requested due to automatic review settings January 19, 2026 00:16
@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

The PR enhances the CodeCall data class with support for method chaining and cross-language call semantics by introducing six new fields (ReceiverExpr, ReceiverType, IsOptional, Chain, ChainArguments, ReturnType) and five helper methods for chain-aware logic, supplemented by comprehensive test coverage across diverse call patterns.

Changes

Cohort / File(s) Summary
CodeCall Core Enhancements
chapi-domain/src/main/kotlin/chapi/domain/core/CodeCall.kt
Added six public fields for cross-language call details (ReceiverExpr, ReceiverType, IsOptional, Chain, ChainArguments, ReturnType) and five helper methods (allMethodNames, isChainedCall, lastMethodName, chainLength, buildChainString) to support fluent API and method chaining patterns.
Test Coverage
chapi-domain/src/test/kotlin/chapi/domain/core/CodeCallTest.kt
Added eight new test methods covering TypeScript promise chains, optional chaining, Go fluent APIs, Rust async chains, simple function calls, chained arguments, legacy NodeName fallback behavior, and non-chained calls.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Hops through chains of shimmering calls,
ReceiverType within our halls,
Fluent APIs now take the stage,
CodeCall chains write a brilliant page!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(domain): add structured call semantics to CodeCall' accurately and concisely summarizes the main change—adding new structured fields and helper methods to the CodeCall class for cross-language call semantics.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the CodeCall domain model to better represent cross-language call semantics, specifically addressing Issue #41 Section E. The changes add structured fields for tracking receiver expressions, types, optional chaining, call chains, and return types.

Changes:

  • Added 6 new structured fields to CodeCall: ReceiverExpr, ReceiverType, IsOptional, Chain, ChainArguments, and ReturnType
  • Implemented 5 helper methods for chain analysis: allMethodNames(), isChainedCall(), lastMethodName(), chainLength(), and buildChainString()
  • Added comprehensive documentation with TypeScript, Go, and Rust examples

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
chapi-domain/src/main/kotlin/chapi/domain/core/CodeCall.kt Core implementation adding structured fields and helper methods with extensive documentation for cross-language call representation
chapi-domain/src/test/kotlin/chapi/domain/core/CodeCallTest.kt Comprehensive test suite with 9 new test cases covering TypeScript promise chains, optional chaining, Go fluent APIs, Rust async chains, and edge cases

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* ### Rust
* ```rust
* client.get(url).send().await?.json::<T>().await?
* // ReceiverExpr="client", FunctionName="get", Chain=["send", "await", "json", "await"]
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Chain field in this Rust documentation example shows ["send", "await", "json", "await"], but the corresponding test in CodeCallTest.kt (line 129) only uses ["send", "json"]. The documentation should be updated to match the test implementation, or the test should be updated to include the await methods if they should be part of the chain representation.

Suggested change
* // ReceiverExpr="client", FunctionName="get", Chain=["send", "await", "json", "await"]
* // ReceiverExpr="client", FunctionName="get", Chain=["send", "json"]

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +139
fun `should handle Rust async chain`() {
// client.get(url).send().await?.json::<T>().await?
val call = CodeCall(
ReceiverExpr = "client",
FunctionName = "get",
Chain = listOf("send", "json"),
ReceiverType = CodeTypeRef.simple("reqwest::Client"),
ReturnType = CodeTypeRef.generic("Result", CodeTypeRef.simple("T"), CodeTypeRef.simple("Error"))
)

assertEquals("client", call.ReceiverExpr)
assertEquals("get", call.FunctionName)
assertEquals(listOf("send", "json"), call.Chain)
assertEquals("reqwest::Client", call.ReceiverType?.name)
assertEquals(TypeRefKind.GENERIC, call.ReturnType?.kind)
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test validates the fields but doesn't assert the behavior of buildChainString() for this Rust async chain scenario. Consider adding an assertion like assertEquals("client.get().send().json()", call.buildChainString()) to ensure the chain representation is correct.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +154
fun `should handle simple function call without receiver`() {
// println("hello")
val call = CodeCall(
FunctionName = "println",
Parameters = listOf(CodeProperty(TypeValue = "\"hello\"", TypeType = "string"))
)

assertEquals("", call.ReceiverExpr)
assertEquals("println", call.FunctionName)
assertFalse(call.isChainedCall())
assertEquals(1, call.chainLength())
assertEquals("println", call.lastMethodName())
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test validates the fields but doesn't assert the behavior of buildChainString() for a simple function call without a receiver. Consider adding an assertion like assertEquals("println()", call.buildChainString()) to ensure standalone function calls are properly represented.

Copilot uses AI. Check for mistakes.
Comment on lines +193 to +199
fun `should handle all method names for non-chained call`() {
val call = CodeCall(FunctionName = "doSomething")

assertEquals(listOf("doSomething"), call.allMethodNames())
assertEquals("doSomething", call.lastMethodName())
assertFalse(call.isChainedCall())
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test validates allMethodNames() and lastMethodName() but doesn't assert the behavior of buildChainString() for non-chained calls. Consider adding an assertion like assertEquals("doSomething()", call.buildChainString()) to complete the test coverage.

Copilot uses AI. Check for mistakes.
@phodal phodal merged commit 30fb781 into master Jan 19, 2026
9 of 10 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@chapi-domain/src/main/kotlin/chapi/domain/core/CodeCall.kt`:
- Around line 243-246: buildChainString() drops the optional chaining operator
when IsOptional is true because it always inserts "." between the receiver and
the first method; change it to pick the separator based on IsOptional. In
buildChainString(), compute receiver using ReceiverExpr.ifEmpty { NodeName },
compute methods via allMethodNames().joinToString(".") { "$it()" }, then if
receiver.isNotEmpty() use a separator = if (IsOptional) "?." else "." and return
"$receiver$separator$methods" (otherwise return methods). This preserves "?. "
between receiver and the first call while keeping subsequent method joins as
".".
🧹 Nitpick comments (1)
chapi-domain/src/test/kotlin/chapi/domain/core/CodeCallTest.kt (1)

89-102: Add a buildChainString assertion for optional chaining.

This will protect the optional-chain formatting once the helper includes ?..

✅ Suggested test addition
         assertEquals("user?.profile", call.ReceiverExpr)
         assertEquals("getName", call.FunctionName)
         assertTrue(call.IsOptional)
         assertFalse(call.isChainedCall())
+        assertEquals("user?.profile?.getName()", call.buildChainString())

Comment on lines +243 to +246
fun buildChainString(): String {
val receiver = ReceiverExpr.ifEmpty { NodeName }
val methods = allMethodNames().joinToString(".") { "$it()" }
return if (receiver.isNotEmpty()) "$receiver.$methods" else methods
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve optional chaining in buildChainString().

When IsOptional is true, the rendered string currently drops the optional call operator (e.g., user?.profile.getName() instead of user?.profile?.getName()). Consider inserting ?. between receiver and the first call.

💡 Suggested update
-    fun buildChainString(): String {
-        val receiver = ReceiverExpr.ifEmpty { NodeName }
-        val methods = allMethodNames().joinToString(".") { "$it()" }
-        return if (receiver.isNotEmpty()) "$receiver.$methods" else methods
-    }
+    fun buildChainString(): String {
+        val receiver = ReceiverExpr.ifEmpty { NodeName }
+        val methods = allMethodNames().joinToString(".") { "$it()" }
+        val separator = if (IsOptional && receiver.isNotEmpty()) "?." else "."
+        return if (receiver.isNotEmpty()) "$receiver$separator$methods" else methods
+    }
🤖 Prompt for AI Agents
In `@chapi-domain/src/main/kotlin/chapi/domain/core/CodeCall.kt` around lines 243
- 246, buildChainString() drops the optional chaining operator when IsOptional
is true because it always inserts "." between the receiver and the first method;
change it to pick the separator based on IsOptional. In buildChainString(),
compute receiver using ReceiverExpr.ifEmpty { NodeName }, compute methods via
allMethodNames().joinToString(".") { "$it()" }, then if receiver.isNotEmpty()
use a separator = if (IsOptional) "?." else "." and return
"$receiver$separator$methods" (otherwise return methods). This preserves "?. "
between receiver and the first call while keeping subsequent method joins as
".".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants