@tanstack/router-generator 1.166.29 → 1.166.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/cjs/config.cjs +0 -1
  2. package/dist/cjs/config.cjs.map +1 -1
  3. package/dist/cjs/config.d.cts +0 -3
  4. package/dist/cjs/generator.cjs +1 -44
  5. package/dist/cjs/generator.cjs.map +1 -1
  6. package/dist/cjs/index.cjs +0 -2
  7. package/dist/cjs/index.d.cts +1 -2
  8. package/dist/cjs/template.cjs +15 -12
  9. package/dist/cjs/template.cjs.map +1 -1
  10. package/dist/cjs/transform/transform.cjs +283 -266
  11. package/dist/cjs/transform/transform.cjs.map +1 -1
  12. package/dist/cjs/transform/transform.d.cts +1 -3
  13. package/dist/cjs/transform/types.d.cts +1 -7
  14. package/dist/cjs/utils.cjs +0 -16
  15. package/dist/cjs/utils.cjs.map +1 -1
  16. package/dist/cjs/utils.d.cts +0 -24
  17. package/dist/esm/config.d.ts +0 -3
  18. package/dist/esm/config.js +0 -1
  19. package/dist/esm/config.js.map +1 -1
  20. package/dist/esm/generator.js +2 -45
  21. package/dist/esm/generator.js.map +1 -1
  22. package/dist/esm/index.d.ts +1 -2
  23. package/dist/esm/index.js +1 -2
  24. package/dist/esm/template.js +15 -12
  25. package/dist/esm/template.js.map +1 -1
  26. package/dist/esm/transform/transform.d.ts +1 -3
  27. package/dist/esm/transform/transform.js +280 -265
  28. package/dist/esm/transform/transform.js.map +1 -1
  29. package/dist/esm/transform/types.d.ts +1 -7
  30. package/dist/esm/utils.d.ts +0 -24
  31. package/dist/esm/utils.js +1 -15
  32. package/dist/esm/utils.js.map +1 -1
  33. package/package.json +4 -4
  34. package/src/config.ts +0 -1
  35. package/src/generator.ts +0 -66
  36. package/src/index.ts +1 -7
  37. package/src/template.ts +16 -36
  38. package/src/transform/transform.ts +633 -446
  39. package/src/transform/types.ts +1 -8
  40. package/src/utils.ts +5 -43
  41. package/dist/cjs/transform/utils.cjs +0 -34
  42. package/dist/cjs/transform/utils.cjs.map +0 -1
  43. package/dist/cjs/transform/utils.d.cts +0 -2
  44. package/dist/esm/transform/utils.d.ts +0 -2
  45. package/dist/esm/transform/utils.js +0 -34
  46. package/dist/esm/transform/utils.js.map +0 -1
  47. package/src/transform/utils.ts +0 -42
@@ -1,299 +1,316 @@
1
- require("../_virtual/_rolldown/runtime.cjs");
2
- const require_utils = require("../utils.cjs");
3
- const require_utils$1 = require("./utils.cjs");
1
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
+ let magic_string = require("magic-string");
3
+ magic_string = require_runtime.__toESM(magic_string);
4
+ let _babel_types = require("@babel/types");
5
+ _babel_types = require_runtime.__toESM(_babel_types);
4
6
  let _tanstack_router_utils = require("@tanstack/router-utils");
5
- let recast = require("recast");
6
- let source_map = require("source-map");
7
7
  //#region src/transform/transform.ts
