feat(domain): add structured call semantics to CodeCall#45
Conversation
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
📝 WalkthroughWalkthroughThe 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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
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, andReturnType - Implemented 5 helper methods for chain analysis:
allMethodNames(),isChainedCall(),lastMethodName(),chainLength(), andbuildChainString() - 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"] |
There was a problem hiding this comment.
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.
| * // ReceiverExpr="client", FunctionName="get", Chain=["send", "await", "json", "await"] | |
| * // ReceiverExpr="client", FunctionName="get", Chain=["send", "json"] |
| 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) | ||
| } |
There was a problem hiding this comment.
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.
| 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()) | ||
| } |
There was a problem hiding this comment.
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.
| 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()) | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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())
| fun buildChainString(): String { | ||
| val receiver = ReceiverExpr.ifEmpty { NodeName } | ||
| val methods = allMethodNames().joinToString(".") { "$it()" } | ||
| return if (receiver.isNotEmpty()) "$receiver.$methods" else methods |
There was a problem hiding this comment.
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
".".
Summary
CodeCallfor better cross-language call representation (Issue chapi-domain 核心模型:面向多语言 parser 的结构化改进(namespace/module、import/export、type、call、top-level) #41 Section E)ReceiverExpr,ReceiverType,IsOptional,Chain,ChainArguments,ReturnTypeallMethodNames(),isChainedCall(),lastMethodName(),chainLength(),buildChainString()Motivation
This addresses the call semantics issues described in #41:
axios.get().then().catch()can now be properly representedobj?.method()) is now trackable viaIsOptionalChanges
ReceiverExprStringReceiverTypeCodeTypeRef?IsOptionalBoolean?.())ChainList<String>ChainArgumentsList<List<CodeProperty>>ReturnTypeCodeTypeRef?Backward Compatibility
NodeNameandOriginNodeNameare preserved with documentationTest plan
Refs #41
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.