GitNexus/gitnexus/test
henry201605 b16ec344f7
perf(group/http): skip source parse for graph-covered route files (#2138 Part 2) (#2265)
* feat(routes): resolve + persist handler symbol on Route nodes (#2138 part 2, WIP)

Part 2 groundwork for #2138: give the graph-assisted HTTP provider path the
handler symbol directly, so it no longer re-parses source to recover the
handler name. (The remaining parse-skip in extract() + a call-count benchmark
land in a follow-up commit.)

- ExtractedDecoratorRoute gains `handlerName`; the Spring extractor captures
  the decorated method's name (the method_declaration node is in hand).
- New `resolveRouteHandlerSymbols` (call-processor) resolves each route's
  handler to a real symbol UID, keyed by normalized route URL — Laravel
  framework routes (controller + method) and decorator routes (Spring/FastAPI)
  both reduce to `(filePath, name) -> nodeId`. Threaded through the parse phase
  onto `ParseOutput.routeHandlerSymbols`.
- routes phase stamps `Route.handlerSymbolId`; persisted end-to-end (schema +
  Route CSV row + getCopyQuery COPY columns), mirroring Part 1's `method`.
- HttpRouteExtractor: `HANDLES_ROUTE_QUERY` returns `handlerSymbolId`;
  `extractProvidersGraph` uses it as the authoritative symbol and SKIPS
  `getDetections()` for resolved rows (CONTAINS is a cheap graph lookup for the
  display name only — no tree-sitter parse). Fully backward compatible: an
  unresolved/old-index route with no `handlerSymbolId` keeps the source-scan
  fallback.
- Extracted `normalizeExtractedRoutePath` to `route-extractors/route-path.ts`
  (shared by routes phase + resolver without an import cycle).
- SCHEMA_BUMP 6->7 (ParseWorkerResult gained `handlerName`); regenerated the
  emit-persistence byte-identity baseline (route.csv header gained two columns).
- Tests: Spring pipeline asserts the Route node carries a handlerSymbolId
  resolving to the handler method; extractor fast-path test proves the handler
  resolves with zero source detections.

Refs #2138

* perf(group/http): skip source parse for graph-covered route files (#2138 Part 2)

Builds on the persisted Route.handlerSymbolId (U0–U3a). When a file's
HANDLES_ROUTE rows all resolve a handler symbol AND its language plugin
declares routeCoverage: 'complete' (Java/Python/PHP), the graph is
authoritative for that file's providers, so the source scan + tree-sitter
parse can be skipped — the scan would only re-discover routes the graph
already has. This is the measurable parse reduction #2167 could not show.

Consumer safety: routeCoverage: 'complete' asserts *provider* Route-node
completeness only. The scan() of those same languages also emits consumer
detections (RestTemplate/WebClient/OkHttp/Feign, Guzzle/Http::,
requests/httpx), and ingestion's FETCHES edges are JS/TS-only — so the
graph cannot back up server-side consumers. A provider-covered controller
that also calls out would otherwise lose its consumer contract. Guarded by
a cheap, parse-free text gate.

- types: HttpLanguagePlugin gains
    - routeCoverage?: 'complete' | 'partial' (default 'partial')
    - hasConsumerSignals?(content): false only when the raw source provably
      has no outbound-HTTP call this plugin detects (conservative).
- java/python/php: mark routeCoverage 'complete' + implement
  hasConsumerSignals with a token regex over their consumer idioms.
- http-route-extractor: run the graph provider pass first to build a
  coveredFiles set; then keep a file covered only when
  hasConsumerSignals(content) === false (read via readSafe, no parse).
  scanFiles = files not covered → drives collectProjectDetections + both
  source scans. Fail-open per file: any unresolved row, a 'partial'
  language, a positive consumer signal, a missing hook, or an unreadable
  file leaves the file in the scan set. The orchestrator names no
  languages — token knowledge stays in the plugins.

Net: pure-provider controllers skip the parse (the win); controllers that
also call out are still parsed (no consumer loss); partial-coverage
languages and graph-less runs are unchanged.

- test: route-parse-skip integration test spies the real parseSourceSafe to
  COUNT parses over a temp repo of Spring controllers with a mock DB —
  baseline (every file parsed), fully-covered (0 parses), mixed (unresolved
  file falls back, resolved stays skipped), and provider+consumer (a covered
  controller that also calls restTemplate is parsed; its consumer contract
  survives).

* fix(group/http): cover Spring HTTP Interface @*Exchange in Java consumer-signal gate

#2254 (merged) added Spring 6 HTTP Interface `@(Get|...)Exchange` /
`@HttpExchange` as a new Java *consumer* idiom. The #2138 parse-skip
consumer-safety gate must recognize it, or a provider-covered file carrying
an `@GetExchange` could be parse-skipped and lose that consumer contract.
Add `Exchange` to JAVA_HTTP_PLUGIN.hasConsumerSignals (conservative; also
matches `restTemplate.exchange(`).

* style(group/http): prettier formatting for #2138 Part 2 files

* style(ingestion): prettier formatting for call-processor.ts (#2138 Part 2)

* fix(group/http): P1 (Java over-claim) + P2 (handler mis-attribution) on top of #2268 (#2138 Part 2)

Re-applied on the maintainer's #2268 (expanded Java/Kotlin consumer
extraction) base.

P1 — `routeCoverage: 'complete'` over-claimed for Java: the graph provider
set is a strict subset of the group scan (array-form `@GetMapping({...})`,
interface-inherited routes, same-URL multi-verb have no graph Route node),
so parse-skip could drop those group-only providers.
- java/python → default 'partial' (always source-scanned). Java flips to
  'complete' only once ingestion provider extraction matches the group scan
  (a separate follow-up). Python was a no-op anyway (no handlerName resolved);
  'complete' was a latent trap. PHP stays 'complete' (Laravel ingestion ⊇ the
  group scan, the one language the skip engages for).
- python hasConsumerSignals widened to a true superset of scan() (uri=/url=
  wrapper, aiohttp, urllib). Java's gate already covers #2268's consumer set
  (same receivers; the @*Exchange token is present).

P2 — resolveRouteHandlerSymbols: reserve the URL slot on first encounter even
when unresolved (mirrors addRoute first-writer-wins, so a later same-URL route
can't stamp the node-winner's slot); refuse to guess on an ambiguous same-name
lookup (exactly one match → use it; zero/many → fail-open, never a wrong
handler). The cross-source case (filesystem route winning a URL a framework
route also normalizes to) is unchanged — the resolver never receives
filesystem routes — and stays fail-open.

Tests:
- route-parse-skip rewritten: the parse-skip win is proven on PHP (fully
  covered → 0 parses; mixed fallback; consumer-covered file still parsed), plus
  three Java P1 regression guards (array-form / interface-inherited / multi-verb)
  asserting the group-only routes survive — verified they go red if Java is
  flipped back to 'complete'.
- resolve-route-handler-symbols: direct unit tests (the fn had none) — unique
  resolve, ambiguous/unknown fail-open, same-URL reservation, first-writer-wins.
- http-consumer-signals: each plugin's hasConsumerSignals is a superset of its
  scan() consumer idioms; pure providers return false.
- route-handler-symbol-roundtrip: real-LadybugDB CSV→COPY→query for
  Route.handlerSymbolId.

---------

Co-authored-by: henry <zhangwei2017@unipus.cn>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-06-23 07:22:43 +01:00
..
fixtures feat(group): Support Django route extraction for multi-repo (#1836) 2026-06-21 20:11:30 +01:00
helpers feat(cfg): PDG/CFG visitors for all supported languages (#2195) (#2197) 2026-06-15 12:31:04 +01:00
integration perf(group/http): skip source parse for graph-covered route files (#2138 Part 2) (#2265) 2026-06-23 07:22:43 +01:00
unit perf(group/http): skip source parse for graph-covered route files (#2138 Part 2) (#2265) 2026-06-23 07:22:43 +01:00
utils perf(hooks): cmdline-first Linux db-lock scan, drop the lsof fallback (#2180) (#2183) 2026-06-13 11:52:14 +01:00