fix(extractors): recover exports dropped by tree-sitter's bare export+newline misparse - #2561
Merged
Conversation
…+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
Contributor
Greptile SummaryThe PR recovers JavaScript and TypeScript exports lost when tree-sitter parses a newline-separated bare
Confidence Score: 5/5The 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
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
Reviews (1): Last reviewed commit: "fix(extractors): recover exports dropped..." | Re-trigger Greptile |
Contributor
Codegraph Impact Analysis3 functions changed → 8 callers affected across 3 files
|
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
tree-sitter-javascript/typescript misparses
exportfollowed by a newline beforeconst/let/var/class/function/interface/typeas a standalone(expression_statement (identifier))node instead of a singleexport_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 noExportentry is ever created, in either engine.Per the ECMAScript grammar,
export Declarationhas no[no LineTerminator here]restriction — unlikereturn/throw, ASI does not apply — so this is valid, correctly-exported JS/TS in every real engine.Fix
exportis a reserved word, so a realidentifiernode whose text is exactly"export"can only ever occur via this misparse — never a legitimate variable reference — making recovery unambiguous:src/extractors/javascript.ts):handleExpressionStmtdetects the bare-exportshape and calls the newrecoverBareExportMisparse, which walks forward past any comment siblings to the next declaration and feeds it throughcollectExportedDeclarations— the same function a correctly-parsedexport_statement's declaration already goes through.(expression_statement (identifier) @bare_export_kw) @bare_export_stmtadded toCOMMON_QUERY_PATTERNSin bothsrc/domain/parser.tsandsrc/domain/wasm-worker-entry.ts(this codebase duplicates that list across the two build contexts). Filtered by text (=== 'export') indispatchQueryMatchrather than a query predicate, since this codebase has no existing predicate usage.crates/codegraph-core/src/extractors/javascript.rs):handle_expr_stmtgained the same check, calling the newrecover_bare_export_misparse, mirroring the TS walk path and reusinghandle_export_declaration.Restricted to direct children of
programin both engines —exportis not valid syntax anywhere else a bare single-identifier expression statement could appear.Out-of-scope finding filed separately
While investigating, found that
enum_declarationis entirely absent fromcollectExportedDeclarations/handle_export_declarationin both engines —export enum Foo {}never produces anExportrecord even when correctly parsed (a real, pre-existing, unrelated bug). Filed as #2560 rather than folding into this PR.Test plan
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;)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")crates/codegraph-core/src/extractors/javascript.rsmirroring all of the above[]/ empty exports); restored and confirmed green.nodeaddon, rancodegraph build --engine nativeand--engine wasmagainst a temp fixture with the exact repro from the issue, confirmedcodegraph exportslists the recovered symbol as exported on both enginesnpm test(full suite) — 339 files, 5447+ passedcargo test --lib(full suite) — 1088 passednpx tsc --noEmit— cleannpm run lint— cleanCloses #2459