Skip to content

fix(extractors): recover exports dropped by tree-sitter's bare export+newline misparse - #2561

Merged
carlos-alm merged 1 commit into
mainfrom
fix/issue-2459
Aug 18, 2026
Merged

fix(extractors): recover exports dropped by tree-sitter's bare export+newline misparse#2561
carlos-alm merged 1 commit into
mainfrom
fix/issue-2459

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

tree-sitter-javascript/typescript misparses export followed by a newline before const/let/var/class/function/interface/type as a standalone (expression_statement (identifier)) node instead of a single export_statement (export default/{/* ARE handled correctly across a newline — the grammar's ASI-like heuristic special-cases only those). The declaration that follows is then extracted as an ordinary, internal (non-exported) top-level statement — the export relationship never exists in the parse tree, so no Export entry is ever created, in either engine.

Per the ECMAScript grammar, export Declaration has no [no LineTerminator here] restriction — unlike return/throw, ASI does not apply — so this is valid, correctly-exported JS/TS in every real engine.

Fix

export is a reserved word, so a real identifier node whose text is exactly "export" can only ever occur via this misparse — never a legitimate variable reference — making recovery unambiguous:

  • TS walk path (src/extractors/javascript.ts): handleExpressionStmt detects the bare-export shape and calls the new recoverBareExportMisparse, which walks forward past any comment siblings to the next declaration and feeds it through collectExportedDeclarations — the same function a correctly-parsed export_statement's declaration already goes through.
  • TS query path: new capture (expression_statement (identifier) @bare_export_kw) @bare_export_stmt added to COMMON_QUERY_PATTERNS in both src/domain/parser.ts and src/domain/wasm-worker-entry.ts (this codebase duplicates that list across the two build contexts). Filtered by text (=== 'export') in dispatchQueryMatch rather than a query predicate, since this codebase has no existing predicate usage.
  • Rust native engine (crates/codegraph-core/src/extractors/javascript.rs): handle_expr_stmt gained the same check, calling the new recover_bare_export_misparse, mirroring the TS walk path and reusing handle_export_declaration.

Restricted to direct children of program in both engines — export is not valid syntax anywhere else a bare single-identifier expression statement could appear.

Out-of-scope finding filed separately

While investigating, found that enum_declaration is entirely absent from collectExportedDeclarations/handle_export_declaration in both engines — export enum Foo {} never produces an Export record even when correctly parsed (a real, pre-existing, unrelated bug). Filed as #2560 rather than folding into this PR.

Test plan

  • New tests in tests/parsers/javascript.test.ts (walk path): recovers const/class/function/TS interface/TS type-alias, skips an intervening comment, still exports same-line declarations normally, and doesn't misfire on an unrelated identifier (notExport;)
  • New tests in tests/engines/query-walk-parity.test.ts: a parity case proving query and walk paths agree, plus two dedicated query-path-only tests proving the query capture itself fires correctly (not just "agrees with walk")
  • New tests in crates/codegraph-core/src/extractors/javascript.rs mirroring all of the above
  • Revert-verified on both engines: temporarily disabled the fix (TS walk path, TS query dispatch, Rust) and confirmed all corresponding new tests fail with the exact expected pre-fix output ([] / empty exports); restored and confirmed green
  • End-to-end verified against the real build pipeline (not just unit tests) — rebuilt the native .node addon, ran codegraph build --engine native and --engine wasm against a temp fixture with the exact repro from the issue, confirmed codegraph exports lists the recovered symbol as exported on both engines
  • npm test (full suite) — 339 files, 5447+ passed
  • cargo test --lib (full suite) — 1088 passed
  • npx tsc --noEmit — clean
  • npm run lint — clean

Closes #2459

…+newline misparse

tree-sitter-javascript/typescript misparses `export` followed by a newline
before const/let/var/class/function/interface/type as a standalone
identifier expression statement rather than a single export_statement, so
the declaration that follows is extracted as an internal (non-exported)
definition with no matching Export entry at all.

`export` is a reserved word, so a real identifier node with that exact text
can only occur via this misparse. Both extraction paths now recover it:
recoverBareExportMisparse (walk path, TS) / recover_bare_export_misparse
(Rust) reuse collectExportedDeclarations/handle_export_declaration -- the
same function a correctly-parsed export_statement's declaration goes
through -- keyed off the next non-comment sibling of the misparsed
statement. The query path gets a new capture
('(expression_statement (identifier) @bare_export_kw) @bare_export_stmt' in
parser.ts and wasm-worker-entry.ts) filtered by text in dispatchQueryMatch,
since this codebase has no existing query-predicate usage to filter at the
query level.

Verified end-to-end against the real native and wasm build pipelines, not
just unit tests.

docs check acknowledged -- bug fix only, no new language support, feature,
or architectural change to document.

Impact: 3 functions changed, 6 affected
@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR recovers JavaScript and TypeScript exports lost when tree-sitter parses a newline-separated bare export as an identifier expression.

  • Adds equivalent recovery logic to the TypeScript walk/query paths and native Rust extractor.
  • Keeps the main-thread and WASM-worker query definitions synchronized.
  • Adds walk, query, parity, native, TypeScript, comment-boundary, and negative-case coverage.

Confidence Score: 5/5

The PR appears safe to merge with no concrete changed-code failure identified.

The native, query, and walk implementations apply the same top-level reserved-word guard and reuse their normal export-declaration handlers, with focused parity and negative-case coverage.

Important Files Changed

Filename Overview
src/extractors/javascript.ts Adds guarded bare-export recovery to both query and walk extraction paths while reusing existing export classification.
crates/codegraph-core/src/extractors/javascript.rs Mirrors the TypeScript recovery in the native extractor and adds corresponding native tests.
src/domain/parser.ts Adds the bare-identifier expression capture required by main-thread query extraction.
src/domain/wasm-worker-entry.ts Keeps the WASM worker's duplicated query patterns aligned with the main parser.
tests/engines/query-walk-parity.test.ts Verifies query/walk parity and independently checks query-path recovery and filtering.
tests/parsers/javascript.test.ts Covers recovered declaration kinds, intervening comments, normal exports, and unrelated identifiers.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Source["export + newline + declaration"] --> Parser["tree-sitter parse"]
  Parser --> Bare["bare export expression + declaration sibling"]
  Bare --> Query["TS query capture"]
  Bare --> Walk["TS walk recovery"]
  Bare --> Native["Rust native recovery"]
  Query --> Shared["normal exported-declaration collector"]
  Walk --> Shared
  Native --> NativeCollector["native export-declaration handler"]
  Shared --> Output["ExtractorOutput.exports"]
  NativeCollector --> Output
Loading

Reviews (1): Last reviewed commit: "fix(extractors): recover exports dropped..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

3 functions changed8 callers affected across 3 files

  • dispatchQueryMatch in src/extractors/javascript.ts:371 (4 transitive callers)
  • recoverBareExportMisparse in src/extractors/javascript.ts:2549 (6 transitive callers)
  • handleExpressionStmt in src/extractors/javascript.ts:2580 (3 transitive callers)

@carlos-alm
carlos-alm merged commit 30d177e into main Aug 18, 2026
50 of 52 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2459 branch August 18, 2026 00:38
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 18, 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.

tree-sitter-javascript misparses bare export + newline before a declaration — export silently dropped entirely

1 participant