diff --git a/CHANGELOG.md b/CHANGELOG.md index 08226636c..d4875fde6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In Go, a call or type written through an import whose path ends in a version, or in something other than the package's name, is now known to go through that import: `yaml.Unmarshal(…)` after `import "go.yaml.in/yaml/v3"` or `"gopkg.in/yaml.v3"`, `sqlite3.Error` after `import "github.com/mattn/go-sqlite3"`, `klog.V(2)` after `import "k8s.io/klog/v2"`. Before, only the last part of the path named such an import (`v3`, `yaml.v3`, `go-sqlite3`), so the name was matched on its own and could link to any project function, method or type that shared it: kubernetes' `klog.V(…)` calls were linked to a logging wrapper's `V` method, and etcd's `semver.Version` parameters to an unrelated `Version` function. A name from another module now links to nothing, and one through a package of your own project, like `kit.New()` after `import "example.com/kit/v2"`, links to that package's symbol. A comment in an import block is also no longer taken for the name of the import after it. Re-index Go projects after upgrading. Thanks @danusha2345 for the comment fix. (#2374) - React Router routes written as JSX now read `` the way route objects do: inside `}>`, `} />` is the page at `/`, and `Layout` counts as the layout of every route nested in it, as does a guard with no path of its own like `}>`. Before, the index route was never read, so `/` led to the layout itself and a `` holding only an index route led to nothing. A route's `element` written on lines of its own, the way Prettier formats a long one, or wrapped in `}>`, now links to the page it shows rather than to nothing or to the fallback, and a path written in braces, like `path={"agents"}`, is read like any other. Re-index React projects after upgrading. - In JavaScript and TypeScript, an import written `import { default as AppRoot } from './routes/app/root'`, as in bulletproof-react's router, now counts as the module's default import, the same as `import AppRoot from './routes/app/root'`. Before, it was read as an import of an export named `default`, which no module has, so a call, a route or a JSX attribute that used `AppRoot` was matched by its name alone: it could link to another file's `AppRoot`, or to nothing when the default export has a name of its own. Svelte, Vue and Astro script blocks are read the same way. +- 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. Re-index JavaScript and TypeScript projects after upgrading. ## [1.6.2] - 2026-10-03 diff --git a/__tests__/default-export-declaration.test.ts b/__tests__/default-export-declaration.test.ts new file mode 100644 index 000000000..ab39db1c2 --- /dev/null +++ b/__tests__/default-export-declaration.test.ts @@ -0,0 +1,274 @@ +/** + * 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 regex with a backtick inside a template literal's interpolation + // (outline's headingToSlug): a string masker loses step there and blanked + // the statement below it. + 'src/slug.ts': `export function escapeHtml(text: string) { + return text +} + +function safeSlugify(text: string) { + return \`h-\${escapeHtml(text).replace(/[\`]/g, "")}\` +} + +export default function headingToSlug(text: string, index = 0) { + return index === 0 ? safeSlugify(text) : \`\${safeSlugify(text)}-\${index}\` +} +`, + // 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