From 4cd15ee9bf1f44ba331a8b69c4906f2aaafe2a62 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Wed, 21 Oct 2026 11:01:24 -0500 Subject: [PATCH] fix(js,ts): an import reaches what its module hands on, not a namesake or the first export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A module that exports an import of its own declares nothing under the exported name: outline's `server/presenters/index.ts` is `import presentUser from './user'; … export { presentUser, … }`, excalidraw's `index.tsx` is `import Footer from "./components/footer/FooterCenter"; … export { Footer }`, and an `index.ts` beside a component is `import Avatar from './Avatar'; export default Avatar`. findExportedSymbol found no node there, so the import fell to a name match, which took any namesake in the project. A default written as an expression had only the first-exported-function guess behind it: `export default new Logger()` sent outline's server logging calls to the browser app's Logger, and `export default traceFunction({…})(accountProvisioner)` linked `provisionFirstCollection`. import-resolver.ts: - statedDefaultExport also reads `new X()` (a class the file declares), what a wrapper call hands on (handedOn: a capitalized argument or the last of a curried chain, or a function written there, through nested wrappers; only a function, class or component of the file answers), and `export { X as default }`. A default naming a binding the file imports is that import, followed by esmDefaultExport with the walk's visited set and depth. - A local export clause naming an import (`export { X }`, `export { X as Y }`, CommonJS `module.exports = { X }`) is followed too (forwardedExport; clauseImports is filled beside clauseAliases), in the named and the namespace-member branches. - A default the statement leaves unnamed (`createStore(reducer)`) keeps the first-export guess, and a cast (`export default X as T`) is still not read. Replaces #2451, whose JSX half #2563, #2558, #2738 and #2420 cover. Regression test import-forwarding.test.ts: 6 cases, 5 fail on main (the sixth guards `createStore(reducer)` and a call chained past `new X()`). A/B on 16 repos, fresh init, each arm's kernel built from its own sources: 9 byte-identical (5 controls); outline 448 retargeted, 348 added, 0 removed; react-native 71/7/0; excalidraw 4/2/0; mastodon 1/3/6; community-app 1/4/1; refine-examples 0/21/0; redux-realworld 0/9/0. Every changed edge triaged against the source. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + __tests__/import-forwarding.test.ts | 193 +++++++++++++++++++ src/resolution/import-resolver.ts | 288 ++++++++++++++++++++++++---- 3 files changed, 441 insertions(+), 41 deletions(-) create mode 100644 __tests__/import-forwarding.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ff9e3ba3..5aea6ce2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -212,6 +212,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `codegraph sync` and the file watcher now move a React Router route to its page when the router file loads that page lazily into a value of its own, like `const Docs = lazy(() => import('./pages/Docs'))` rendered by `} />`, and the page's file is added after the router was indexed, comes back after being deleted, or gets or changes its default export in a later edit. Before, the route kept pointing at the `Docs` declaration, or at the page's old component, until the router file itself changed or the project was re-indexed, unless the page's component happened to share the declaration's name. The same now holds for a `lazy: () => import('./pages/Docs')` route whose page switches its default export to another component. Re-index React projects after upgrading to correct routes an earlier version left behind. - `codegraph sync` and the file watcher now link a component to the JSX that renders it, like ``, when the component is added in a file with no JSX of its own, as in `export default function Team() { return null; }`. Before, the link waited for an unrelated change to a file with JSX, or for a re-index, so the new component looked unused. - In JavaScript and TypeScript, a default import now links to what the module's `export default` statement exports, wherever the statement sits in the file. Before, a module that declares its default, like `export default function Vans() {…}` or `export default class Store {…}`, had its default import linked to the first function it exports instead: in a React Router data-router app, where a page exports its `loader` or `action` above the page component, `import Vans, { loader as vansLoader } from './pages/Vans'` made the route `/vans` and every use of `Vans` lead to the loader. A component file that also exports a styled or memoized component no longer hands its default import to that component, and an anonymous default, like `export default function () {…}` or `export default () => …`, is no longer linked to a function exported beside it or nested inside it. A module loaded with `require('./x').default` is read the same way. Re-index JavaScript and TypeScript projects after upgrading. +- In JavaScript and TypeScript, an import now links to what its module passes on when the module exports an import of its own, like outline's `server/presenters/index.ts`, which imports each presenter and lists it in `export { presentUser, presentPolicies, … }`, or an `index.ts` written `import Footer from './FooterCenter'; export { Footer }` or `export default Avatar`, and `export { createThing as default }` is read too. A default export written as an expression now links as well: `export default new Logger()` to the methods of that `Logger` class, and a wrapper like `export default connect(mapState)(Profile)`, `observer(DocumentNew)` or `traceFunction({ … })(accountProvisioner)` to the component or function it wraps. Before, such an import was matched by its name alone, so it could land on a namesake elsewhere in the project, as when outline's server logging calls linked to the browser app's `Logger` and excalidraw's public `Footer` to an internal component of that name, or on whichever function the module happened to export first. Re-index JavaScript and TypeScript projects after upgrading. - In React and Vue, a tag for a component the file imports from a package, like `