fix(js/ts): resolve JSX elements and call-arg identifiers as references (#2389) - #2529
Merged
Conversation
…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
Contributor
Greptile SummaryThe follow-up replaces callee-shape-specific call-argument handling with a generic call-expression query capture, closing the previously reported WASM extraction gap.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
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"]
Reviews (2): Last reviewed commit: "fix(js/ts): route call-arg value-refs th..." | Re-trigger Greptile |
Contributor
Codegraph Impact Analysis7 functions changed → 17 callers affected across 5 files
|
…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
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two members of the "value-position reference produces no edge" family (#2257, #2260):
<Header />produced no edge toHeader.Factory.create(AppModule)produced no edge toAppModule.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-refdynamic calls — the existing mechanism already used for object-literal property values,instanceofoperands, and logical-or/ternary fallbacks (#1771/#1895/#2257):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; amember_expressionname (<Namespace.Component />) credits the base object identifier.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/tsxgrammars — plain.ts(no JSX) has no JSX node types at all, so folding them into the shared TypeScript pattern set would throw atQuery()compile time and break all.tsparsing. These patterns are duplicated in bothsrc/domain/parser.tsand the isolatedwasm-worker-entry.ts(which intentionally keeps its own copy to avoid importingparser.ts's non-worker-safe process-global caches) — I initially only updatedparser.tsand 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-fixwasm-worker-entry.tsbefore confirming it passes with the fix.Verification
cargo fmt -- --check/cargo clippy --workspace --all-targets -- -D warnings: passnpx 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-fixwasm-worker-entry.tsand 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: passnapi build) and the WASM engine:Header/AppModulenow both show "Used in: ..." on both engines, with identical node/edge countsCloses #2389