8
- var b = recast.types.builders;
9
- async function transform({ ctx, source, node }) {
10
- let appliedChanges = false;
8
+ var routeConstructors = ["createFileRoute", "createLazyFileRoute"];
9
+ function transform({ ctx, source, node }) {
11
10
  let ast;
12
11
  try {
13
- ast = (0, recast.parse)(source, {
14
- sourceFileName: "output.ts",
15
- parser: { parse(code) {
16
- return (0, _tanstack_router_utils.parseAst)({
17
- code,
18
- tokens: true
19
- });
20
- } }
21
- });
22
- } catch (e) {
23
- console.error("Error parsing code", ctx.routeId, source, e);
12
+ ast = (0, _tanstack_router_utils.parseAst)({ code: source });
13
+ } catch (error) {
24
14
  return {
25
15
  result: "error",
26
- error: e
16
+ error
27
17
  };
28
18
  }
29
- const preferredQuote = detectPreferredQuoteStyle(ast);
30
- let routeExportHandled = false;
31
- function onExportFound(decl) {
32
- if (decl.init?.type === "CallExpression") {
33
- const callExpression = decl.init;
34
- const firstArgument = callExpression.arguments[0];
35
- if (firstArgument) {
36
- if (firstArgument.type === "ObjectExpression") {
37
- const staticProperties = firstArgument.properties.flatMap((p) => {
38
- if (p.type === "ObjectProperty" && p.key.type === "Identifier") return p.key.name;
39
- return [];
40
- });
41
- node.createFileRouteProps = new Set(staticProperties);
42
- }
43
- }
44
- let identifier;
45
- if (callExpression.callee.type === "Identifier") {
46
- identifier = callExpression.callee;
47
- if (ctx.verboseFileRoutes) {
48
- callExpression.callee = b.callExpression(identifier, [b.stringLiteral(ctx.routeId)]);
49
- appliedChanges = true;
50
- }
51
- } else if (callExpression.callee.type === "CallExpression" && callExpression.callee.callee.type === "Identifier") {
52
- identifier = callExpression.callee.callee;
53
- if (!ctx.verboseFileRoutes) {
54
- callExpression.callee = identifier;
55
- appliedChanges = true;
56
- } else appliedChanges = require_utils$1.ensureStringArgument(callExpression.callee, ctx.routeId, ctx.preferredQuote);
57
- }
58
- if (identifier === void 0) throw new Error(`expected identifier to be present in ${ctx.routeId} for export "Route"`);
59
- if (identifier.name === "createFileRoute" && ctx.lazy) {
60
- identifier.name = "createLazyFileRoute";
61
- appliedChanges = true;
62
- } else if (identifier.name === "createLazyFileRoute" && !ctx.lazy) {
63
- identifier.name = "createFileRoute";
64
- appliedChanges = true;
65
- }
66
- } else throw new Error(`expected "Route" export to be initialized by a CallExpression`);
67
- routeExportHandled = true;
19
+ const exportedRouteNames = getExportedRouteNames(ast.program.body);
20
+ if (exportedRouteNames.size === 0) return { result: "no-route-export" };
21
+ const { calls: routeCalls, hasUnsupportedRouteId, hasMalformedRouteCall } = findExportedRouteCalls(ast.program.body, exportedRouteNames);
22
+ if (routeCalls.length === 0 && hasMalformedRouteCall) return {
23
+ result: "error",
24
+ error: /* @__PURE__ */ new Error(`expected Route export in ${ctx.routeId} to use createFileRoute('/path')({...}) or createLazyFileRoute('/path')({...})`)
25
+ };
26
+ if (routeCalls.length === 0 && hasUnsupportedRouteId) return {
27
+ result: "error",
28
+ error: /* @__PURE__ */ new Error(`expected route id to be a string literal or plain template literal in ${ctx.routeId}`)
29
+ };
30
+ if (routeCalls.length === 0) return { result: "not-modified" };
31
+ if (routeCalls.length > 1) return {
32
+ result: "error",
33
+ error: /* @__PURE__ */ new Error(`expected exactly one createFileRoute/createLazyFileRoute call in ${ctx.routeId}`)
34
+ };
35
+ const routeCall = routeCalls[0];
36
+ const routeIdQuote = getRouteIdQuote(source, routeCall.routeIdArg);
37
+ const createFileRouteProps = getCreateFileRouteProps(routeCall.optionsArg);
38
+ if (createFileRouteProps) node.createFileRouteProps = createFileRouteProps;
39
+ const expectedCallee = getExpectedRouteConstructor(ctx.lazy);
40
+ const expectedRouteId = `${routeIdQuote}${ctx.routeId}${routeIdQuote}`;
41
+ const currentRouteId = source.slice(routeCall.routeIdArg.start, routeCall.routeIdArg.end);
42
+ const targetModule = `@tanstack/${ctx.target}-router`;
43
+ const imports = parseTargetImports(ast.program.body, source, targetModule);
44
+ const s = new magic_string.default(source);
45
+ let modified = false;
46
+ if (routeCall.callee.name !== expectedCallee) {
47
+ s.update(routeCall.callee.start, routeCall.callee.end, expectedCallee);
48
+ modified = true;
68
49
  }
69
- const program = ast.program;
70
- for (const n of program.body) {
71
- if (n.type === "ExportNamedDeclaration") {
72
- if (n.declaration?.type === "VariableDeclaration") {
73
- const decl = n.declaration.declarations[0];
74
- if (decl && decl.type === "VariableDeclarator" && decl.id.type === "Identifier") {
75
- if (decl.id.name === "Route") onExportFound(decl);
76
- }
77
- } else if (n.declaration === null && n.specifiers) {
78
- for (const spec of n.specifiers) if (typeof spec.exported.name === "string") {
79
- if (spec.exported.name === "Route") {
80
- const variableName = spec.local?.name || spec.exported.name;
81
- for (const decl of program.body) if (decl.type === "VariableDeclaration" && decl.declarations[0]) {
82
- const variable = decl.declarations[0];
83
- if (variable.type === "VariableDeclarator" && variable.id.type === "Identifier" && variable.id.name === variableName) {
84
- onExportFound(variable);
85
- break;
86
- }
87
- }
88
- }
89
- }
90
- }
91
- }
92
- if (routeExportHandled) break;
50
+ if (currentRouteId !== expectedRouteId) {
51
+ s.update(routeCall.routeIdArg.start, routeCall.routeIdArg.end, expectedRouteId);
52
+ modified = true;
93
53
  }
94
- if (!routeExportHandled) return { result: "no-route-export" };
95
- const imports = {
96
- required: [],
97
- banned: []
54
+ if (updateRouteImports({
55
+ imports,
56
+ source,
57
+ s,
58
+ targetModule,
59
+ required: expectedCallee,
60
+ lineEnding: getLineEnding(source)
61
+ })) modified = true;
62
+ if (!modified) return { result: "not-modified" };
63
+ return {
64
+ result: "modified",
65
+ output: s.toString()
98
66
  };
99
- const targetModule = `@tanstack/${ctx.target}-router`;
100
- if (ctx.verboseFileRoutes === false) imports.banned = [{
101
- source: targetModule,
102
- specifiers: [{ imported: "createLazyFileRoute" }, { imported: "createFileRoute" }]
103
- }];
104
- else if (ctx.lazy) {
105
- imports.required = [{
106
- source: targetModule,
107
- specifiers: [{ imported: "createLazyFileRoute" }]
108
- }];
109
- imports.banned = [{
110
- source: targetModule,
111
- specifiers: [{ imported: "createFileRoute" }]
112
- }];
113
- } else {
114
- imports.required = [{
115
- source: targetModule,
116
- specifiers: [{ imported: "createFileRoute" }]
117
- }];
118
- imports.banned = [{
119
- source: targetModule,
120
- specifiers: [{ imported: "createLazyFileRoute" }]
121
- }];
67
+ }
68
+ function getExportedRouteNames(body) {
69
+ const exportedRouteNames = /* @__PURE__ */ new Set();
70
+ for (const statement of body) {
71
+ if (!_babel_types.isExportNamedDeclaration(statement) || statement.source) continue;
72
+ if (_babel_types.isVariableDeclaration(statement.declaration)) {
73
+ for (const declarator of statement.declaration.declarations) if (_babel_types.isIdentifier(declarator.id) && declarator.id.name === "Route") exportedRouteNames.add("Route");
74
+ }
75
+ for (const specifier of statement.specifiers) {
76
+ if (!_babel_types.isExportSpecifier(specifier) || getExportedName(specifier.exported) !== "Route") continue;
77
+ const localName = getLocalBindingName(specifier.local);
78
+ if (localName) exportedRouteNames.add(localName);
79
+ }
122
80
  }
123
- imports.required = require_utils.mergeImportDeclarations(imports.required);
124
- imports.banned = require_utils.mergeImportDeclarations(imports.banned);
125
- const importStatementCandidates = [];
126
- const importDeclarationsToRemove = [];
127
- for (const n of program.body) {
128
- const findImport = (opts) => (i) => {
129
- if (i.source === opts.source) {
130
- const importKind = i.importKind || "value";
131
- return (opts.importKind || "value") === importKind;
81
+ return exportedRouteNames;
82
+ }
83
+ function findExportedRouteCalls(body, exportedRouteNames) {
84
+ const calls = [];
85
+ let hasUnsupportedRouteId = false;
86
+ let hasMalformedRouteCall = false;
87
+ for (const statement of body) {
88
+ const declaration = getVariableDeclaration(statement);
89
+ if (!declaration) continue;
90
+ for (const declarator of declaration.declarations) {
91
+ if (!_babel_types.isIdentifier(declarator.id) || !exportedRouteNames.has(declarator.id.name)) continue;
92
+ const init = getRouteConstructorInit(declarator.init);
93
+ if (!init) {
94
+ if (isDirectRouteConstructorCall(declarator.init)) hasMalformedRouteCall = true;
95
+ continue;
132
96
  }
133
- return false;
134
- };
135
- if (n.type === "ImportDeclaration" && typeof n.source.value === "string") {
136
- const filterImport = findImport({
137
- source: n.source.value,
138
- importKind: n.importKind
97
+ const routeIdArg = init.innerCall.arguments[0];
98
+ if (isSupportedRouteId(routeIdArg)) calls.push({
99
+ callee: init.callee,
100
+ routeIdArg,
101
+ optionsArg: init.outerCall.arguments[0]
139
102
  });
140
- let requiredImports = imports.required.filter(filterImport)[0];
141
- const bannedImports = imports.banned.filter(filterImport)[0];
142
- if (!requiredImports && !bannedImports) continue;
143
- const importSpecifiersToRemove = [];
144
- if (n.specifiers) {
145
- for (const spec of n.specifiers) {
146
- if (!requiredImports && !bannedImports) break;
147
- if (spec.type === "ImportSpecifier" && typeof spec.imported.name === "string") {
148
- if (requiredImports) {
149
- const requiredImportIndex = requiredImports.specifiers.findIndex((imp) => imp.imported === spec.imported.name);
150
- if (requiredImportIndex !== -1) {
151
- requiredImports.specifiers.splice(requiredImportIndex, 1);
152
- if (requiredImports.specifiers.length === 0) {
153
- imports.required = imports.required.splice(imports.required.indexOf(requiredImports), 1);
154
- requiredImports = void 0;
155
- }
156
- } else importStatementCandidates.push(n);
157
- }
158
- if (bannedImports) {
159
- if (bannedImports.specifiers.findIndex((imp) => imp.imported === spec.imported.name) !== -1) importSpecifiersToRemove.push(spec);
160
- }
161
- }
162
- }
163
- if (importSpecifiersToRemove.length > 0) {
164
- appliedChanges = true;
165
- n.specifiers = n.specifiers.filter((spec) => !importSpecifiersToRemove.includes(spec));
166
- if (n.specifiers.length === 0) importDeclarationsToRemove.push(n);
167
- }
168
- }
103
+ else hasUnsupportedRouteId = true;
169
104
  }
170
105
  }
171
- imports.required.forEach((requiredImport) => {
172
- if (requiredImport.specifiers.length > 0) {
173
- appliedChanges = true;
174
- if (importStatementCandidates.length > 0) {
175
- const importStatement = importStatementCandidates.find((importStatement) => {
176
- if (importStatement.source.value === requiredImport.source) return (importStatement.importKind || "value") === (requiredImport.importKind || "value");
177
- return false;
178
- });
179
- if (importStatement) {
180
- if (importStatement.specifiers === void 0) importStatement.specifiers = [];
181
- const importSpecifiersToAdd = requiredImport.specifiers.map((spec) => b.importSpecifier(b.identifier(spec.imported), b.identifier(spec.imported)));
182
- importStatement.specifiers = [...importStatement.specifiers, ...importSpecifiersToAdd];
183
- return;
184
- }
185
- }
186
- const importStatement = b.importDeclaration(requiredImport.specifiers.map((spec) => b.importSpecifier(b.identifier(spec.imported), spec.local ? b.identifier(spec.local) : null)), b.stringLiteral(requiredImport.source));
187
- program.body.unshift(importStatement);
106
+ return {
107
+ calls,
108
+ hasUnsupportedRouteId,
109
+ hasMalformedRouteCall
110
+ };
111
+ }
112
+ function getVariableDeclaration(statement) {
113
+ const declaration = _babel_types.isExportNamedDeclaration(statement) ? statement.declaration : statement;
114
+ return _babel_types.isVariableDeclaration(declaration) ? declaration : null;
115
+ }
116
+ function getExportedName(node) {
117
+ return _babel_types.isIdentifier(node) ? node.name : node.value;
118
+ }
119
+ function getLocalBindingName(node) {
120
+ return _babel_types.isIdentifier(node) ? node.name : null;
121
+ }
122
+ function getRouteConstructorInit(expression) {
123
+ if (!expression || !_babel_types.isCallExpression(expression)) return null;
124
+ if (!_babel_types.isCallExpression(expression.callee)) return null;
125
+ const innerCall = expression.callee;
126
+ if (!_babel_types.isIdentifier(innerCall.callee) || !isRouteConstructor(innerCall.callee)) return null;
127
+ return {
128
+ callee: innerCall.callee,
129
+ outerCall: expression,
130
+ innerCall
131
+ };
132
+ }
133
+ function isDirectRouteConstructorCall(expression) {
134
+ return !!expression && _babel_types.isCallExpression(expression) && _babel_types.isIdentifier(expression.callee) && isRouteConstructor(expression.callee);
135
+ }
136
+ function isRouteConstructor(callee) {
137
+ return routeConstructors.includes(callee.name);
138
+ }
139
+ function isSupportedRouteId(arg) {
140
+ return !!arg && (_babel_types.isStringLiteral(arg) || _babel_types.isTemplateLiteral(arg) && arg.expressions.length === 0);
141
+ }
142
+ function getRouteIdQuote(source, arg) {
143
+ const raw = source.slice(arg.start, arg.end);
144
+ if (raw.startsWith("'")) return "'";
145
+ if (raw.startsWith("\"")) return "\"";
146
+ return "`";
147
+ }
148
+ function getCreateFileRouteProps(arg) {
149
+ if (!arg || !_babel_types.isObjectExpression(arg)) return;
150
+ const props = /* @__PURE__ */ new Set();
151
+ for (const property of arg.properties) {
152
+ if (!_babel_types.isObjectProperty(property) || property.computed) continue;
153
+ if (_babel_types.isIdentifier(property.key)) {
154
+ props.add(property.key.name);
155
+ continue;
188
156
  }
157
+ if (_babel_types.isStringLiteral(property.key)) props.add(property.key.value);
158
+ }
159
+ return props;
160
+ }
161
+ function parseTargetImports(body, source, targetModule) {
162
+ const imports = [];
163
+ for (const statement of body) {
164
+ if (!_babel_types.isImportDeclaration(statement) || statement.importKind === "type" || statement.source.value !== targetModule) continue;
165
+ const rawSource = source.slice(statement.source.start, statement.source.end);
166
+ const importStatement = source.slice(statement.start, statement.end);
167
+ imports.push({
168
+ declaration: statement,
169
+ defaultImport: statement.specifiers.find((specifier) => _babel_types.isImportDefaultSpecifier(specifier))?.local.name,
170
+ namespace: statement.specifiers.find((specifier) => _babel_types.isImportNamespaceSpecifier(specifier))?.local.name,
171
+ named: statement.specifiers.filter((specifier) => _babel_types.isImportSpecifier(specifier)).map((specifier) => ({
172
+ imported: _babel_types.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value,
173
+ local: specifier.local.name,
174
+ importKind: specifier.importKind ?? void 0
175
+ })),
176
+ moduleName: statement.source.value,
177
+ quote: rawSource[0],
178
+ semicolon: importStatement.trimEnd().endsWith(";")
179
+ });
180
+ }
181
+ return imports;
182
+ }
183
+ function updateRouteImports({ imports, source, s, targetModule, required, lineEnding }) {
184
+ const analysis = analyzeRouteImports(imports, required);
185
+ if (analysis.kind === "ok") return false;
186
+ if (analysis.kind === "rename") {
187
+ s.update(analysis.imported.start, analysis.imported.end, analysis.next);
188
+ s.update(analysis.local.start, analysis.local.end, analysis.next);
189
+ return true;
190
+ }
191
+ return normalizeRouteImports({
192
+ imports,
193
+ source,
194
+ s,
195
+ targetModule,
196
+ required,
197
+ lineEnding
189
198
  });
190
- if (importDeclarationsToRemove.length > 0) {
191
- appliedChanges = true;
192
- for (const importDeclaration of importDeclarationsToRemove) if (importDeclaration.specifiers?.length === 0) {
193
- const index = program.body.indexOf(importDeclaration);
194
- if (index !== -1) program.body.splice(index, 1);
199
+ }
200
+ function analyzeRouteImports(imports, required) {
201
+ const opposite = getOtherRouteConstructor(required);
202
+ let requiredCount = 0;
203
+ let oppositeCount = 0;
204
+ let renameCandidate;
205
+ for (const declaration of imports) for (const specifier of declaration.declaration.specifiers) {
206
+ if (!_babel_types.isImportSpecifier(specifier)) continue;
207
+ const imported = specifier.imported;
208
+ if (!_babel_types.isIdentifier(imported)) return { kind: "normalize" };
209
+ if (!isRouteConstructorName(imported.name)) continue;
210
+ if (specifier.local.name !== imported.name) return { kind: "normalize" };
211
+ if (imported.name === required) {
212
+ requiredCount++;
213
+ continue;
214
+ }
215
+ if (imported.name === opposite) {
216
+ oppositeCount++;
217
+ renameCandidate = {
218
+ imported,
219
+ local: specifier.local,
220
+ next: required
221
+ };
195
222
  }
196
223
  }
197
- if (!appliedChanges) return { result: "not-modified" };
198
- const printResult = (0, recast.print)(ast, {
199
- reuseWhitespace: true,
200
- sourceMapName: "output.map"
201
- });
202
- let transformedCode = printResult.code;
203
- if (printResult.map) transformedCode = await fixTransformedOutputText({
204
- originalCode: source,
205
- transformedCode,
206
- sourceMap: printResult.map,
207
- preferredQuote
208
- });
209
- return {
210
- result: "modified",
211
- output: transformedCode
224
+ if (requiredCount === 1 && oppositeCount === 0) return { kind: "ok" };
225
+ if (requiredCount === 0 && oppositeCount === 1 && renameCandidate) return {
226
+ kind: "rename",
227
+ ...renameCandidate
212
228
  };
229
+ return { kind: "normalize" };
213
230
  }
214
- async function fixTransformedOutputText({ originalCode, transformedCode, sourceMap, preferredQuote }) {
215
- const originalLines = originalCode.split("\n");
216
- const transformedLines = transformedCode.split("\n");
217
- const defaultUsesSemicolons = detectSemicolonUsage(originalCode);
218
- const consumer = await new source_map.SourceMapConsumer(sourceMap);
219
- return transformedLines.map((line, i) => {
220
- const transformedLineNum = i + 1;
221
- let origLineText = void 0;
222
- for (let col = 0; col < line.length; col++) {
223
- const mapped = consumer.originalPositionFor({
224
- line: transformedLineNum,
225
- column: col
226
- });
227
- if (mapped.line != null && mapped.line > 0) {
228
- origLineText = originalLines[mapped.line - 1];
229
- break;
230
- }
231
- }
232
- if (origLineText !== void 0) {
233
- if (origLineText === line) return origLineText;
234
- return fixLine(line, {
235
- originalLine: origLineText,
236
- useOriginalSemicolon: true,
237
- useOriginalQuotes: true,
238
- fallbackQuote: preferredQuote
239
- });
240
- } else return fixLine(line, {
241
- originalLine: null,
242
- useOriginalSemicolon: false,
243
- useOriginalQuotes: false,
244
- fallbackQuote: preferredQuote,
245
- fallbackSemicolon: defaultUsesSemicolons
231
+ function normalizeRouteImports({ imports, source, s, targetModule, required, lineEnding }) {
232
+ const owner = imports.find((declaration) => hasNamedImport(declaration.named, required)) ?? imports.find((declaration) => !declaration.namespace);
233
+ let modified = false;
234
+ for (const declaration of imports) {
235
+ const named = normalizeNamedImports({
236
+ named: declaration.named,
237
+ required,
238
+ isOwner: declaration === owner
246
239
  });
247
- }).join("\n");
240
+ if (sameNamedImports(declaration.named, named)) continue;
241
+ const replacement = renderImportDeclaration({
242
+ ...declaration,
243
+ named
244
+ });
245
+ if (replacement === null) {
246
+ s.remove(declaration.declaration.start, getRemovalEnd(source, declaration.declaration.end));
247
+ modified = true;
248
+ continue;
249
+ }
250
+ s.update(declaration.declaration.start, declaration.declaration.end, replacement);
251
+ modified = true;
252
+ }
253
+ if (!owner) {
254
+ const quote = imports[0]?.quote ?? "'";
255
+ const semicolon = imports[0]?.semicolon ?? false;
256
+ s.prepend(`import { ${required} } from ${quote}${targetModule}${quote}${semicolon ? ";" : ""}${lineEnding}`);
257
+ modified = true;
258
+ }
259
+ return modified;
248
260
  }
249
- function fixLine(line, { originalLine, useOriginalSemicolon, useOriginalQuotes, fallbackQuote, fallbackSemicolon = true }) {
250
- let result = line;
251
- if (useOriginalQuotes && originalLine) result = fixQuotes(result, originalLine, fallbackQuote);
252
- else if (!useOriginalQuotes && fallbackQuote) result = fixQuotesToPreferred(result, fallbackQuote);
253
- if (useOriginalSemicolon && originalLine) {
254
- const hadSemicolon = originalLine.trimEnd().endsWith(";");
255
- const hasSemicolon = result.trimEnd().endsWith(";");
256
- if (hadSemicolon && !hasSemicolon) result += ";";
257
- if (!hadSemicolon && hasSemicolon) result = result.replace(/;\s*$/, "");
258
- } else if (!useOriginalSemicolon) {
259
- const hasSemicolon = result.trimEnd().endsWith(";");
260
- if (!fallbackSemicolon && hasSemicolon) result = result.replace(/;\s*$/, "");
261
- if (fallbackSemicolon && !hasSemicolon && result.trim()) result += ";";
261
+ function normalizeNamedImports({ named, required, isOwner }) {
262
+ const banned = getOtherRouteConstructor(required);
263
+ const nextNamed = [];
264
+ const seen = /* @__PURE__ */ new Set();
265
+ for (const specifier of named) {
266
+ if (specifier.imported === banned) continue;
267
+ if (specifier.local === required && (specifier.imported !== required || !isOwner)) continue;
268
+ const key = `${specifier.importKind ?? "value"}:${specifier.imported}:${specifier.local}`;
269
+ if (seen.has(key)) continue;
270
+ seen.add(key);
271
+ nextNamed.push(specifier);
262
272
  }
263
- return result;
273
+ if (isOwner && !hasNamedImport(nextNamed, required)) nextNamed.push({
274
+ imported: required,
275
+ local: required
276
+ });
277
+ return nextNamed;
264
278
  }
265
- function fixQuotes(line, originalLine, fallbackQuote) {
266
- let originalQuote = detectQuoteFromLine(originalLine);
267
- if (!originalQuote) originalQuote = fallbackQuote;
268
- return fixQuotesToPreferred(line, originalQuote);
279
+ function getExpectedRouteConstructor(lazy) {
280
+ return lazy ? "createLazyFileRoute" : "createFileRoute";
269
281
  }
270
- function fixQuotesToPreferred(line, quote) {
271
- return line.replace(/(['"`])([^'"`\\]*(?:\\.[^'"`\\]*)*)\1/g, (_, q, content) => {
272
- return `${quote}${content.replaceAll(quote, `\\${quote}`)}${quote}`;
273
- });
282
+ function getOtherRouteConstructor(constructor) {
283
+ return constructor === "createFileRoute" ? "createLazyFileRoute" : "createFileRoute";
274
284
  }
275
- function detectQuoteFromLine(line) {
276
- const match = line.match(/(['"`])(?:\\.|[^\\])*?\1/);
277
- return match ? match[1] : null;
285
+ function hasNamedImport(named, required) {
286
+ return named.some((specifier) => specifier.imported === required && specifier.local === required && specifier.importKind !== "type");
278
287
  }
279
- function detectSemicolonUsage(code) {
280
- const lines = code.split("\n").map((l) => l.trim());
281
- const total = lines.length;
282
- return lines.filter((l) => l.endsWith(";")).length > total / 2;
288
+ function sameNamedImports(left, right) {
289
+ return left.length === right.length && left.every((specifier, index) => specifier.imported === right[index].imported && specifier.local === right[index].local && specifier.importKind === right[index].importKind);
283
290
  }
284
- function detectPreferredQuoteStyle(ast) {
285
- let single = 0;
286
- let double = 0;
287
- (0, recast.visit)(ast, { visitStringLiteral(path) {
288
- if (path.parent.node.type !== "JSXAttribute") {
289
- const raw = path.node.extra?.raw;
290
- if (raw?.startsWith("'")) single++;
291
- else if (raw?.startsWith("\"")) double++;
292
- }
293
- return false;
294
- } });
295
- if (single >= double) return "'";
296
- return "\"";
291
+ function isRouteConstructorName(value) {
292
+ return routeConstructors.includes(value);
293
+ }
294
+ function renderImportDeclaration(importDeclaration) {
295
+ const parts = [];
296
+ if (importDeclaration.defaultImport) parts.push(importDeclaration.defaultImport);
297
+ if (importDeclaration.namespace) parts.push(`* as ${importDeclaration.namespace}`);
298
+ if (importDeclaration.named.length > 0) parts.push(`{ ${importDeclaration.named.map((specifier) => `${specifier.importKind === "type" ? "type " : ""}${specifier.imported === specifier.local ? specifier.imported : `${specifier.imported} as ${specifier.local}`}`).join(", ")} }`);
299
+ if (parts.length === 0) return null;
300
+ return `import ${parts.join(", ")} from ${importDeclaration.quote}${importDeclaration.moduleName}${importDeclaration.quote}${importDeclaration.semicolon ? ";" : ""}`;
301
+ }
302
+ function getLineEnding(source) {
303
+ if (source.includes("\r\n")) return "\r\n";
304
+ if (source.includes("\n")) return "\n";
305
+ if (source.includes("\r")) return "\r";
306
+ return "\n";
307
+ }
308
+ function getRemovalEnd(source, end) {
309
+ let pos = end;
310
+ while (pos < source.length && (source[pos] === " " || source[pos] === " ")) pos++;
311
+ if (source[pos] === "\r" && source[pos + 1] === "\n") return pos + 2;
312
+ if (source[pos] === "\n" || source[pos] === "\r") return pos + 1;
313
+ return end;
297
314
  }
298
315
  //#endregion
299
316
  exports.transform = transform;