From 0262187825fd35fcdfc019585ad5c5c65effb3b3 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Wed, 7 Oct 2026 04:38:16 -0500 Subject: [PATCH 1/2] fix(js,ts): a default import is what `export default` declares, not the first exported function A default import resolved to the module's first exported function or component whenever the `export default` statement wrote a declaration (`export default function Vans()`, `export default class Store`): only a separate `export default NAME` statement was read. A React Router 6.4+ data-router page exports its `loader` or `action` above the page component, so `import Vans, { loader as vansLoader } from './Vans'` bound the route `/vans` (and every call through `Vans`) to `loader`. The resolver now reads the file's own `export default` statement, with comments and strings masked: a declaration it writes is found by position, a binding it names by name, and an anonymous function, class or arrow function is no node at all (the guess took an exported function beside it, or one nested inside it). The statement also beats an exported styled/memo component above it. Only an expression default (`memo(Card)`, `new Service()`), a binding the file doesn't declare, or no statement keeps the old guess. Co-Authored-By: Claude Opus 5.5 --- __tests__/default-export-declaration.test.ts | 255 +++++++++++++++++++ src/resolution/import-resolver.ts | 162 +++++++++--- 2 files changed, 377 insertions(+), 40 deletions(-) create mode 100644 __tests__/default-export-declaration.test.ts diff --git a/__tests__/default-export-declaration.test.ts b/__tests__/default-export-declaration.test.ts new file mode 100644 index 000000000..1913495ec --- /dev/null +++ b/__tests__/default-export-declaration.test.ts @@ -0,0 +1,255 @@ +/** + * A default import is the declaration the module's `export default` statement + * names, wherever it sits in the file — not the first exported function. + * + * A React Router 6.4+ data-router page exports its loader or action above the + * page component (`export function loader() {…}` then `export default function + * Vans() {…}`), and `import Vans, { loader as vansLoader } from './Vans'` bound + * `Vans` to `loader`: the route `/vans` rendered the loader, and every call + * through the default import went there too. A component file that exports a + * styled or memo component lost its default function or binding to that + * component, and an anonymous default (`export default function () {…}`, + * `export default () => …`) took whatever exported function came first — even + * one nested inside the default itself. + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; + +let root = ''; +let cg: CodeGraph; + +const files: Record = { + 'package.json': JSON.stringify({ name: 'vanlife', private: true, dependencies: { react: '^18', 'react-router-dom': '^6.4' } }), + 'src/index.jsx': `import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route } from "react-router-dom" +import Vans, { loader as vansLoader } from "./pages/Vans/Vans" +import HostVans, { loader as hostVansLoader } from "./pages/Host/HostVans" +import Login, { action as loginAction } from "./pages/Login" + +const router = createBrowserRouter(createRoutesFromElements( + + } action={loginAction} /> + } loader={vansLoader} /> + } loader={hostVansLoader} /> + +)) + +export default function App() { + return +} +`, + 'src/api.js': `export function getVans() { + return fetch("/api/vans") +} +`, + 'src/pages/Vans/Vans.jsx': `import { useLoaderData, defer } from "react-router-dom" +import { getVans } from "../../api" + +export function loader() { + return defer({ vans: getVans() }) +} + +export default function Vans() { + const dataPromise = useLoaderData() + function renderVanElements(vans) { + return vans.map((van) =>

{van.name}

) + } + return
{renderVanElements(dataPromise.vans)}
+} +`, + 'src/pages/Host/HostVans.jsx': `import { useLoaderData } from "react-router-dom" +import { getVans } from "../../api" + +export const loader = async () => getVans() + +export default function HostVans() { + return
    {useLoaderData().length}
+} +`, + 'src/pages/Login.jsx': `import { useActionData } from "react-router-dom" + +export async function action({ request }) { + return request.formData() +} + +export default function Login() { + const data = useActionData() + return
{data}
+} +`, + // A Next.js API route: a helper exported above the default handler. + 'src/api/vans.ts': `export function listVans() { + return [] +} + +export default async function handler(req: unknown, res: { json(v: unknown): void }) { + res.json(listVans()) +} +`, + 'src/store.ts': `export function createStore() { + return {} +} + +export default class Store { + read() { + return 1 + } +} +`, + // A line break between the statement and the declaration it writes. + 'src/stream.js': `export function emit() { + return 1 +} + +export default +function* stream() { + yield emit() +} +`, + // An exported styled component above the default function, or the binding. + 'src/components/Title.tsx': `import styled from "styled-components" + +export const Heading = styled.h1\` + font-size: 2rem; +\` + +export default function Title() { + return Vans +} +`, + 'src/components/Banner.tsx': `import styled from "styled-components" + +export const Strip = styled.div\` + display: flex; +\` + +const Banner = () => + +export default Banner +`, + // Anonymous defaults name no declaration of their own. + 'src/anonymous.js': `export function helper() { + return 1 +} + +export default function () { + function inner() { + return helper() + } + return inner() +} +`, + 'src/arrow.jsx': `export function useThing() { + return 1 +} + +export default () => { + const handle = () => useThing() + return