@dcoder-x/plugin-shared 0.1.8 → 0.1.9
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.
|
@@ -7,6 +7,10 @@ export declare class RouteExtractor {
|
|
|
7
7
|
private walkPagesDir;
|
|
8
8
|
private walkTanStackDir;
|
|
9
9
|
private extractReactRouterRoutes;
|
|
10
|
+
private buildImportMap;
|
|
11
|
+
private resolveRouteComponentFile;
|
|
12
|
+
private collectJSXComponentNames;
|
|
13
|
+
private resolveImportToFile;
|
|
10
14
|
private findFilesContaining;
|
|
11
15
|
private extractParams;
|
|
12
16
|
private findNearestLayout;
|
|
@@ -131,6 +131,7 @@ class RouteExtractor {
|
|
|
131
131
|
catch {
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
|
+
const importMap = this.buildImportMap(ast, filePath);
|
|
134
135
|
traverse(ast, {
|
|
135
136
|
JSXOpeningElement: (nodePath) => {
|
|
136
137
|
const name = nodePath.node.name?.name;
|
|
@@ -138,22 +139,102 @@ class RouteExtractor {
|
|
|
138
139
|
return;
|
|
139
140
|
const pathAttr = nodePath.node.attributes.find((a) => a.name?.name === 'path');
|
|
140
141
|
const routePath = pathAttr?.value?.value;
|
|
141
|
-
if (routePath)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
if (!routePath)
|
|
143
|
+
return;
|
|
144
|
+
const resolvedFilePath = this.resolveRouteComponentFile(nodePath, importMap) ?? filePath;
|
|
145
|
+
results.push({
|
|
146
|
+
path: routePath,
|
|
147
|
+
filePath: resolvedFilePath,
|
|
148
|
+
isDynamic: routePath.includes(':'),
|
|
149
|
+
params: (routePath.match(/:(\w+)/g) || []).map((p) => p.slice(1)),
|
|
150
|
+
layout: null,
|
|
151
|
+
routerType: 'react-router',
|
|
152
|
+
semantic: this.deriveSemanticMeaning(routePath),
|
|
153
|
+
});
|
|
152
154
|
},
|
|
153
155
|
});
|
|
154
156
|
}
|
|
155
157
|
return results;
|
|
156
158
|
}
|
|
159
|
+
buildImportMap(ast, fromFile) {
|
|
160
|
+
const map = new Map();
|
|
161
|
+
for (const node of ast.program.body) {
|
|
162
|
+
if (node.type !== 'ImportDeclaration')
|
|
163
|
+
continue;
|
|
164
|
+
const importSource = node.source?.value;
|
|
165
|
+
if (!importSource)
|
|
166
|
+
continue;
|
|
167
|
+
const resolved = this.resolveImportToFile(importSource, fromFile);
|
|
168
|
+
if (!resolved)
|
|
169
|
+
continue;
|
|
170
|
+
for (const specifier of node.specifiers ?? []) {
|
|
171
|
+
if (specifier.type === 'ImportDefaultSpecifier' ||
|
|
172
|
+
specifier.type === 'ImportSpecifier') {
|
|
173
|
+
const localName = specifier.local?.name;
|
|
174
|
+
if (localName)
|
|
175
|
+
map.set(localName, resolved);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return map;
|
|
180
|
+
}
|
|
181
|
+
resolveRouteComponentFile(routeOpeningPath, importMap) {
|
|
182
|
+
const elementAttr = routeOpeningPath.node.attributes.find((a) => a.name?.name === 'element');
|
|
183
|
+
if (!elementAttr)
|
|
184
|
+
return null;
|
|
185
|
+
const expr = elementAttr.value?.expression;
|
|
186
|
+
if (!expr)
|
|
187
|
+
return null;
|
|
188
|
+
// Try each JSX component name in document order; first match in the import map wins.
|
|
189
|
+
// Wrapper components like <Can> or <RouteGuard> won't be in the map, so we fall
|
|
190
|
+
// through to the actual page component nested inside them.
|
|
191
|
+
const componentNames = this.collectJSXComponentNames(expr);
|
|
192
|
+
for (const componentName of componentNames) {
|
|
193
|
+
const resolved = importMap.get(componentName);
|
|
194
|
+
if (resolved)
|
|
195
|
+
return resolved;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
collectJSXComponentNames(node) {
|
|
200
|
+
if (!node)
|
|
201
|
+
return [];
|
|
202
|
+
if (node.type === 'JSXElement') {
|
|
203
|
+
const tagName = node.openingElement?.name?.name ?? '';
|
|
204
|
+
const names = [];
|
|
205
|
+
if (tagName && /^[A-Z]/.test(tagName))
|
|
206
|
+
names.push(tagName);
|
|
207
|
+
for (const child of node.children ?? []) {
|
|
208
|
+
names.push(...this.collectJSXComponentNames(child));
|
|
209
|
+
}
|
|
210
|
+
return names;
|
|
211
|
+
}
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
resolveImportToFile(importSource, fromFile) {
|
|
215
|
+
if (!importSource.startsWith('.') && !importSource.startsWith('/'))
|
|
216
|
+
return null;
|
|
217
|
+
const base = importSource.startsWith('/')
|
|
218
|
+
? path_1.default.normalize(importSource)
|
|
219
|
+
: path_1.default.resolve(path_1.default.dirname(fromFile), importSource);
|
|
220
|
+
const candidates = [
|
|
221
|
+
base,
|
|
222
|
+
`${base}.ts`,
|
|
223
|
+
`${base}.tsx`,
|
|
224
|
+
`${base}.js`,
|
|
225
|
+
`${base}.jsx`,
|
|
226
|
+
path_1.default.join(base, 'index.ts'),
|
|
227
|
+
path_1.default.join(base, 'index.tsx'),
|
|
228
|
+
path_1.default.join(base, 'index.js'),
|
|
229
|
+
path_1.default.join(base, 'index.jsx'),
|
|
230
|
+
];
|
|
231
|
+
for (const candidate of candidates) {
|
|
232
|
+
if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isFile()) {
|
|
233
|
+
return path_1.default.normalize(candidate);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
157
238
|
findFilesContaining(dir, pattern) {
|
|
158
239
|
const results = [];
|
|
159
240
|
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
@@ -157,10 +157,10 @@ function findAttributeInsertIndex(source, range) {
|
|
|
157
157
|
if (!range)
|
|
158
158
|
return null;
|
|
159
159
|
const end = range[1];
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return
|
|
160
|
+
// Avoid searching backwards with lastIndexOf('>') — it hits '>' inside JSX expression
|
|
161
|
+
// containers (e.g. onClick={() => navigate('/')}). Use the AST range end directly.
|
|
162
|
+
const isSelfClosing = source[end - 2] === '/';
|
|
163
|
+
return isSelfClosing ? end - 2 : end - 1;
|
|
164
164
|
}
|
|
165
165
|
function applyEdits(source, edits) {
|
|
166
166
|
const sorted = edits.slice().sort((a, b) => b.index - a.index);
|