Repository navigation
fix(js): a JavaScript file's import of a TypeScript module links that module - #2674
Merged
Merged
Conversation
… module An extensionless import from a `.js` or `.jsx` file of a TypeScript module, like mastodon's `import AudioModal from './audio_modal'` in modal_root.jsx beside audio_modal.tsx, never resolved by path: the import resolver's javascript and jsx extension lists held only JavaScript extensions, while the typescript and tsx lists already try `.js`/`.jsx`. In a codebase moving to TypeScript those imports were matched by name instead, so an import under another name linked nothing, and a repeated name could link the wrong file (mastodon's `<IconButton>` and `<Status>` tags rendered other same-named components than the ones each file imports). The javascript and jsx lists now try `.ts`/`.tsx` (and their index files) after their own extensions, as a bundler and tsc's allowJs resolve them. JavaScript stays first, so a `helpers.ts` beside `helpers.js` does not take its importers, and never `.d.ts`, which is no module a JavaScript file runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What was wrong
An extensionless import from a
.jsor.jsxfile of a TypeScript module never resolved by path. mastodon'smodal_root.jsxdoesimport AudioModal from './audio_modal', and the module isaudio_modal.tsxbeside it. The cause wasEXTENSION_RESOLUTIONinsrc/resolution/import-resolver.ts: thejavascriptandjsxlists held only JavaScript extensions, whiletypescriptandtsxalready try.js/.jsx.In a codebase moving to TypeScript, every such import fell through to name matching:
import Modal from './audio_modal') or a repeated name linked nothing.<IconButton>(×16) renderedcomponents/button/redesign.tsx'sIconButton, not theicon_button.tsxeach file imports. Its<Status>(×10) rendered the legacy class, notcomponents/status/index.tsx, and happa'sButtonimports (×23) landed on a stories file'sButton..jsxfile's JSX tags couldn't follow their imports (fix(react): a JSX tag imported from the project renders what its module exports #2563's import-first rendering), because the imports never resolved.Found while triaging #2673: its probe's "no-module" bucket was these imports. No open issue or PR covers it; #1484 and #1767 handle a
.jsspecifier inside TypeScript files.The fix
The
javascriptandjsxlists now try.ts/.tsx(and/index.ts//index.tsx) after their own extensions, as a bundler and tsc'sallowJsresolve them:helpers.tsbesidehelpers.jsdoesn't take its importers..d.ts: a declaration file is not a module a JavaScript file runs, and declared packages stay closed to JavaScript importers as before.Tests
__tests__/js-imports-ts-modules.test.ts:.jsxfile default-imports a.tsxcomponent under another name, imports a function whose name also exists in a second module, and its JSX renders the import..jsfile imports a folder'sindex.ts..jsimporter of./helperskeepshelpers.jswhenhelpers.tsalso exists.Red on main ab6ec21: the first test gets no edges at all from the
.jsxfile and only name-matched ones from the.jsfile. Green with the fix. The control passes on both.Full suite on this branch at main ab6ec21 (Windows 11, kernel built from the tree,
CODEGRAPH_KERNEL_EXPECT=1,--maxWorkers=4 --minWorkers=1): 7,491 passed, 80 skipped, 1 failed. The failure wasmcp-daemon.test.ts's "does not replace a live legacy lock with a second daemon": a daemon candidate still running at the check under parallel load. It's a process-lifecycle test that import resolution never touches, and the file passes 21/21 alone, twice.A/B on real repos
The same 23-repo kit as #2671 and #2673. Both arms are this branch on main ab6ec21 with the same kernel, identical except
dist/resolution/import-resolver.js. Edges are paired per site (source node, position, edge kind, reference name). Synthesized edges are diffed whole. Node counts are identical everywhere.Triage, checked against the fixed arm's own resolver:
.ts/.tsxfiles (e.g. home-assistant'sgallery/src/entrypoint.js→ha-gallery.ts, bitwarden's.storybook/manager.js→panel.tsx).import RoutePath from 'utils/routePath', mastodon'sapi×27) now links the module file, exactly as the same import from a.ts/.tsxfile already did on main. In happa, the 26 TypeScript importers ofutils/routePathlink the file; the 21 JavaScript ones linked the class by name.Button×23 (a stories file before); mastodon'sIconButton×15,Toggle,PollandCard(stories before),addPollOption(compose_typed.ts, notcompose.js),{ ImmutableHashtag as Hashtag }, andVideo(the component, not its async loader); react.dev'sCodeBlockfolder index.IconButtonfunction value →button/redesign.tsx(wrong). The import now bindsicon_button.tsx's class, which no function-value edge names (fix(js,ts): a function value imported from the project links only to what its import binds #2673)..jsxfiles now render the component their file imports. In mastodon,IconButton×16,Status×10,Dropdown,Toggle,ColumnHeader,Video,NavigationBar,Footer,RadioButtonand the autosuggest items all move from a namesake to the import, and new renders appear where the import was the only way to the component (ColumnHeader×15,BoostButton,SkipLinks; happa'sDateComponent,FlashMessage). Three of the "lost" renders are same-file moves (ShortNumber→ theShortNumberRendererit wraps,Hashtag→ the aliasedImmutableHashtag). react.dev's 4 lost<CodeBlock>renders inHomeContent.jsnow match the.tsximporters of that module on main, which render nothing there either: its default export ismemo(function CodeBlockWrapper…), which fix(react): a JSX tag renders what the file imports under that name #2451's default-export reading would link for both.Cost: none measurable. Index times match within noise (mastodon 21 s / 20 s, happa 8 s / 9 s, bitwarden 57 s / 58 s). Only JavaScript imports that miss every JavaScript candidate probe further.
🤖 Generated with Claude Code