Skip to content

Commit 57da40b

Browse files
authored
de-duplicate generated types for +register (href) (#13499)
1 parent 4037e51 commit 57da40b

File tree

2 files changed

+37
-32
lines changed

2 files changed

+37
-32
lines changed

.changeset/witty-brooms-work.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Fix `TS2300: Duplicate identifier` errors caused by generated types
6+
7+
Previously, routes that had the same full path would cause duplicate entries in the generated types for `href` (`.react-router/types/+register.ts`), causing type checking errors.

packages/react-router-dev/typegen/index.ts

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { Context } from "./context";
1313
import { getTypesDir, getTypesPath } from "./paths";
1414
import * as Params from "./params";
1515
import * as Route from "./route";
16+
import type { RouteManifestEntry } from "../config/routes";
1617

1718
export async function run(rootDirectory: string, { mode }: { mode: string }) {
1819
const ctx = await createContext({ rootDirectory, mode, watch: false });
@@ -108,44 +109,41 @@ function register(ctx: Context) {
108109

109110
const { t } = Babel;
110111

111-
const indexPaths = new Set(
112-
Object.values(ctx.config.routes)
113-
.filter((route) => route.index)
114-
.map((route) => route.path)
115-
);
112+
const fullpaths: Record<string, RouteManifestEntry[]> = {};
113+
for (const route of Object.values(ctx.config.routes)) {
114+
// skip pathless (layout) routes
115+
if (route.id !== "root" && !route.path) continue;
116+
117+
const lineage = Route.lineage(ctx.config.routes, route);
118+
const fullpath = Route.fullpath(lineage);
119+
const existing = fullpaths[fullpath];
120+
if (!existing || existing.length < lineage.length) {
121+
fullpaths[fullpath] = lineage;
122+
}
123+
}
116124

117125
const typeParams = t.tsTypeAliasDeclaration(
118126
t.identifier("Params"),
119127
null,
120128
t.tsTypeLiteral(
121-
Object.values(ctx.config.routes)
122-
.map((route) => {
123-
// filter out pathless (layout) routes
124-
if (route.id !== "root" && !route.path) return undefined;
125-
126-
// filter out layout routes that have a corresponding index
127-
if (!route.index && indexPaths.has(route.path)) return undefined;
128-
129-
const lineage = Route.lineage(ctx.config.routes, route);
130-
const fullpath = Route.fullpath(lineage);
131-
const params = Params.parse(fullpath);
132-
return t.tsPropertySignature(
133-
t.stringLiteral(fullpath),
134-
t.tsTypeAnnotation(
135-
t.tsTypeLiteral(
136-
Object.entries(params).map(([param, isRequired]) => {
137-
const property = t.tsPropertySignature(
138-
t.stringLiteral(param),
139-
t.tsTypeAnnotation(t.tsStringKeyword())
140-
);
141-
property.optional = !isRequired;
142-
return property;
143-
})
144-
)
129+
Object.keys(fullpaths).map((fullpath) => {
130+
const params = Params.parse(fullpath);
131+
return t.tsPropertySignature(
132+
t.stringLiteral(fullpath),
133+
t.tsTypeAnnotation(
134+
t.tsTypeLiteral(
135+
Object.entries(params).map(([param, isRequired]) => {
136+
const property = t.tsPropertySignature(
137+
t.stringLiteral(param),
138+
t.tsTypeAnnotation(t.tsStringKeyword())
139+
);
140+
property.optional = !isRequired;
141+
return property;
142+
})
145143
)
146-
);
147-
})
148-
.filter((x): x is Babel.Babel.TSPropertySignature => x !== undefined)
144+
)
145+
);
146+
})
149147
)
150148
);
151149

0 commit comments

Comments
 (0)