From 8206c8c2719ed7ee9be058099d9cdb532a2bd3d6 Mon Sep 17 00:00:00 2001 From: Colby Mchenry Date: Wed, 14 Oct 2026 23:22:28 +0000 Subject: [PATCH 1/5] fix(react): a JSX tag imported from the project renders what its module exports (#2563) The JSX render pass matched a tag by name and used the file's imports only to break ties, so an index that forwards another module's component declared no candidate and the tag took a namesake elsewhere: outline's Sidebar/index.ts is `import Sidebar from "./App"; export default Sidebar`, and rendered Aside.tsx's styled Sidebar, not the AppSidebar that App.tsx exports as observer(AppSidebar). A tag imported from a module of the project now renders what that module exports under the imported name, read by exportedComponent, the reader lazy routes use: through an index or barrel, past a wrapper call. Two rules join that reader, for routes too: a const holding another binding is that binding (`const Avatar = AvatarWithHoverCard; export { Avatar }`; a let may be reassigned, so it stays), and a memo wrapper's bodyless component node is the component it wraps (`const MemoizedBlurhash = memo(Blurhash)`). A module whose export is no component node (`export default () => ...`) links nothing rather than a namesake. Sync records every file a tag's import is read through as a synthesis input, so rewiring only the index redraws the tag. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + __tests__/jsx-child-disambiguation.test.ts | 206 +++++++++++++++++++ __tests__/react-router-lazy-barrel.test.ts | 20 +- __tests__/sync-jsx-import-forwarding.test.ts | 176 ++++++++++++++++ src/resolution/callback-synthesizer.ts | 97 ++++++++- src/resolution/frameworks/react.ts | 71 ++++++- 6 files changed, 552 insertions(+), 19 deletions(-) create mode 100644 __tests__/sync-jsx-import-forwarding.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d00b7b23b..49c38c124f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,6 +151,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In React and Vue, a tag for a component the file imports from a package, like `; +} + +export default React.forwardRef(Button); +` + ); + write( + 'components/Toolbar.tsx', + "import ToolbarButton from './Button';\n\nexport function Toolbar() {\n return
;\n}\n" + ); + await index(); + expect(renders('Toolbar')).toEqual(['Button components/Button.tsx:5']); + }); + + it('renders its own app’s component through a barrel when every app has one of that name', async () => { + // bulletproof-react: each app imports `Button` from its own + // `@/components/ui/button`, a barrel of `export * from './button'`. + for (const app of ['nextjs-app', 'react-vite']) { + write(`apps/${app}/tsconfig.json`, JSON.stringify({ compilerOptions: { baseUrl: '.', paths: { '@/*': ['./src/*'] } } })); + write(`apps/${app}/src/components/ui/button/button.tsx`, 'export function Button() {\n return