Skip to content

Tree-sitter never loaded in a packaged install (#47) - #48

Merged
nedonatelli merged 1 commit into
mainfrom
fix/ship-treesitter
Aug 4, 2026
Merged

Tree-sitter never loaded in a packaged install (#47)#48
nedonatelli merged 1 commit into
mainfrom
fix/ship-treesitter

Conversation

@nedonatelli

Copy link
Copy Markdown
Owner

Closes #47.

The bug

const modulePath = './treeSitterAnalyzer.js';
const mod = await import(modulePath).catch(() => null);

The indirection existed to stop static resolution — its comment says the module
is "optional, it only exists when tree-sitter grammars are installed".
src/parsing/treeSitterAnalyzer.ts is tracked in git and always present, so that
premise was stale. What the indirection still did was stop esbuild bundling
it, leaving a runtime specifier pointing at a file dist/ does not contain.

.catch(() => null) sat inside the try block and absorbed exactly the error the
outer logger.warn existed to report. Silent, every time.

Confirmed three ways

static dist/ and the VSIX contain no treeSitterAnalyzer.js — only extension.js and its map
runtime reproducing the call site from dist/ERR_MODULE_NOT_FOUND
empirical the graph 0.122.4 actually cached matches regex (6,541 symbols over src/), not tree-sitter (5,836)

What this means retroactively

The perDeclarator mapping that fixed 221 invisible exported constants, and the
generator_function_declaration mapping from #45, never ran in production.
Both happened to have regex counterparts doing the real work — luck, not design.

The extension shipped 28 MB of .wasm grammars it could not load, and parsed
everything with the less precise fallback (regex yields ~13% more symbols on the
same tree; that gap is false positives, not coverage).

The fix

Literal specifier, so esbuild bundles the module. Grammar loading stays lazy —
the expensive work is inside createTreeSitterAnalyzer, not the import. The
swallow is gone and the fallback now names what degrades.

The test reads the build output

Every existing test in src/parsing/ calls createTreeSitterAnalyzer directly
and passed throughout — they verify a path no install reached. That is the
sharpest form of this repo's recurring failure: not a check that measures
nothing, but one that measures something real which users never execute.

The new suite asserts on dist/extension.js: the analyzer is present, no
dist-relative runtime import survives, and the failure path is not silent. It
throws in CI if the bundle is missing, mirroring grammarsTestSupport — a
skipped suite here would cover nothing while reporting green.

Mutation-checked: restoring the old load path and rebuilding fails two of three.

Verified in a real package

generator_function_declaration     1
createTreeSitterAnalyzer           2
Tree-sitter failed to load         1
dangling ./ runtime import         0

npm run check: 8622 passed, 2 skipped, 465 files. Integration: 45 passing.

🤖 Generated with Claude Code

`registry.ts` reached the analyzer through an indirected dynamic import:

    const modulePath = './treeSitterAnalyzer.js';
    const mod = await import(modulePath).catch(() => null);

The indirection existed to stop static resolution, with a comment saying the
module is "optional — it only exists when tree-sitter grammars are installed".
src/parsing/treeSitterAnalyzer.ts is tracked in git and always present, so that
premise was stale. What the indirection still did was stop esbuild bundling it,
leaving a runtime specifier in dist/extension.js pointing at a file `dist/` does
not contain — it holds extension.js and its sourcemap, nothing else.

Every packaged install therefore threw ERR_MODULE_NOT_FOUND there, and
`.catch(() => null)` sat INSIDE the try block and absorbed exactly the error the
outer handler existed to report. No warning, ever. The extension shipped 28 MB
of .wasm grammars it could not load and parsed everything with the regex
fallback.

Confirmed three ways rather than inferred:
  static     dist/ and the VSIX contain no treeSitterAnalyzer.js
  runtime    reproducing the call site from dist/ gives ERR_MODULE_NOT_FOUND
  empirical  the graph 0.122.4 actually cached matches the regex analyzer's
             count over src/ (6,541) and not tree-sitter's (5,836)

Consequences worth recording: the perDeclarator mapping that fixed 221 invisible
exported constants, and the generator mapping in #45, never ran in production.
Both had regex counterparts that did the real work — luck, not design.

The specifier is now literal so esbuild bundles the module; grammar loading
stays lazy because the expensive part is inside createTreeSitterAnalyzer, not
the import. The swallow is gone, and the fallback now names what degrades.

The new test reads dist/extension.js, because every existing test in this
directory calls createTreeSitterAnalyzer directly and so passed throughout —
they verify a path no install reached. Mutation-checked: restoring the old load
path and rebuilding fails two of the three.

Closes #47

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

✅ All checks passed

Check Status
Type check ✅ success
Lint ✅ success
Tests ✅ success

Posted by SideCarAI-Bot

@nedonatelli
nedonatelli merged commit 766ede2 into main Aug 4, 2026
3 checks passed
@nedonatelli
nedonatelli deleted the fix/ship-treesitter branch September 1, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tree-sitter has never run in a packaged install — the analyzer module is not shipped

1 participant