Skip to content

fix(js/ts): resolve JSX elements and call-arg identifiers as references (#2389) - #2529

Merged
carlos-alm merged 2 commits into
mainfrom
fix/issue-2389
Aug 15, 2026
Merged

fix(js/ts): resolve JSX elements and call-arg identifiers as references (#2389)#2529
carlos-alm merged 2 commits into
mainfrom
fix/issue-2389

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

Two members of the "value-position reference produces no edge" family (#2257, #2260):

  1. A JSX element is not a reference to its component. <Header /> produced no edge to Header.
  2. An identifier passed as a call argument is not a use. Factory.create(AppModule) produced no edge to AppModule.

Both are structural, not edge cases: every React component used only as JSX read as dead, and NestJS's module/controller registration pattern (NestFactory.create(AppModule)) relies entirely on the second pattern — depressing caller coverage and inflating dead-code counts across every React/NestJS repo in the org rollout.

Changes

Both patterns are extracted as value-ref dynamic calls — the existing mechanism already used for object-literal property values, instanceof operands, and logical-or/ternary fallbacks (#1771/#1895/#2257):

  • JSX element reference (handleJsxElementRef): a JSX opening/self-closing element's tag name is credited as a reference to the component it renders, gated on JSX's own capitalization convention — lowercase (<div>, <span>) is an intrinsic DOM element, never a symbol reference; a member_expression name (<Namespace.Component />) credits the base object identifier.
  • Call-argument identifier reference (extractCallArgumentIdentifierRefs): a capitalized bare identifier passed as a call argument is credited as a reference to whatever it names.

Why capitalized-only for call arguments

This restriction isn't stylistic — it's load-bearing. Issue #1741 is an existing regression guard proving that crediting an arbitrary lowercase DATA argument (e.g. analyzeDrift(communities, communityDirs)) as any kind of reference risks the global-fallback resolver binding it to an unrelated same-named function elsewhere in the repo, fabricating a call edge and, transitively, a phantom cycle. My first pass at this fix did exactly that and broke 13 existing tests locally before I caught it. A class/component reference passed by value is overwhelmingly PascalCase in JS/TS convention, so restricting to capitalized identifiers satisfies #2389's request without reopening #1741 — verified all 13 previously-broken tests pass again with this restriction in place.

Engine parity

Mirrored in crates/codegraph-core/src/extractors/javascript.rs.

The WASM query-based extraction path required new tree-sitter query patterns scoped to only the javascript/tsx grammars — plain .ts (no JSX) has no JSX node types at all, so folding them into the shared TypeScript pattern set would throw at Query() compile time and break all .ts parsing. These patterns are duplicated in both src/domain/parser.ts and the isolated wasm-worker-entry.ts (which intentionally keeps its own copy to avoid importing parser.ts's non-worker-safe process-global caches) — I initially only updated parser.ts and the fix silently didn't work through the real WASM worker pool. A new query-vs-walk parity test case for a capitalized JSX tag catches exactly this class of omission; verified it fails against the pre-fix wasm-worker-entry.ts before confirming it passes with the fix.

Verification

  • cargo fmt -- --check / cargo clippy --workspace --all-targets -- -D warnings: pass
  • npx vitest run tests/parsers/javascript.test.ts: 382/382 pass (8 new tests added, all 13 previously-regressed callback-extraction tests still pass with the capitalization restriction)
  • npx vitest run tests/engines/query-walk-parity.test.ts: 25/25 pass (3 new JSX/call-argument parity cases added; verified they fail against the pre-fix wasm-worker-entry.ts and pass against the fix)
  • cargo test --package codegraph-core extractors::javascript: 299/299 pass (6 new Rust tests added, including a regression guard mirroring bug(resolver): identifier-arg dynamic call resolution fabricates false edges/cycles on name collision (e.g. communities.ts phantom cycle) #1741)
  • npx vitest run tests/parsers/ tests/engines/: 1120/1128 pass (8 skipped, pre-existing)
  • npx vitest run tests/integration/: 1483/1487 pass (2 skipped, 2 todo, pre-existing)
  • npm run lint: pass
  • Manually reproduced both of the issue's exact fixtures against a freshly built native addon (napi build) and the WASM engine: Header/AppModule now both show "Used in: ..." on both engines, with identical node/edge counts

Closes #2389

…es (#2389)

<Header /> and Factory.create(AppModule) produced no reference edge to
Header/AppModule, causing systematic false dead-code across React
(every component used as JSX) and NestJS (module/controller
registration via NestFactory.create(AppModule)) codebases.

Both patterns are extracted as value-ref dynamic calls, the existing
mechanism already used for object-literal property values, instanceof
operands, and logical-or/ternary fallbacks:

- A JSX opening/self-closing element's tag name is credited as a
  reference to the component it renders, gated on JSX's own
  capitalization convention (lowercase = intrinsic DOM element, never a
  symbol reference).
- A capitalized bare identifier passed as a call argument is credited
  as a reference to whatever it names. Restricted to capitalized
  identifiers specifically because issue #1741 is a regression guard
  proving that crediting an arbitrary lowercase DATA argument risks the
  global-fallback resolver binding it to an unrelated same-named
  function elsewhere in the repo, fabricating a call edge and a
  phantom cycle -- a class/component reference passed by value is
  overwhelmingly PascalCase in JS/TS convention, so this restriction
  satisfies #2389's request without reopening #1741.

Mirrored in crates/codegraph-core/src/extractors/javascript.rs for
engine parity. The WASM query-based extraction path required adding
new tree-sitter query patterns scoped to only the javascript/tsx
grammars (plain .ts has no JSX node types at all -- folding them into
the shared TS pattern set would throw at Query-compile time) --
duplicated in both src/domain/parser.ts and the isolated
wasm-worker-entry.ts, which intentionally keeps its own copy of these
patterns to avoid importing parser.ts's non-worker-safe caches. A new
query-vs-walk parity test case for a capitalized JSX tag would have
caught the omission from the worker-entry copy immediately; verified it
fails against the pre-fix worker-entry file before confirming the fix.

docs check acknowledged: internal extractor bugfix, no README/CLAUDE.md/
ROADMAP surface area changed.

Impact: 7 functions changed, 0 affected
@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The follow-up replaces callee-shape-specific call-argument handling with a generic call-expression query capture, closing the previously reported WASM extraction gap.

  • Extracts capitalized call-argument identifiers for all call-expression callee shapes.
  • Adds JSX component references across TypeScript, WASM-worker, and Rust extraction paths.
  • Adds query-vs-walk parity coverage for expression-based callees and JSX references.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/domain/parser.ts Adds the generic call-expression query capture and scopes JSX patterns to grammars that support JSX.
src/domain/wasm-worker-entry.ts Mirrors the parser query configuration in the isolated WASM worker path.
src/extractors/javascript.ts Dispatches generic call captures to argument-reference extraction and handles JSX component references consistently across query and walk paths.
crates/codegraph-core/src/extractors/javascript.rs Mirrors JSX and capitalized call-argument value-reference extraction in the native Rust engine.
tests/engines/query-walk-parity.test.ts Covers the previously missing expression-based callee through query-vs-walk parity testing.
tests/parsers/javascript.test.ts Adds focused extraction and false-positive regression coverage for JSX tags and call arguments.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Source["JS / TS / TSX source"] --> Native["Native Rust extraction"]
  Source --> Wasm["WASM query extraction"]
  Source --> Walk["JavaScript walk fallback"]
  Wasm --> Generic["Generic call_expression capture"]
  Generic --> Args["Capitalized argument value-ref extraction"]
  Native --> Output["ExtractorOutput calls"]
  Args --> Output
  Walk --> Output
  Output --> Graph["Resolved graph reference edges"]
Loading

Reviews (2): Last reviewed commit: "fix(js/ts): route call-arg value-refs th..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

7 functions changed17 callers affected across 5 files

  • doLoadLanguage in src/domain/parser.ts:240 (7 transitive callers)
  • loadLanguageLazy in src/domain/wasm-worker-entry.ts:454 (2 transitive callers)
  • dispatchQueryMatch in src/extractors/javascript.ts:371 (4 transitive callers)
  • walkJavaScriptNode in src/extractors/javascript.ts:1480 (4 transitive callers)
  • handleCallExpr in src/extractors/javascript.ts:2186 (3 transitive callers)
  • handleJsxElementRef in src/extractors/javascript.ts:2268 (5 transitive callers)
  • extractCallArgumentIdentifierRefs in src/extractors/javascript.ts:2310 (6 transitive callers)

…capture (#2389)

Greptile flagged that the query path's callfn/callmem/callsub captures only
cover identifier/member/subscript callees, so expression-based callees like
getFactory()(AppModule) never reached extractCallArgumentIdentifierRefs in
WASM builds even though the walk path and native engine handle every
call_expression unconditionally. Adds a generic (call_expression) @callarg_node
capture (excluding super/this to match handleCallExpr's early returns) and
moves the extraction there instead of duplicating it per callee shape.

docs check acknowledged

Impact: 1 functions changed, 2 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review — addressed the query-path callee-shape coverage gap by routing call-argument value-ref extraction through a generic (call_expression) capture instead of duplicating it per callee shape.

@carlos-alm
carlos-alm merged commit 9ef90fd into main Aug 15, 2026
30 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2389 branch August 15, 2026 23:43
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(js/ts): JSX elements and call-argument identifiers produce no reference edge, causing false dead-code in React and NestJS

1 participant