Skip to content

feat(group): add Kotlin Spring HTTP consumer extraction#1855

Merged
magyargergo merged 2 commits into
abhigyanpatwari:mainfrom
henry201605:feat/kotlin-spring-http-consumers
May 27, 2026
Merged

feat(group): add Kotlin Spring HTTP consumer extraction#1855
magyargergo merged 2 commits into
abhigyanpatwari:mainfrom
henry201605:feat/kotlin-spring-http-consumers

Conversation

@henry201605

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #1849 (Kotlin providers, merged) — extends http-patterns/kotlin.ts with three call-site patterns common in Kotlin Spring projects, completing 80% of real-world consumer detection coverage.

Framework Pattern Verb source
RestTemplate restTemplate.getForObject("/x", ...) etc. method name (getForObject → GET, postForEntity → POST, put → PUT, delete → DELETE, patchForObject → PATCH)
WebClient (short form) webClient.get().uri("/x") etc. inner navigation suffix (get/post/put/delete/patch)
OkHttp Request.Builder().url("/x") always GET (matches the Java plugin's heuristic)

The mappings (REST_TEMPLATE_TO_HTTP, WEB_CLIENT_SHORT_TO_HTTP) mirror the Java plugin so polyglot repos coalesce on the same http::VERB::/path contract IDs.

Out of scope

webClient.method(HttpMethod.X).uri("/y") long form is intentionally deferred. The verb sits on a sibling call_expression two hops away, so it needs walk-up logic rather than a flat tree-sitter query — that warrants its own focused PR. An anti-overreach test pins the current behavior so a future short-form change can't accidentally start matching the long form.

Why these queries are correct

The Kotlin AST shape for webClient.get().uri("/x") is:

call_expression                       ← outer .uri("/x")
  navigation_expression
    call_expression                   ← inner webClient.get()
      navigation_expression
        simple_identifier "webClient"
        navigation_suffix .get
      call_suffix ()
    navigation_suffix .uri
  call_suffix
    value_arguments
      value_argument . string_literal "/x"

The query anchors on the OUTER call (.uri(...)) and walks one level inward to constrain the inner suffix to a verb name. The leading . on value_argument ensures the path is the first positional argument.

OkHttp uses the same outer/inner shape but with Request / Builder / url constraints. RestTemplate is one level shallower (no chained verb hop).

Receiver-name constraints (and the trade-off)

#eq? @obj "restTemplate" and #eq? @cls "Request" match the Java plugin's heuristic. A project that aliases the receiver under a different name (val template = ...; template.getForObject(...)) won't be picked up. This keeps false-positive rates low and is documented in the file header. Same trade-off the Java plugin already accepts.

Tests

5 new cases under consumer extraction — fetch patterns, gated by tree-sitter-kotlin grammar availability (matches the Provider gating from #1849).

Positive (3)

  • RestTemplate verbs (5 calls × 5 verbs)
  • WebClient short-form verbs (5 calls × 5 verbs)
  • OkHttp Request.Builder().url("/x")

Anti-regression (2)

  • WebClient long form .method(HttpMethod.X) produces no consumer (deferred-feature pin)
  • non-restTemplate receiver does not match (receiver-name pin)

I sanity-checked the constraint the other way: removing (#eq? @obj "restTemplate") makes the receiver-name anti-regression test fail. Same shape as the validation done on #1834 and #1849.

Local validation

  • http-route-extractor.test.ts — 59 / 59 ✅
  • test/unit/group — 539 / 539 ✅
  • npm run format:check — clean ✅

Follow-up to abhigyanpatwari#1849 (Kotlin providers). Extends `http-patterns/kotlin.ts`
with three call-site patterns common in Kotlin Spring projects:

  - RestTemplate: `restTemplate.getForObject("/x", ...)` and the
    full verb family (getForObject/getForEntity → GET,
    postForObject/postForEntity → POST, put → PUT, delete → DELETE,
    patchForObject → PATCH). Mirrors the Java plugin's
    `REST_TEMPLATE_TO_HTTP` map so polyglot repos coalesce on a
    single contract id.

  - WebClient short form: `webClient.get().uri("/x")` and the
    `.post()` / `.put()` / `.delete()` / `.patch()` siblings. The
    chain parses as two nested `call_expression` nodes; the query
    anchors on the outer `.uri(...)` and walks one level inward
    to constrain the verb.

  - OkHttp: `Request.Builder().url("/x")`. Kotlin parses
    `Request.Builder()` as a `call_expression` whose callee is a
    `navigation_expression` (not Java's `object_creation_expression`),
    so the query shape differs from `java.ts` but the receiver/method
    constraints (`Request` / `Builder` / `url`) and emitted
    contract format match.

Out of scope: `webClient.method(HttpMethod.X).uri("/y")` long form.
The verb sits on a sibling `call_expression` two hops away, so it
needs a walk-up helper rather than a flat tree-sitter query. A
dedicated anti-overreach test pins the current behavior so a future
short-form change can't accidentally start matching the long form.

Receiver name constraints (`#eq? @obj "restTemplate"`,
`#eq? @cls "Request"`) match the Java plugin's heuristic — a project
that aliases the receiver under a different name won't be picked up.
This trade-off keeps false-positive rates low and is documented in
the file header.

Tests: 5 new cases under `consumer extraction — fetch patterns`,
gated by tree-sitter-kotlin grammar availability.

  positive (3)
   - RestTemplate verbs (5 calls × 5 verbs)
   - WebClient short-form verbs (5 calls × 5 verbs)
   - OkHttp Request.Builder().url("/x")
  anti-regression (2)
   - WebClient long form `.method(HttpMethod.X)` produces no
     consumer (deferred-feature pin)
   - non-restTemplate receiver does not match (receiver-name pin)

Reverse-validated: removing the `(#eq? @obj "restTemplate")`
constraint causes the receiver-name anti-regression test to fail.

Local validation:
  - test/unit/group/http-route-extractor.test.ts: 59/59 ✅
  - test/unit/group: 539/539 ✅
  - npm run format:check: clean ✅
@vercel

vercel Bot commented May 27, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the NexusCore Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
10005 10001 0 4 596s

✅ All 10001 tests passed

4 test(s) skipped — expand for details
  • PHP pipeline benchmark > scales with file count (workers enabled)
  • Ruby pipeline benchmark > scales with file count (workers enabled)
  • Rust pipeline benchmark > scales with file count (workers enabled)
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 79.73% 34809/43654 79.48% 📈 +0.2 🟢 ███████████████░░░░░
Branches 68.25% 22183/32499 68% 📈 +0.2 🟢 █████████████░░░░░░░
Functions 84.67% 3602/4254 84.56% 📈 +0.1 🟢 ████████████████░░░░
Lines 83.23% 31354/37668 82.97% 📈 +0.3 🟢 ████████████████░░░░

📋 View full run · Generated by CI

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 4m 9s —— View job


Adversarial Production-Readiness Review — PR #1855


Review Bar for This PR

Combined Kotlin + Spring Boot + tree-sitter + GitNexus contract integration review. Standard: correct AST matching, no new false positives, stable contract IDs, meaningful CI-verified tests, no hidden Unicode, focused scope.


Problem Being Solved

PR #1855 extends kotlin.ts (established in #1849) with three Kotlin HTTP consumer patterns: RestTemplate, WebClient short-form, and OkHttp Request.Builder().url(). It emits role: 'consumer' detections with http::METHOD::/path contract IDs matching the Java plugin so polyglot repos coalesce on a single contract key.


Current PR State

Open, one commit (57b8c9c), CI green (10000 tests passed, 4 skipped — the 4 are known benchmark and Ruby tests, not Kotlin). TypeScript typecheck clean. No conflicts visible.


Merge Status and Mergeability

CI bot reports all checks passed. npm ci in setup-gitnexus installs optionalDependencies including tree-sitter-kotlin ^0.3.8. The 4 CI-skipped tests match the known benchmarks and Ruby skip — not the 5 new Kotlin consumer tests — confirming tree-sitter-kotlin installed and the Kotlin tests executed on the Ubuntu runner.


Repository History Considered


Branch Hygiene Assessment

Clean. One feature commit, two causally related files. No CI workflows, Docker, release, UI, or dependency churn outside the stated scope.


Understanding of the Change

kotlin.ts gains three compiled pattern bundles (REST_TEMPLATE_PATTERNS, WEB_CLIENT_SHORT_PATTERNS, OK_HTTP_PATTERNS) inside the existing buildKotlinPlugin() factory, guarded by the same Kotlin grammar null-check. scan() gains three new loops that append HttpDetection objects with role: 'consumer'. The test file gains 5 Kotlin consumer tests (3 positive, 2 anti-overreach) inside the existing 'consumer extraction — fetch patterns' suite, gated by kotlinConsumerAvailable.


Findings

1. Kotlin/tree-sitter AST Correctness

RestTemplate query (kotlin.ts:241–248):

(call_expression
  (navigation_expression
    (simple_identifier) @obj (#eq? @obj "restTemplate")
    (navigation_suffix (simple_identifier) @method))
  (call_suffix
    (value_arguments . (value_argument . (string_literal) @path))))

The tree-sitter-kotlin (fwcd 0.3.8) AST for restTemplate.getForObject("/x", cls) is:

call_expression
  navigation_expression
    simple_identifier "restTemplate"
    navigation_suffix → simple_identifier "getForObject"
  call_suffix
    value_arguments
      value_argument → string_literal "/x"
      value_argument → class_reference

The anchor . before value_argument ensures the string_literal is the FIRST positional argument. This correctly fires on all 7 RestTemplate methods and skips unknown methods via the REST_TEMPLATE_TO_HTTP map lookup. ✅

WebClient short-form query (kotlin.ts:276–290):
The two-level nesting (webClient.get() inner, .uri(path) outer) is correctly modelled. The #match? @verb constraint restricts to exactly the 5 HTTP verb helpers. The #eq? @uri "uri" constraint prevents false matches on .header(), .retrieve(), etc. Tree-sitter matches sub-expressions at any depth, so .uri(...) embedded deeper in .retrieve().awaitBody<T>() chains is still found correctly. ✅

OkHttp query (kotlin.ts:310–321):
Request.Builder() in Kotlin IS a call_expression (via navigation_expression) rather than Java's object_creation_expression. The query correctly adapts the node types from java.ts. ✅

Potential AST edge case — call_suffix (value_arguments) for .get():
The inner pattern requires (call_suffix (value_arguments)) for the zero-argument webClient.get() call. In fwcd tree-sitter-kotlin, value_arguments is always present in call_suffix even when empty. This should work, and the passing CI confirms it. ✅

Named arguments not captured:
restTemplate.getForObject(url = "/api/users", responseType = User::class.java) would not match (named argument form puts simple_identifier "url" before string_literal, breaking the . (value_argument . (string_literal)) positional anchor). This is intentionally not handled — dynamic/named paths silently produce no output via unquoteLiteral returning null. Not a defect; consistent with the Kotlin provider pattern and documented AST coverage. ✅

this.webClient receiver:
this.webClient.get().uri("/x") would not match because the outer simple_identifier @obj would be this, not webClient. Same limitation as Java plugin. Documented trade-off. ✅


2. Spring Boot HTTP Client Semantics

RestTemplate mapping (kotlin.ts:87–95):
Maps getForObject, getForEntity, postForObject, postForEntity, put, delete, patchForObject. Exact parity with java.ts:119–127. Methods exchange, execute, postForLocation, headForHeaders, optionsForAllow are absent from both plugins. The if (!httpMethod) continue guard means unknown methods produce no output rather than crashing. Scope is intentionally narrow — not a defect, and Java parity is correct.

WebClient short form:
get/post/put/delete/patch on a WebClient instance returns a RequestHeadersUriSpec whose .uri() call carries the path. The verb is unambiguously deterministic from the method name alone. No false HTTP-method risk here. ✅

OkHttp GET limitationminor, non-blocking:

  • Risk: Request.Builder().url("/api/users").post(body).build() is a common non-GET OkHttp call. The query matches the .url(...) sub-expression inside this chain and would emit http::GET::/api/users — a false contract.
  • Evidence checked: OkHttp query at kotlin.ts:310–321. Test at line 1497–1521 only covers Request.Builder().url("/api/items").build() (no verb override). Java plugin (java.ts:174–189) has the same behavior and the same limitation is already in production.
  • Recommended fix: Add a documented negative test pinning the accepted limitation for .url("/x").post(body).build() chains, mirroring the anti-overreach style used for WebClient long-form. The limitation itself is acceptable given Java parity, but it is currently untested.
  • Blocks merge: No — explicitly documented as Java parity, accepted heuristic. Negative test is a follow-up.

3. GitNexus Contract Integration

HttpDetection at types.ts uses framework: string (open-ended, not an enum). spring-rest-template, spring-web-client, okhttp already appear in java.ts and downstream group-sync code. No new framework strings — no contract drift risk. ✅

role: 'consumer' is the correct HttpRole value. confidence: 0.7 matches Java consumer confidence. name: null is correct (call-site consumers have no handler name structurally). path is the raw literal from source, passed through unquoteLiteral which strips quotes. ✅

Plugin registration in index.ts:39–42: if (KOTLIN_HTTP_PLUGIN) guard is correct — the consumer scan runs inside the same buildKotlinPlugin() factory that provider scanning uses, so consumers are activated together with providers when the grammar is available. ✅

Contract ID format: the orchestrator (http-route-extractor.ts) normalizes paths into http::METHOD::/path — the plugin emits only the raw HttpDetection, not the final contract ID. No risk of malformed IDs from this PR. ✅


4. Tests and CI

5 new Kotlin consumer tests at http-route-extractor.test.ts:1413–1590:

  • extracts Kotlin RestTemplate verbs — 7 calls × 5 distinct method-to-verb mappings, asserts framework label spring-rest-template, count ≥ 5. ✅
  • extracts Kotlin WebClient short-form verbs — 5 calls × 5 verbs, asserts spring-web-client label. ✅
  • extracts Kotlin OkHttp Request.Builder().url(...) — asserts http::GET::/api/items and framework === 'okhttp'. ✅
  • does NOT match Kotlin WebClient long form — anti-overreach: webClient.method(HttpMethod.GET).uri(...) emits zero consumers. ✅
  • does NOT pick up unrelated string-literal calls on a non-restTemplate receiver — anti-regression: cacheClient.getForObject(...) emits zero consumers. ✅

All tests go through extractor.extract(null, dir, makeRepo(dir)) → real HttpRouteExtractorKOTLIN_HTTP_PLUGIN.scan(). Not isolated helper tests. ✅

CI evidence: 10000 tests passed, 4 skipped. The 4 listed skips are benchmarks + Ruby — not the 5 Kotlin consumer tests. The PR description reports 59/59 for http-route-extractor.test.ts. This confirms tree-sitter-kotlin installed on the Ubuntu runner and all Kotlin tests executed. ✅

Grammar availability gating — minor concern:

  • Risk: const itKotlinConsumer = kotlinConsumerAvailable ? it : it.skip silently skips all 5 Kotlin consumer tests if tree-sitter-kotlin fails to install in a future CI run, without any loud signal.
  • Evidence checked: package.json:94,97tree-sitter-kotlin is in optionalDependencies. setup-gitnexus action runs plain npm ci which installs optionalDependencies when the native build toolchain is present. Currently works on Ubuntu. If the grammar becomes unavailable (npm registry, build toolchain change), CI would still pass green with 5 more silently skipped tests.
  • Recommended fix: Add a CI assertion (e.g., if (!kotlinConsumerAvailable) throw new Error(...) in a non-skippable test, or a dedicated CI step) to make grammar availability failures loud. Same gap exists in the provider tests from feat(group): add Kotlin Spring HTTP route extraction (named + positional) #1849; if this was accepted there, it can be deferred here too.
  • Blocks merge: No — current CI run confirmed the tests executed. Same gating pattern as provider tests from feat(group): add Kotlin Spring HTTP route extraction (named + positional) #1849. Minor follow-up.

5. Hidden Unicode and Hygiene

grep -Pn '[\x{202A}-\x{202E}\x{2066}-\x{2069}]' kotlin.ts http-route-extractor.test.ts
→ No bidi chars found ✅

Non-ASCII characters found: (em dash), , in inline comments. These are cosmetic ASCII art used throughout the existing kotlin.ts file and consistent with repo style (same in java.ts and existing test file). Not hidden/bidi controls. ✅

git diff --check is unavailable here (no origin/main branch in the runner), but the hygiene scan is clean.


6. Branch Scope and Unrelated Churn

Diff is +420/-9 across exactly 2 files: kotlin.ts and http-route-extractor.test.ts. The -9 deletions in kotlin.ts are dead comment lines from before this PR. No workflow, release, Docker, UI, or dependency changes. Focused.


Back-and-Forth Avoided by Verifying

  • OkHttp POST-chain false positive: confirmed accepted limitation with Java parity documentation; no merge block.
  • Receiver-name constraints: confirmed both sides — anti-regression test for non-restTemplate receiver; documented trade-off for non-conventional injection names.
  • Missing RestTemplate methods (exchange, execute, etc.): confirmed Java parity — java.ts has the same 7-method mapping; unknown methods silently skip.
  • WebClient long-form: confirmed anti-overreach test is present and correct.
  • Grammar gating: confirmed tree-sitter-kotlin installed and Kotlin tests ran in CI via skip count evidence.
  • framework labels downstream: confirmed framework: string is open-ended and existing labels already used in java.ts.

Open Questions

None that block merge.


Final Verdict

production-ready with minor follow-ups

The implementation is correct for its stated scope: AST queries are well-formed for the fwcd tree-sitter-kotlin grammar, all three consumer patterns are faithfully adapted from the Java plugin with documented receiver-name trade-offs, contract IDs and HttpDetection fields are correctly formed, and all 5 tests exercise the real HttpRouteExtractor path and executed in CI. The two follow-ups are: (1) a negative test pinning the accepted OkHttp POST-chain false-positive behavior, mirroring the anti-overreach style already used for WebClient long-form; and (2) a loud CI gate if tree-sitter-kotlin grammar becomes unavailable, preventing silent test-skip regressions. Both are quality-of-life improvements consistent with the DoD.md §2.7 bar ("assertions are meaningful") rather than correctness failures. The PR is clean, focused, and demonstrates the same rigor as #1849 and #1834 before it.


@magyargergo

Copy link
Copy Markdown
Collaborator

@henry201605 could you please double check these findings before I merge it in? 🙏

Address Claude review on PR abhigyanpatwari#1855 (Finding 1).

The OkHttp query in `kotlin.ts:OK_HTTP_PATTERNS` matches the
`.url("/x")` sub-expression of a builder chain, but the verb is
encoded on a separate sibling call (`.post(body)` / `.delete()` /
...). The query intentionally does not walk the chain to recover
the verb — it emits `method: 'GET'` for every match, mirroring the
Java plugin's `OK_HTTP_PATTERNS` (java.ts).

Concretely: `Request.Builder().url("/x").post(body).build()` becomes
`http::GET::/x`, not `http::POST::/x`. This is an already-accepted
Java parity heuristic, but it was untested on the Kotlin side.

This commit:
  - Adds an anti-overreach test pinning the current behavior:
      * exactly one consumer is emitted with method=GET
      * no second http::POST::/x consumer appears
  - Documents the limitation in kotlin.ts as a "Known limitation"
    block tied to the test, so a future verb-walk implementation
    has to update the comment in lockstep with the assertion.

Rationale for not implementing verb-walk in this PR:
  - Verb-walk requires walking sibling call_expression nodes (the
    `.post(body)` chain), which is the same shape as the
    deferred WebClient long-form work
  - Java has the same limitation in production today; fixing only
    Kotlin would create polyglot drift
  - A coordinated future PR can add verb-walk to both plugins at
    once and update both comments + the pin tests together

Finding 2 (silent test-skip when tree-sitter-kotlin grammar is
unavailable) is intentionally NOT addressed here — same gating
pattern was accepted in abhigyanpatwari#1849 for Provider tests, and a coordinated
follow-up should add a CI sentinel covering both Provider and
Consumer suites in one place.

Local validation:
  - test/unit/group/http-route-extractor.test.ts: 60/60 ✅
  - test/unit/group: 540/540 ✅
  - npm run format:check: clean ✅
@henry201605

Copy link
Copy Markdown
Contributor Author

@magyargergo Thanks for the careful review! 🙏 Went through both findings — quick notes below.

Finding 1 (OkHttp POST-chain heuristic-default GET) — addressed in commit 652e324f. Added an anti-overreach test pinning the current Java-parity behavior (Request.Builder().url("/x").post(body).build() → exactly one http::GET::/x consumer, no spurious POST), plus a "Known limitation" block in kotlin.ts tied to the test. If a future verb-walk implementation lands, the test goes red and forces both kotlin.ts and java.ts to be updated in lockstep.

Finding 2 (silent test-skip when tree-sitter-kotlin is unavailable) — intentionally deferred. The same it.skip gating pattern was accepted in #1849 for the Provider tests, and the cleanest fix covers both Provider + Consumer suites in one place rather than asymmetrically here. Happy to open a follow-up issue/PR adding a CI sentinel that asserts grammar availability before any itKotlin*/itKotlinConsumer runs — let me know if you'd like me to scope that next.

Local validation after the new commit:

  • http-route-extractor.test.ts — 60 / 60 ✅
  • test/unit/group — 540 / 540 ✅
  • npm run format:check — clean ✅

Happy to make any further adjustments!

@magyargergo magyargergo merged commit d9d6318 into abhigyanpatwari:main May 27, 2026
26 of 27 checks passed
henry201605 pushed a commit to henry201605/GitNexus that referenced this pull request May 29, 2026
…atwari#1884

Two minor follow-ups from the production-readiness review:

F1 — Stale block comment at the top of the Kotlin consumer suite
(was: "Three consumer flavors covered here ... long-form deferred
to a follow-up"). Updated to "Four consumer flavors" and removed
the deferred sentence — the deferral is resolved by this PR. The
kotlin.ts file header was already updated; this brings the test
file comment in sync. Per DoD §2.3 (no stale comments).

F2 — Replaced `expect(wcConsumers.length).toBeGreaterThanOrEqual(4)`
with `expect(wcConsumers).toHaveLength(4)` in the multi-verb test.
The fixture is fully deterministic — exactly 4 long-form calls,
no other consumer types — so an exact count assertion is the right
shape per DoD §2.7 ("use toBe / toEqual for exact expectations").
Added a comment explaining what the assertion catches that the
existing per-verb toBeDefined() checks would miss (accidental 5th
consumer from a duplicate query firing or a regressed receiver
constraint).

F3 (HEAD/OPTIONS/TRACE negative test) is intentionally not added
in this PR — same precedent as abhigyanpatwari#1855 where HEAD/OPTIONS/TRACE on
the short form are also implicitly excluded without a pinning
test. Happy to add one in a separate PR if maintainers want
explicit pinning across both forms.

F4 (CI on pre-merge SHA) is the maintainer's call — the merge from
main is theirs to re-trigger CI on. The merge brings only Java
consumer changes (PR abhigyanpatwari#1872) and Go provider changes (PR abhigyanpatwari#1886),
both in entirely separate files from this PR's Kotlin work.

Local validation:
  - test/unit/group/http-route-extractor.test.ts: 73/73 ✅
    (66 from this PR pre-merge + 7 from PR abhigyanpatwari#1872 merged via main)
  - npx prettier --check (changed files): clean ✅
magyargergo added a commit that referenced this pull request May 29, 2026
…action (#1884)

* feat(group): add Kotlin Spring WebClient long-form HTTP consumer extraction

Follow-up to #1855. Extends `kotlin.ts` with the long-form WebClient
fluent chain that #1855 explicitly deferred:

  webClient.method(HttpMethod.GET).uri("/x").retrieve().awaitBody<T>()

This pattern remains common in Kotlin Spring 4 → 5 migrations and in
codebases that prefer the fluent verb-as-enum style. The short form
(`webClient.get().uri("/x")`) was already supported in #1855.

Approach:
  - Single deeper tree-sitter query (`WEB_CLIENT_LONG_PATTERNS`) that
    matches the full chain structurally — both `.method(HttpMethod.X)`
    and `.uri("...")` in one pattern. Verb is captured as the
    `simple_identifier` of the `HttpMethod.X` field access.
  - Verb is whitelisted to GET/POST/PUT/DELETE/PATCH (consistent with
    the short-form's `WEB_CLIENT_SHORT_TO_HTTP` map).
  - Receiver constraint `(#eq? @obj "webClient")` mirrors the short
    form and Java plugin heuristic.

Out of scope (intentional):
  - Variable-bound verbs: `val verb = HttpMethod.PATCH; webClient.method(verb)...`
    Source-scan can't follow the binding without graph context.
    Pinned by an anti-overreach test.
  - HEAD/OPTIONS/TRACE: not in `WEB_CLIENT_SHORT_TO_HTTP` either —
    keeps polyglot symmetry with java.ts and the short form.

Tests: 4 new cases under `consumer extraction — fetch patterns`,
gated by tree-sitter-kotlin grammar availability.

  positive (3)
   - long form GET
   - long form POST / PUT / DELETE / PATCH (4 verbs in 1 fixture)
   - no double-emit pin (long-form chain produces exactly one
     consumer, not one from each query)
  anti-regression (1)
   - variable-bound verb does NOT match (graph-aware concern)

The previous `'does NOT match Kotlin WebClient long form (deferred
to follow-up)'` test from #1855 is replaced by these — the deferred
state is now resolved.

Reverse-validated: temporarily disabling the long-form emit makes
exactly the 3 positive tests fail; the variable-bound-verb anti-
regression test continues to pass (it pins behavior independent
of the emit being on or off).

Local validation:
  - test/unit/group/http-route-extractor.test.ts: 66/66 ✅
  - test/unit/group: 546/546 ✅
  - npx prettier --check (changed files): clean ✅

* test(group): address Claude review findings F1 and F2 on PR #1884

Two minor follow-ups from the production-readiness review:

F1 — Stale block comment at the top of the Kotlin consumer suite
(was: "Three consumer flavors covered here ... long-form deferred
to a follow-up"). Updated to "Four consumer flavors" and removed
the deferred sentence — the deferral is resolved by this PR. The
kotlin.ts file header was already updated; this brings the test
file comment in sync. Per DoD §2.3 (no stale comments).

F2 — Replaced `expect(wcConsumers.length).toBeGreaterThanOrEqual(4)`
with `expect(wcConsumers).toHaveLength(4)` in the multi-verb test.
The fixture is fully deterministic — exactly 4 long-form calls,
no other consumer types — so an exact count assertion is the right
shape per DoD §2.7 ("use toBe / toEqual for exact expectations").
Added a comment explaining what the assertion catches that the
existing per-verb toBeDefined() checks would miss (accidental 5th
consumer from a duplicate query firing or a regressed receiver
constraint).

F3 (HEAD/OPTIONS/TRACE negative test) is intentionally not added
in this PR — same precedent as #1855 where HEAD/OPTIONS/TRACE on
the short form are also implicitly excluded without a pinning
test. Happy to add one in a separate PR if maintainers want
explicit pinning across both forms.

F4 (CI on pre-merge SHA) is the maintainer's call — the merge from
main is theirs to re-trigger CI on. The merge brings only Java
consumer changes (PR #1872) and Go provider changes (PR #1886),
both in entirely separate files from this PR's Kotlin work.

Local validation:
  - test/unit/group/http-route-extractor.test.ts: 73/73 ✅
    (66 from this PR pre-merge + 7 from PR #1872 merged via main)
  - npx prettier --check (changed files): clean ✅

* refactor(group): hoist Kotlin WebClient long-form verb regex to module scope

Address @magyargergo's review request on PR #1884:

  > Can you please extract the regexp from the for loop? 🙏
    (kotlin.ts:510)

Compiles the verb whitelist `^(GET|POST|PUT|DELETE|PATCH)$` once at
module load instead of every iteration of the long-form scan loop.
Mirrors the placement and JSDoc style of the sibling
`WEB_CLIENT_SHORT_TO_HTTP` constant.

Behavior is unchanged — same verb whitelist, same exclusion of
HEAD/OPTIONS/TRACE for symmetry with the short form. The 4
itKotlinConsumer long-form tests added in this PR continue to
pass, and the variable-bound-verb anti-overreach test continues
to pin the deliberate non-match.

Local validation:
  - test/unit/group/http-route-extractor.test.ts: 77/77 ✅
  - test/unit/group: 557/557 ✅
  - npx prettier --check (changed file): clean ✅

---------

Co-authored-by: henry <zhangwei2017@unipus.cn>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
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