Skip to content

fix(js,ts): a function exported as exports["name"] is a symbol named after its export - #3032

Merged
colbymchenry merged 1 commit into
mainfrom
claude/fix-commonjs-bracket-exports
Oct 29, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/fix-commonjs-bracket-exports

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

A CommonJS export written with its name in quotes, like exports["getItems"] = async (req, res) => {…} or module.exports['deleteItem'] = function (req, res) {…}, left its function without a symbol, in both the WebAssembly and the native extractor. The dotted form, exports.getItems = …, has named its function after the export since the CommonJS controller fix. Without a node nothing could find or link to the handler, and its calls counted as the file's own.

Root cause

commonJsExportName (src/extraction/tree-sitter.ts) and its kernel twin common_js_export_name (codegraph-kernel/src/tsjs/extractors.rs) accepted only a member_expression on the assignment's left; exports["getItems"] is a subscript_expression.

Fix

Both engines also accept a subscript_expression on exports / module.exports whose index is a string literal spelling a plain identifier. The test for such a string already existed in both engines for this['svc'] receivers; it is now one helper per engine (quotedIdentifier / quoted_identifier), used by both readers, with the same ASCII identifier rule in each. A key no identifier can spell (exports["not-a-name"]), a computed key (exports[key]) and a template string still name nothing, and handlers["onSave"] = … (not an export) is unchanged.

Validation

  • __tests__/commonjs-exports.test.ts: a new case runs the bracket forms (both quote styles, plus the four that must name nothing) through the kernel and wasm for .js and .ts, and checks names, lines, isExported, isAsync and that the handlers' calls are their own; a parity case compares the two engines' nodes and refs. It fails on main in both engines and passes with the fix.
  • Red on main with our build: main's kernel plus main's tree-sitter.ts give the bracket case no function in either engine (expected [] to deeply equal [ …(2) ], kernel and wasm). Green: the kernel rebuilt from this branch (build-kernel.sh), with CODEGRAPH_KERNEL_EXPECT=1, passes all 5 cases in both engines.
  • scripts/kernel-parity.mjs --lang javascript,typescript,tsx,jsx over the bracket-export fixtures (both quote styles, the names-nothing forms, the dotted and object forms), express and mastodon's app/javascript: 1,100 of 1,101 files byte-identical across engines, 0 with diffs; the one deferred to wasm, menu.stories.tsx, is deferred by main's kernel too.
  • A/B (main f1da755 with its own kernel vs this branch with its kernel, natural-key dumps): express, mastodon (app/javascript), excalidraw, gin and flask are byte-identical. None of them writes exports["…"] = (a survey of our clones found no such function export; the form shows up mostly in generated or older CommonJS code), so this fixes the shape without moving anything else.
  • Full suite on the pushed commit (71a950f, main f1da755, CODEGRAPH_KERNEL_EXPECT=1 with this branch's kernel, --maxWorkers=2 for memory headroom): 789 files (783 passed, 6 skipped), 9,409 tests: 9,324 passed, 0 failed, 85 skipped.

Reimplements the bug part of #2896 by @johnhldiver-beep, who found it; this is an independent implementation in both engines with new tests, credited in the CHANGELOG.

🤖 Generated with Claude Code

…after its export

`exports.getItems = async (req, res) => {…}` names its function after the
export property, in both engines (since the CommonJS controller fix), but
the same export written with the name in quotes, `exports["getItems"] =
…` or `module.exports['deleteItem'] = function () {…}`, left the function
anonymous: no node, so nothing could find it or link to it, and its calls
counted as the file's. commonJsExportName (tree-sitter.ts) and its kernel
twin common_js_export_name (tsjs/extractors.rs) accepted only a
member_expression on the left of the assignment.

Both now also accept a subscript_expression on `exports` or
`module.exports` whose index is a string literal spelling a plain
identifier. The test for that string, which `this['svc']` receivers
already used in both engines, is now one helper per engine
(quotedIdentifier / quoted_identifier) shared by the two readers. A key
that no identifier can spell (`exports["not-a-name"]`), a computed key
and a template string still name nothing.

Validation: the new commonjs-exports cases fail on main and pass in both
engines, and a parity case compares the kernel's and wasm's nodes and
refs for the JS and TS forms.

Reimplements the bug part of PR #2896 by @johnhldiver-beep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry
colbymchenry merged commit 8e854d1 into main Oct 29, 2026
@colbymchenry
colbymchenry deleted the claude/fix-commonjs-bracket-exports branch October 29, 2026 21:40
inth3shadows added a commit to inth3shadows/codegraph that referenced this pull request Oct 29, 2026
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.

1 participant