@ripast/vue 0.2.1 → 0.3.0
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.
- package/dist/index.d.mts +61 -1
- package/dist/index.mjs +1376 -18
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
-
import { extractTemplateExpressions, hyphenateVueName, isInsideAutoImportScope, posToLineCol, rewriteTemplateReferences, rgFiles, scan } from "@ripast/core/adapter";
|
|
3
|
+
import { extractTemplateExpressions, hyphenateVueName, isInsideAutoImportScope, parseSourceFile, parseVueTemplateAst, posToLineCol, rewriteTemplateReferences, rgFiles, rgFilesMany, scan } from "@ripast/core/adapter";
|
|
4
4
|
import { URI } from "vscode-uri";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { FileType, createLanguage, createLanguageService, createUriMap } from "@volar/language-service";
|
|
@@ -11,7 +11,67 @@ import ts from "typescript";
|
|
|
11
11
|
import { create } from "volar-service-typescript";
|
|
12
12
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
13
13
|
import { spawnSync } from "node:child_process";
|
|
14
|
+
function stringifyArg(arg) {
|
|
15
|
+
if (arg instanceof Error) return arg.message;
|
|
16
|
+
if (typeof arg === "string") return arg;
|
|
17
|
+
return String(arg);
|
|
18
|
+
}
|
|
19
|
+
function isNoisyDiagnostic(text) {
|
|
20
|
+
return text.includes("languageId not found") || text.includes(".d.ts.map") || text.includes(".d.mts.map") || text.includes(".d.cts.map");
|
|
21
|
+
}
|
|
22
|
+
function withFilteredConsoleWarn(fn) {
|
|
23
|
+
const original = console.warn;
|
|
24
|
+
console.warn = (...args) => {
|
|
25
|
+
const first = typeof args[0] === "string" ? args[0] : "";
|
|
26
|
+
if (first.startsWith("[Vue] Resolve plugin path failed") || first.startsWith("[Vue] Load plugin failed")) return;
|
|
27
|
+
if (first.startsWith("languageId not found")) return;
|
|
28
|
+
original.apply(console, args);
|
|
29
|
+
};
|
|
30
|
+
const restore = () => {
|
|
31
|
+
console.warn = original;
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
const result = fn();
|
|
35
|
+
if (result && typeof result.then === "function") return result.then((v) => {
|
|
36
|
+
restore();
|
|
37
|
+
return v;
|
|
38
|
+
}, (e) => {
|
|
39
|
+
restore();
|
|
40
|
+
throw e;
|
|
41
|
+
});
|
|
42
|
+
restore();
|
|
43
|
+
return result;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
restore();
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
14
49
|
function createVueService(tsconfigPath, cwd) {
|
|
50
|
+
const restore = installFilteredConsoleWarn();
|
|
51
|
+
const service = createVueServiceInternal(tsconfigPath, cwd);
|
|
52
|
+
const origDispose = service.dispose;
|
|
53
|
+
service.dispose = () => {
|
|
54
|
+
try {
|
|
55
|
+
origDispose();
|
|
56
|
+
} finally {
|
|
57
|
+
restore();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return service;
|
|
61
|
+
}
|
|
62
|
+
function installFilteredConsoleWarn() {
|
|
63
|
+
const original = console.warn;
|
|
64
|
+
console.warn = (...args) => {
|
|
65
|
+
const first = typeof args[0] === "string" ? args[0] : "";
|
|
66
|
+
if (first.startsWith("[Vue] Resolve plugin path failed") || first.startsWith("[Vue] Load plugin failed")) return;
|
|
67
|
+
if (first.startsWith("languageId not found")) return;
|
|
68
|
+
original.apply(console, args);
|
|
69
|
+
};
|
|
70
|
+
return () => {
|
|
71
|
+
console.warn = original;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function createVueServiceInternal(tsconfigPath, cwd) {
|
|
15
75
|
const commandLine = createParsedCommandLine(ts, ts.sys, tsconfigPath);
|
|
16
76
|
const vueExts = getAllExtensions(commandLine.vueOptions).map((ext) => {
|
|
17
77
|
return {
|
|
@@ -72,8 +132,12 @@ function createVueService(tsconfigPath, cwd) {
|
|
|
72
132
|
console: {
|
|
73
133
|
log() {},
|
|
74
134
|
warn() {},
|
|
75
|
-
|
|
76
|
-
|
|
135
|
+
info() {},
|
|
136
|
+
error(...args) {
|
|
137
|
+
const text = args.map(stringifyArg).join(" ");
|
|
138
|
+
if (isNoisyDiagnostic(text)) return;
|
|
139
|
+
process.stderr.write(`${text}\n`);
|
|
140
|
+
}
|
|
77
141
|
}
|
|
78
142
|
};
|
|
79
143
|
const project = { typescript: {
|
|
@@ -133,7 +197,7 @@ function workspaceEditToChanges(edit, vue, cwd, filter) {
|
|
|
133
197
|
function workspaceRelativePath(fileName, cwd) {
|
|
134
198
|
return fileName.startsWith(cwd) ? fileName.slice(cwd.length).replace(/^[/\\]/, "") : fileName;
|
|
135
199
|
}
|
|
136
|
-
function hasVueFilesContaining
|
|
200
|
+
function hasVueFilesContaining(cwd, pattern) {
|
|
137
201
|
return listVueFilesContaining(cwd, pattern).length > 0;
|
|
138
202
|
}
|
|
139
203
|
function listVueFiles(cwd) {
|
|
@@ -159,9 +223,6 @@ function rgVueFiles(cwd, args) {
|
|
|
159
223
|
encoding: "utf8"
|
|
160
224
|
}).stdout.split("\n").filter(Boolean).map((path) => resolve(cwd, path));
|
|
161
225
|
}
|
|
162
|
-
function hasVueFilesContaining(cwd, pattern) {
|
|
163
|
-
return hasVueFilesContaining$1(cwd, pattern);
|
|
164
|
-
}
|
|
165
226
|
async function applyVueRename(tsconfigPath, cwd, from, to, sites) {
|
|
166
227
|
const vue = createVueService(tsconfigPath, cwd);
|
|
167
228
|
try {
|
|
@@ -236,11 +297,11 @@ async function vueRegressions(tsconfigPath, cwd, pendingChanges) {
|
|
|
236
297
|
if (!vueFiles.length) return [];
|
|
237
298
|
const vue = createVueService(tsconfigPath, cwd);
|
|
238
299
|
try {
|
|
239
|
-
const baselinePairs = await Promise.all(vueFiles.map(async (file) => [file, await collectDiagKeys(vue, file)]));
|
|
300
|
+
const baselinePairs = await withFilteredConsoleWarn(() => Promise.all(vueFiles.map(async (file) => [file, await collectDiagKeys(vue, file)])));
|
|
240
301
|
const baseline = new Map(baselinePairs);
|
|
241
302
|
for (const c of pendingChanges) vue.setSnapshot(c.path, c.after);
|
|
242
303
|
const checkSet = [...new Set([...vueFiles, ...pendingChanges.filter((c) => c.path.endsWith(".vue")).map((c) => c.path)])];
|
|
243
|
-
const postPairs = await Promise.all(checkSet.map(async (file) => [file, await getDiags(vue, file)]));
|
|
304
|
+
const postPairs = await withFilteredConsoleWarn(() => Promise.all(checkSet.map(async (file) => [file, await getDiags(vue, file)])));
|
|
244
305
|
const out = [];
|
|
245
306
|
for (const [file, post] of postPairs) {
|
|
246
307
|
const before = baseline.get(file) ?? /* @__PURE__ */ new Set();
|
|
@@ -273,7 +334,531 @@ async function getDiags(vue, fileName) {
|
|
|
273
334
|
function diagKey(d) {
|
|
274
335
|
return `${d.range.start.line}:${d.range.start.character}:${d.code ?? ""}:${d.message}`;
|
|
275
336
|
}
|
|
276
|
-
|
|
337
|
+
function parseComponent(absPath) {
|
|
338
|
+
return parseComponentSource(absPath, readFileSync(absPath, "utf8"));
|
|
339
|
+
}
|
|
340
|
+
function parseComponentSource(absPath, source) {
|
|
341
|
+
const empty = {
|
|
342
|
+
props: [],
|
|
343
|
+
emits: [],
|
|
344
|
+
slots: [],
|
|
345
|
+
exposes: [],
|
|
346
|
+
scriptLang: null,
|
|
347
|
+
setup: false,
|
|
348
|
+
propsResolution: "literal"
|
|
349
|
+
};
|
|
350
|
+
if (!absPath.endsWith(".vue")) {
|
|
351
|
+
const program = safeParse(absPath, source);
|
|
352
|
+
if (!program) return empty;
|
|
353
|
+
return walkProgram(program, {
|
|
354
|
+
...empty,
|
|
355
|
+
scriptLang: absPath.endsWith(".ts") || absPath.endsWith(".tsx") ? "ts" : "js"
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
const block = extractScriptBlock(source);
|
|
359
|
+
if (!block) return empty;
|
|
360
|
+
const program = safeParse(`${absPath}.ts`, block.code);
|
|
361
|
+
if (!program) return {
|
|
362
|
+
...empty,
|
|
363
|
+
scriptLang: block.lang,
|
|
364
|
+
setup: block.setup
|
|
365
|
+
};
|
|
366
|
+
return walkProgram(program, {
|
|
367
|
+
...empty,
|
|
368
|
+
scriptLang: block.lang,
|
|
369
|
+
setup: block.setup
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const SCRIPT_TAG_RE$1 = /<script(\s[^>]*)?>([\s\S]*?)<\/script>/gi;
|
|
373
|
+
function extractScriptBlock(source) {
|
|
374
|
+
const blocks = [];
|
|
375
|
+
for (const m of source.matchAll(SCRIPT_TAG_RE$1)) {
|
|
376
|
+
const attrs = m[1] ?? "";
|
|
377
|
+
const code = m[2] ?? "";
|
|
378
|
+
if (/\bsrc\s*=/.test(attrs)) continue;
|
|
379
|
+
const lang = /\blang\s*=\s*["']ts["']/.test(attrs) ? "ts" : "js";
|
|
380
|
+
const setup = /\bsetup\b/.test(attrs);
|
|
381
|
+
blocks.push({
|
|
382
|
+
code,
|
|
383
|
+
lang,
|
|
384
|
+
setup
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (!blocks.length) return null;
|
|
388
|
+
return blocks.find((b) => b.setup) ?? blocks.reduce((a, b) => b.code.length > a.code.length ? b : a);
|
|
389
|
+
}
|
|
390
|
+
function safeParse(path, code) {
|
|
391
|
+
try {
|
|
392
|
+
return parseSourceFile(path, code).program;
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function walkProgram(program, base) {
|
|
398
|
+
let propsResolution = "literal";
|
|
399
|
+
const props = [];
|
|
400
|
+
const emits = [];
|
|
401
|
+
const slots = [];
|
|
402
|
+
const exposes = [];
|
|
403
|
+
const withDefaultsKeys = /* @__PURE__ */ new Set();
|
|
404
|
+
walkAst(program, (node) => {
|
|
405
|
+
if (node.type === "CallExpression") switch (calleeName(node)) {
|
|
406
|
+
case "defineProps": {
|
|
407
|
+
const result = extractProps(node);
|
|
408
|
+
if (result.unresolved) propsResolution = "unresolved";
|
|
409
|
+
for (const p of result.props) props.push(p);
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
case "defineEmits": {
|
|
413
|
+
const result = extractEmits(node);
|
|
414
|
+
if (result.unresolved) propsResolution = "unresolved";
|
|
415
|
+
for (const e of result.emits) emits.push(e);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case "defineSlots": {
|
|
419
|
+
const result = extractStringKeyedTypeLiteral(node);
|
|
420
|
+
if (result.unresolved) propsResolution = "unresolved";
|
|
421
|
+
for (const s of result.keys) slots.push(s);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case "defineExpose": {
|
|
425
|
+
const result = extractExpose(node);
|
|
426
|
+
if (result.unresolved) propsResolution = "unresolved";
|
|
427
|
+
for (const e of result.keys) exposes.push(e);
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case "withDefaults": {
|
|
431
|
+
const second = node.arguments?.[1];
|
|
432
|
+
if (second?.type === "ObjectExpression") for (const prop of second.properties ?? []) {
|
|
433
|
+
const key = propKey(prop);
|
|
434
|
+
if (key) withDefaultsKeys.add(key);
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
case "defineComponent": {
|
|
439
|
+
const arg = node.arguments?.[0];
|
|
440
|
+
if (arg?.type === "ObjectExpression") for (const prop of arg.properties ?? []) {
|
|
441
|
+
const key = propKey(prop);
|
|
442
|
+
if (key === "props") {
|
|
443
|
+
const r = extractPropsFromOptionsValue(prop.value);
|
|
444
|
+
if (r.unresolved) propsResolution = "unresolved";
|
|
445
|
+
for (const p of r.props) props.push(p);
|
|
446
|
+
} else if (key === "emits") {
|
|
447
|
+
const r = extractEmitsFromValue(prop.value);
|
|
448
|
+
if (r.unresolved) propsResolution = "unresolved";
|
|
449
|
+
for (const e of r.emits) emits.push(e);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
for (const p of props) if (withDefaultsKeys.has(p.name)) p.hasDefault = true;
|
|
457
|
+
return {
|
|
458
|
+
...base,
|
|
459
|
+
props: dedupePropsByName(props),
|
|
460
|
+
emits: [...new Set(emits)].sort(),
|
|
461
|
+
slots: [...new Set(slots)].sort(),
|
|
462
|
+
exposes: [...new Set(exposes)].sort(),
|
|
463
|
+
propsResolution
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function calleeName(node) {
|
|
467
|
+
const callee = node.callee;
|
|
468
|
+
if (callee?.type === "Identifier") return callee.name;
|
|
469
|
+
if (callee?.type === "MemberExpression" && callee.property?.type === "Identifier" && !callee.computed) return callee.property.name;
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
function propKey(prop) {
|
|
473
|
+
if (prop?.type !== "Property" && prop?.type !== "ObjectProperty") return null;
|
|
474
|
+
if (!prop.key) return null;
|
|
475
|
+
if (prop.computed) return null;
|
|
476
|
+
if (prop.key.type === "Identifier") return prop.key.name;
|
|
477
|
+
if (prop.key.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
function extractProps(callNode) {
|
|
481
|
+
const typeArg = callNode.typeArguments?.params?.[0] ?? callNode.typeParameters?.params?.[0];
|
|
482
|
+
if (typeArg) return propsFromTypeLiteral(typeArg);
|
|
483
|
+
const arg = callNode.arguments?.[0];
|
|
484
|
+
if (!arg) return {
|
|
485
|
+
props: [],
|
|
486
|
+
unresolved: false
|
|
487
|
+
};
|
|
488
|
+
return extractPropsFromOptionsValue(arg);
|
|
489
|
+
}
|
|
490
|
+
function extractPropsFromOptionsValue(arg) {
|
|
491
|
+
if (!arg) return {
|
|
492
|
+
props: [],
|
|
493
|
+
unresolved: false
|
|
494
|
+
};
|
|
495
|
+
if (arg.type === "ArrayExpression") {
|
|
496
|
+
const out = [];
|
|
497
|
+
for (const el of arg.elements ?? []) if (el?.type === "Literal" && typeof el.value === "string") out.push({ name: el.value });
|
|
498
|
+
return {
|
|
499
|
+
props: out,
|
|
500
|
+
unresolved: false
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (arg.type === "ObjectExpression") {
|
|
504
|
+
const out = [];
|
|
505
|
+
for (const prop of arg.properties ?? []) {
|
|
506
|
+
const key = propKey(prop);
|
|
507
|
+
if (!key) continue;
|
|
508
|
+
const sig = { name: key };
|
|
509
|
+
const value = prop.value;
|
|
510
|
+
if (value?.type === "ObjectExpression") for (const inner of value.properties ?? []) {
|
|
511
|
+
const innerKey = propKey(inner);
|
|
512
|
+
if (innerKey === "required" && inner.value?.type === "Literal") sig.required = !!inner.value.value;
|
|
513
|
+
if (innerKey === "default") sig.hasDefault = true;
|
|
514
|
+
if (innerKey === "type") sig.type = stringifyTypeNode(inner.value);
|
|
515
|
+
}
|
|
516
|
+
else if (value) sig.type = stringifyTypeNode(value);
|
|
517
|
+
out.push(sig);
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
props: out,
|
|
521
|
+
unresolved: false
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
props: [],
|
|
526
|
+
unresolved: true
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function propsFromTypeLiteral(typeNode) {
|
|
530
|
+
if (typeNode?.type !== "TSTypeLiteral") return {
|
|
531
|
+
props: [],
|
|
532
|
+
unresolved: true
|
|
533
|
+
};
|
|
534
|
+
const out = [];
|
|
535
|
+
for (const member of typeNode.members ?? []) {
|
|
536
|
+
if (member.type !== "TSPropertySignature") continue;
|
|
537
|
+
const name = member.key?.type === "Identifier" ? member.key.name : member.key?.type === "Literal" && typeof member.key.value === "string" ? member.key.value : null;
|
|
538
|
+
if (!name) continue;
|
|
539
|
+
out.push({
|
|
540
|
+
name,
|
|
541
|
+
required: !member.optional,
|
|
542
|
+
type: member.typeAnnotation?.typeAnnotation ? stringifyTypeNode(member.typeAnnotation.typeAnnotation) : void 0
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
props: out,
|
|
547
|
+
unresolved: false
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function extractEmits(callNode) {
|
|
551
|
+
const typeArg = callNode.typeArguments?.params?.[0] ?? callNode.typeParameters?.params?.[0];
|
|
552
|
+
if (typeArg) {
|
|
553
|
+
if (typeArg.type === "TSTypeLiteral") {
|
|
554
|
+
const out = [];
|
|
555
|
+
for (const member of typeArg.members ?? []) if (member.type === "TSCallSignatureDeclaration") {
|
|
556
|
+
const literal = (member.params?.[0])?.typeAnnotation?.typeAnnotation;
|
|
557
|
+
if (literal?.type === "TSLiteralType" && literal.literal?.type === "Literal" && typeof literal.literal.value === "string") out.push(literal.literal.value);
|
|
558
|
+
} else if (member.type === "TSPropertySignature" && member.key?.type === "Identifier") out.push(member.key.name);
|
|
559
|
+
return {
|
|
560
|
+
emits: out,
|
|
561
|
+
unresolved: false
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
emits: [],
|
|
566
|
+
unresolved: true
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
const arg = callNode.arguments?.[0];
|
|
570
|
+
return extractEmitsFromValue(arg);
|
|
571
|
+
}
|
|
572
|
+
function extractEmitsFromValue(arg) {
|
|
573
|
+
if (!arg) return {
|
|
574
|
+
emits: [],
|
|
575
|
+
unresolved: false
|
|
576
|
+
};
|
|
577
|
+
if (arg.type === "ArrayExpression") {
|
|
578
|
+
const out = [];
|
|
579
|
+
for (const el of arg.elements ?? []) if (el?.type === "Literal" && typeof el.value === "string") out.push(el.value);
|
|
580
|
+
return {
|
|
581
|
+
emits: out,
|
|
582
|
+
unresolved: false
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
if (arg.type === "ObjectExpression") {
|
|
586
|
+
const out = [];
|
|
587
|
+
for (const prop of arg.properties ?? []) {
|
|
588
|
+
const key = propKey(prop);
|
|
589
|
+
if (key) out.push(key);
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
emits: out,
|
|
593
|
+
unresolved: false
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
emits: [],
|
|
598
|
+
unresolved: true
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function extractStringKeyedTypeLiteral(callNode) {
|
|
602
|
+
const typeArg = callNode.typeArguments?.params?.[0] ?? callNode.typeParameters?.params?.[0];
|
|
603
|
+
if (typeArg?.type === "TSTypeLiteral") {
|
|
604
|
+
const out = [];
|
|
605
|
+
for (const member of typeArg.members ?? []) {
|
|
606
|
+
if (member.type !== "TSPropertySignature" && member.type !== "TSMethodSignature") continue;
|
|
607
|
+
if (member.key?.type === "Identifier") out.push(member.key.name);
|
|
608
|
+
else if (member.key?.type === "Literal" && typeof member.key.value === "string") out.push(member.key.value);
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
keys: out,
|
|
612
|
+
unresolved: false
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
keys: [],
|
|
617
|
+
unresolved: !!typeArg
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function extractExpose(callNode) {
|
|
621
|
+
const arg = callNode.arguments?.[0];
|
|
622
|
+
if (arg?.type === "ObjectExpression") {
|
|
623
|
+
const out = [];
|
|
624
|
+
for (const prop of arg.properties ?? []) {
|
|
625
|
+
const key = propKey(prop);
|
|
626
|
+
if (key) out.push(key);
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
keys: out,
|
|
630
|
+
unresolved: false
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
keys: [],
|
|
635
|
+
unresolved: !!arg
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function stringifyTypeNode(node) {
|
|
639
|
+
if (!node) return void 0;
|
|
640
|
+
if (node.type === "Identifier") return node.name;
|
|
641
|
+
if (node.type === "TSTypeReference" && node.typeName?.type === "Identifier") return node.typeName.name;
|
|
642
|
+
if (node.type === "TSStringKeyword") return "String";
|
|
643
|
+
if (node.type === "TSNumberKeyword") return "Number";
|
|
644
|
+
if (node.type === "TSBooleanKeyword") return "Boolean";
|
|
645
|
+
if (node.type === "TSArrayType") return `${stringifyTypeNode(node.elementType) ?? "?"}[]`;
|
|
646
|
+
}
|
|
647
|
+
function walkAst(node, visit) {
|
|
648
|
+
if (!node || typeof node !== "object") return;
|
|
649
|
+
if (typeof node.type === "string") visit(node);
|
|
650
|
+
for (const key in node) {
|
|
651
|
+
if (key === "parent" || key === "loc" || key === "range") continue;
|
|
652
|
+
const value = node[key];
|
|
653
|
+
if (Array.isArray(value)) for (const child of value) walkAst(child, visit);
|
|
654
|
+
else if (value && typeof value === "object" && typeof value.type === "string") walkAst(value, visit);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function dedupePropsByName(props) {
|
|
658
|
+
const out = /* @__PURE__ */ new Map();
|
|
659
|
+
for (const p of props) {
|
|
660
|
+
const existing = out.get(p.name);
|
|
661
|
+
if (!existing) {
|
|
662
|
+
out.set(p.name, p);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
out.set(p.name, {
|
|
666
|
+
name: p.name,
|
|
667
|
+
type: existing.type ?? p.type,
|
|
668
|
+
required: existing.required ?? p.required,
|
|
669
|
+
hasDefault: existing.hasDefault || p.hasDefault
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
return [...out.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
673
|
+
}
|
|
674
|
+
const NODE_ELEMENT$1 = 1;
|
|
675
|
+
const NODE_DIRECTIVE = 7;
|
|
676
|
+
const ATTRIBUTE = 6;
|
|
677
|
+
const DEFAULT_GLOB = [
|
|
678
|
+
"*.vue",
|
|
679
|
+
"*.ts",
|
|
680
|
+
"*.tsx",
|
|
681
|
+
"*.js",
|
|
682
|
+
"*.jsx",
|
|
683
|
+
"*.mjs"
|
|
684
|
+
];
|
|
685
|
+
function findComponentUsages(names, opts = {}) {
|
|
686
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
687
|
+
const aliasIndex = buildAliasIndex(names);
|
|
688
|
+
const candidates = candidateFiles(cwd, aliasIndex, opts.glob);
|
|
689
|
+
const out = [];
|
|
690
|
+
for (const abs of candidates) {
|
|
691
|
+
const source = readOrEmpty(abs);
|
|
692
|
+
if (!source) continue;
|
|
693
|
+
if (abs.endsWith(".vue")) collectFromSfc(abs, source, cwd, aliasIndex, out);
|
|
694
|
+
collectFromScript(abs, source, cwd, aliasIndex, out);
|
|
695
|
+
}
|
|
696
|
+
return out.sort(sortUsages);
|
|
697
|
+
}
|
|
698
|
+
function buildAliasIndex(names) {
|
|
699
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
700
|
+
const byString = /* @__PURE__ */ new Set();
|
|
701
|
+
const search = /* @__PURE__ */ new Set();
|
|
702
|
+
for (const name of names) {
|
|
703
|
+
byTag.set(name, name);
|
|
704
|
+
const kebab = hyphenateVueName(name);
|
|
705
|
+
if (kebab !== name) byTag.set(kebab, name);
|
|
706
|
+
byString.add(name);
|
|
707
|
+
search.add(name);
|
|
708
|
+
if (kebab !== name) search.add(kebab);
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
byTag,
|
|
712
|
+
byString,
|
|
713
|
+
search: [...search]
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function candidateFiles(cwd, alias, glob) {
|
|
717
|
+
const globs = glob ? Array.isArray(glob) ? glob : [glob] : DEFAULT_GLOB;
|
|
718
|
+
return rgFilesMany(alias.search, {
|
|
719
|
+
cwd,
|
|
720
|
+
glob: globs
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
function readOrEmpty(abs) {
|
|
724
|
+
try {
|
|
725
|
+
return readFileSync(abs, "utf8");
|
|
726
|
+
} catch {
|
|
727
|
+
return "";
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function collectFromSfc(abs, source, cwd, alias, out) {
|
|
731
|
+
const ast = parseVueTemplateAst(source);
|
|
732
|
+
if (!ast) return;
|
|
733
|
+
walkTemplate(ast, (node) => {
|
|
734
|
+
if (node.type !== NODE_ELEMENT$1) return;
|
|
735
|
+
const canonical = alias.byTag.get(node.tag);
|
|
736
|
+
if (canonical) {
|
|
737
|
+
const { line, col } = posToLineCol(source, node.loc.start.offset + 1);
|
|
738
|
+
out.push({
|
|
739
|
+
name: canonical,
|
|
740
|
+
file: abs,
|
|
741
|
+
rel: relative(cwd, abs),
|
|
742
|
+
line,
|
|
743
|
+
col,
|
|
744
|
+
form: /^[a-z]/.test(node.tag) ? "tag-kebab" : "tag-pascal"
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
if (node.tag === "component") {
|
|
748
|
+
const isProp = (node.props ?? []).find((p) => isIsDirective(p));
|
|
749
|
+
if (isProp) collectDynamicIs(abs, source, cwd, isProp, alias, out);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
function isIsDirective(prop) {
|
|
754
|
+
if (!prop) return false;
|
|
755
|
+
if (prop.type === ATTRIBUTE && prop.name === "is") return true;
|
|
756
|
+
if (prop.type === NODE_DIRECTIVE && prop.name === "bind" && prop.arg?.content === "is") return true;
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
function collectDynamicIs(abs, source, cwd, prop, alias, out) {
|
|
760
|
+
if (prop.type === ATTRIBUTE) {
|
|
761
|
+
const value = prop.value?.content;
|
|
762
|
+
if (typeof value === "string" && alias.byString.has(value)) {
|
|
763
|
+
const offset = prop.value.loc.start.offset;
|
|
764
|
+
const { line, col } = posToLineCol(source, offset);
|
|
765
|
+
out.push({
|
|
766
|
+
name: value,
|
|
767
|
+
file: abs,
|
|
768
|
+
rel: relative(cwd, abs),
|
|
769
|
+
line,
|
|
770
|
+
col,
|
|
771
|
+
form: "dynamic-is-literal"
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const exp = prop.exp;
|
|
777
|
+
if (!exp?.content) return;
|
|
778
|
+
const trimmed = exp.content.trim();
|
|
779
|
+
const literal = trimmed.match(/^['"]([^'"]+)['"]$/);
|
|
780
|
+
if (literal) {
|
|
781
|
+
const name = literal[1];
|
|
782
|
+
if (alias.byString.has(name)) {
|
|
783
|
+
const offset = exp.loc.start.offset;
|
|
784
|
+
const { line, col } = posToLineCol(source, offset);
|
|
785
|
+
out.push({
|
|
786
|
+
name,
|
|
787
|
+
file: abs,
|
|
788
|
+
rel: relative(cwd, abs),
|
|
789
|
+
line,
|
|
790
|
+
col,
|
|
791
|
+
form: "dynamic-is-literal"
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const offset = exp.loc.start.offset;
|
|
797
|
+
const { line, col } = posToLineCol(source, offset);
|
|
798
|
+
out.push({
|
|
799
|
+
name: "*",
|
|
800
|
+
file: abs,
|
|
801
|
+
rel: relative(cwd, abs),
|
|
802
|
+
line,
|
|
803
|
+
col,
|
|
804
|
+
form: "dynamic-is-binding",
|
|
805
|
+
binding: trimmed
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
function walkTemplate(node, visit) {
|
|
809
|
+
if (!node || typeof node !== "object") return;
|
|
810
|
+
if (typeof node.type === "number") visit(node);
|
|
811
|
+
for (const child of node.children ?? []) walkTemplate(child, visit);
|
|
812
|
+
}
|
|
813
|
+
function collectFromScript(abs, source, cwd, alias, out) {
|
|
814
|
+
const program = parseSourceFile(abs.endsWith(".vue") ? `${abs}.ts` : abs, abs.endsWith(".vue") ? extractScriptOnly(source) ?? "" : source).program;
|
|
815
|
+
if (!program) return;
|
|
816
|
+
const scriptOffset = abs.endsWith(".vue") ? scriptOffsetIn(source) : 0;
|
|
817
|
+
walkScript(program, (node) => {
|
|
818
|
+
if (node.type !== "CallExpression") return;
|
|
819
|
+
const callee = node.callee;
|
|
820
|
+
if (!(callee?.type === "Identifier" && callee.name === "resolveComponent")) return;
|
|
821
|
+
const arg = node.arguments?.[0];
|
|
822
|
+
if (arg?.type !== "Literal" || typeof arg.value !== "string") return;
|
|
823
|
+
if (!alias.byString.has(arg.value)) return;
|
|
824
|
+
const { line, col } = posToLineCol(source, (arg.start ?? 0) + scriptOffset);
|
|
825
|
+
out.push({
|
|
826
|
+
name: arg.value,
|
|
827
|
+
file: abs,
|
|
828
|
+
rel: relative(cwd, abs),
|
|
829
|
+
line,
|
|
830
|
+
col,
|
|
831
|
+
form: "resolveComponent"
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
function walkScript(node, visit) {
|
|
836
|
+
if (!node || typeof node !== "object") return;
|
|
837
|
+
if (typeof node.type === "string") visit(node);
|
|
838
|
+
for (const key in node) {
|
|
839
|
+
if (key === "parent" || key === "loc" || key === "range") continue;
|
|
840
|
+
const value = node[key];
|
|
841
|
+
if (Array.isArray(value)) for (const child of value) walkScript(child, visit);
|
|
842
|
+
else if (value && typeof value === "object" && typeof value.type === "string") walkScript(value, visit);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const SCRIPT_TAG_RE = /<script(\s[^>]*)?>([\s\S]*?)<\/script>/i;
|
|
846
|
+
function extractScriptOnly(source) {
|
|
847
|
+
const m = source.match(SCRIPT_TAG_RE);
|
|
848
|
+
return m ? m[2] ?? null : null;
|
|
849
|
+
}
|
|
850
|
+
function scriptOffsetIn(source) {
|
|
851
|
+
const m = source.match(/<script(\s[^>]*)?>/i);
|
|
852
|
+
if (!m || m.index == null) return 0;
|
|
853
|
+
return m.index + m[0].length;
|
|
854
|
+
}
|
|
855
|
+
function sortUsages(a, b) {
|
|
856
|
+
return a.rel.localeCompare(b.rel) || a.line - b.line || a.col - b.col;
|
|
857
|
+
}
|
|
858
|
+
function findComponentUsage(name, opts = {}) {
|
|
859
|
+
return findComponentUsages([name], opts);
|
|
860
|
+
}
|
|
861
|
+
const MODULE_EXT_RE$1 = /\.(?:tsx?|jsx?|mts|cts|mjs|cjs|vue)$/;
|
|
277
862
|
const WIN_SEP_RE = /\\/g;
|
|
278
863
|
function isGeneratedNuxtPath(cwd, filePath) {
|
|
279
864
|
const rel = relative(cwd, filePath).replace(/\\/g, "/");
|
|
@@ -341,6 +926,40 @@ function readTsconfigPaths(tsconfigPath) {
|
|
|
341
926
|
function stripJsonComments(source) {
|
|
342
927
|
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
343
928
|
}
|
|
929
|
+
function loadConsumerLocalAliases(consumerFile, workspaceCwd) {
|
|
930
|
+
const workspaceNorm = workspaceCwd.replace(WIN_SEP_RE, "/");
|
|
931
|
+
let dir = dirname(consumerFile);
|
|
932
|
+
while (true) {
|
|
933
|
+
const tsconfig = join(dir, ".nuxt/tsconfig.json");
|
|
934
|
+
const dirNorm = dir.replace(WIN_SEP_RE, "/");
|
|
935
|
+
if (dirNorm !== workspaceNorm && existsSync(tsconfig)) return readTsconfigPaths(tsconfig);
|
|
936
|
+
if (dirNorm === workspaceNorm || dirNorm === "/" || dirNorm === "") return [];
|
|
937
|
+
const parent = dirname(dir);
|
|
938
|
+
if (parent === dir) return [];
|
|
939
|
+
dir = parent;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function aliasResolvesToTarget(consumerAliases, specifier, targetFile) {
|
|
943
|
+
const targetNorm = targetFile.replace(WIN_SEP_RE, "/");
|
|
944
|
+
for (const alias of consumerAliases) {
|
|
945
|
+
const base = alias.wildcard ? alias.pattern.replace(/\/\*$/, "") : alias.pattern;
|
|
946
|
+
if (!alias.wildcard) {
|
|
947
|
+
if (specifier !== alias.pattern) continue;
|
|
948
|
+
if (alias.targets.some((t) => t.replace(WIN_SEP_RE, "/") === targetNorm)) return true;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (specifier !== base && !specifier.startsWith(`${base}/`)) continue;
|
|
952
|
+
const remainder = specifier === base ? "" : specifier.slice(base.length + 1);
|
|
953
|
+
for (const target of alias.targets) {
|
|
954
|
+
const normTarget = target.replace(WIN_SEP_RE, "/");
|
|
955
|
+
const candidate = remainder ? `${normTarget}/${remainder}` : normTarget;
|
|
956
|
+
if (targetNorm === candidate) return true;
|
|
957
|
+
if (stripModuleExt(targetNorm) === candidate) return true;
|
|
958
|
+
if (targetNorm === `${candidate}/index.ts` || targetNorm === `${candidate}/index.js` || targetNorm === `${candidate}/index.vue`) return true;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
344
963
|
function resolveBestImportSpecifier(fromFile, toFile, aliases, oldSpec) {
|
|
345
964
|
const targetAlias = pickAliasFor(toFile, aliases);
|
|
346
965
|
const sourceAlias = pickAliasFor(fromFile, aliases);
|
|
@@ -376,18 +995,667 @@ function pickAliasFor(file, aliases) {
|
|
|
376
995
|
function formatAliasSpecifier(match, oldSpec) {
|
|
377
996
|
if (!match.wildcard) return match.pattern;
|
|
378
997
|
const base = match.pattern.replace(/\/\*$/, "");
|
|
379
|
-
const tail = oldSpec !== void 0 && MODULE_EXT_RE.test(oldSpec) ? match.remainder : match.remainder.replace(MODULE_EXT_RE, "");
|
|
998
|
+
const tail = oldSpec !== void 0 && MODULE_EXT_RE$1.test(oldSpec) ? match.remainder : match.remainder.replace(MODULE_EXT_RE$1, "");
|
|
380
999
|
return tail ? `${base}/${tail}` : base;
|
|
381
1000
|
}
|
|
382
1001
|
function formatRelativeSpecifier(fromFile, toFile, oldSpec) {
|
|
383
|
-
const hasExt = oldSpec !== void 0 && MODULE_EXT_RE.test(oldSpec);
|
|
1002
|
+
const hasExt = oldSpec !== void 0 && MODULE_EXT_RE$1.test(oldSpec);
|
|
384
1003
|
let rel = relative(dirname(fromFile), toFile).replace(WIN_SEP_RE, "/");
|
|
385
|
-
if (!hasExt) rel = rel.replace(MODULE_EXT_RE, "");
|
|
1004
|
+
if (!hasExt) rel = rel.replace(MODULE_EXT_RE$1, "");
|
|
386
1005
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
387
1006
|
return rel;
|
|
388
1007
|
}
|
|
389
1008
|
function stripModuleExt(path) {
|
|
390
|
-
return path.replace(MODULE_EXT_RE, "");
|
|
1009
|
+
return path.replace(MODULE_EXT_RE$1, "");
|
|
1010
|
+
}
|
|
1011
|
+
const MANIFEST_PATH = ".nuxt/components.d.ts";
|
|
1012
|
+
const MANIFEST_ENTRY_RE = /^\s*(?:export\s+const\s+)?([A-Z]\w*)\s*:\s*(?:LazyComponent<\s*)?typeof\s+import\(\s*["']([^"']+)["']\s*\)\s*\[\s*["']default["']\s*\]/gm;
|
|
1013
|
+
const COMPONENT_DIR_NAMES = new Set(["components"]);
|
|
1014
|
+
const DEFAULT_VUE_GLOB = ["*.vue"];
|
|
1015
|
+
function listComponents(cwd, opts = {}) {
|
|
1016
|
+
const source = opts.source ?? "auto";
|
|
1017
|
+
const warn = opts.warn ?? ((msg) => process.stderr.write(`${msg}\n`));
|
|
1018
|
+
const manifestPath = join(cwd, MANIFEST_PATH);
|
|
1019
|
+
const manifestPresent = existsSync(manifestPath);
|
|
1020
|
+
if (source === "manifest" || source === "auto" && manifestPresent) {
|
|
1021
|
+
if (!manifestPresent) {
|
|
1022
|
+
warn(`ripast components: ${MANIFEST_PATH} not found at ${cwd}; cannot use manifest source.`);
|
|
1023
|
+
return [];
|
|
1024
|
+
}
|
|
1025
|
+
const fromManifest = readManifest(cwd, manifestPath);
|
|
1026
|
+
const overlay = filesystemOverlay(cwd, opts.glob ?? DEFAULT_VUE_GLOB, fromManifest);
|
|
1027
|
+
return [...fromManifest, ...overlay].sort(sortComponents);
|
|
1028
|
+
}
|
|
1029
|
+
if (source === "auto") warn(`ripast components: ${MANIFEST_PATH} not found; falling back to filesystem glob. Run \`nuxi prepare\` for accurate resolution.`);
|
|
1030
|
+
return filesystemOnly(cwd, opts.glob ?? DEFAULT_VUE_GLOB).sort(sortComponents);
|
|
1031
|
+
}
|
|
1032
|
+
function readManifest(cwd, manifestPath) {
|
|
1033
|
+
const entries = parseManifest(readFileSync(manifestPath, "utf8"), cwd, manifestPath);
|
|
1034
|
+
const byImportPath = /* @__PURE__ */ new Map();
|
|
1035
|
+
for (const entry of entries) {
|
|
1036
|
+
const list = byImportPath.get(entry.importPath) ?? [];
|
|
1037
|
+
list.push(entry);
|
|
1038
|
+
byImportPath.set(entry.importPath, list);
|
|
1039
|
+
}
|
|
1040
|
+
const out = [];
|
|
1041
|
+
for (const [importPath, group] of byImportPath) {
|
|
1042
|
+
const file = resolveImportPath(cwd, manifestPath, importPath);
|
|
1043
|
+
if (!file) continue;
|
|
1044
|
+
const primary = group.find((g) => !g.lazy) ?? group[0];
|
|
1045
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
1046
|
+
aliases.add(hyphenateVueName(primary.name));
|
|
1047
|
+
for (const g of group) aliases.add(g.name);
|
|
1048
|
+
aliases.delete(primary.name);
|
|
1049
|
+
out.push({
|
|
1050
|
+
id: file,
|
|
1051
|
+
name: primary.name,
|
|
1052
|
+
registeredName: primary.name,
|
|
1053
|
+
aliases: [...aliases].sort(),
|
|
1054
|
+
file,
|
|
1055
|
+
rel: relative(cwd, file),
|
|
1056
|
+
kind: file.endsWith(".vue") ? "sfc" : "define-component",
|
|
1057
|
+
scope: "auto-import",
|
|
1058
|
+
shadowed: false,
|
|
1059
|
+
source: "manifest"
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
return out;
|
|
1063
|
+
}
|
|
1064
|
+
function parseManifest(source, _cwd, _manifestPath) {
|
|
1065
|
+
const out = [];
|
|
1066
|
+
for (const match of source.matchAll(MANIFEST_ENTRY_RE)) {
|
|
1067
|
+
const fullName = match[1];
|
|
1068
|
+
const importPath = match[2];
|
|
1069
|
+
const lazy = fullName.startsWith("Lazy") && /^Lazy[A-Z]/.test(fullName);
|
|
1070
|
+
out.push({
|
|
1071
|
+
name: fullName,
|
|
1072
|
+
importPath,
|
|
1073
|
+
lazy
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return out;
|
|
1077
|
+
}
|
|
1078
|
+
function resolveImportPath(cwd, manifestPath, importPath) {
|
|
1079
|
+
const bases = candidateBases(cwd, resolve(manifestPath, ".."), importPath);
|
|
1080
|
+
for (const base of bases) {
|
|
1081
|
+
if (existsSync(base) && statSync(base).isFile()) return base;
|
|
1082
|
+
for (const ext of [
|
|
1083
|
+
".vue",
|
|
1084
|
+
".ts",
|
|
1085
|
+
".tsx",
|
|
1086
|
+
".js",
|
|
1087
|
+
".jsx",
|
|
1088
|
+
".mjs"
|
|
1089
|
+
]) {
|
|
1090
|
+
const candidate = `${base}${ext}`;
|
|
1091
|
+
if (existsSync(candidate)) return candidate;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
function candidateBases(cwd, manifestDir, importPath) {
|
|
1097
|
+
if (importPath.startsWith(".")) return [resolve(manifestDir, importPath)];
|
|
1098
|
+
const aliases = loadNuxtPathAliases(cwd);
|
|
1099
|
+
const out = [];
|
|
1100
|
+
for (const alias of aliases) {
|
|
1101
|
+
if (!alias.wildcard) {
|
|
1102
|
+
if (importPath === alias.pattern) for (const target of alias.targets) out.push(resolve(cwd, target));
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
const prefix = alias.pattern.replace(/\*$/, "");
|
|
1106
|
+
if (!importPath.startsWith(prefix)) continue;
|
|
1107
|
+
const tail = importPath.slice(prefix.length);
|
|
1108
|
+
for (const target of alias.targets) {
|
|
1109
|
+
const targetPrefix = target.replace(/\*$/, "");
|
|
1110
|
+
out.push(resolve(cwd, targetPrefix + tail));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
out.push(resolve(cwd, importPath));
|
|
1114
|
+
return out;
|
|
1115
|
+
}
|
|
1116
|
+
function filesystemOverlay(cwd, glob, manifestComponents) {
|
|
1117
|
+
const manifestByRealPath = /* @__PURE__ */ new Set();
|
|
1118
|
+
for (const c of manifestComponents) manifestByRealPath.add(realOrSelf(c.id));
|
|
1119
|
+
const manifestByName = new Map(manifestComponents.map((c) => [c.name, c]));
|
|
1120
|
+
const candidates = discoverVueFiles(cwd, glob);
|
|
1121
|
+
const out = [];
|
|
1122
|
+
for (const file of candidates) {
|
|
1123
|
+
if (manifestByRealPath.has(realOrSelf(file))) continue;
|
|
1124
|
+
if (!isUnderComponentsDir(cwd, file)) continue;
|
|
1125
|
+
const name = filenameToComponentName(file);
|
|
1126
|
+
const shadowedBy = manifestByName.get(name);
|
|
1127
|
+
out.push({
|
|
1128
|
+
id: file,
|
|
1129
|
+
name,
|
|
1130
|
+
registeredName: null,
|
|
1131
|
+
aliases: [hyphenateVueName(name)],
|
|
1132
|
+
file,
|
|
1133
|
+
rel: relative(cwd, file),
|
|
1134
|
+
kind: "sfc",
|
|
1135
|
+
scope: "auto-import",
|
|
1136
|
+
shadowed: !!shadowedBy,
|
|
1137
|
+
shadowedBy: shadowedBy?.id,
|
|
1138
|
+
source: "filesystem"
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
return out;
|
|
1142
|
+
}
|
|
1143
|
+
function realOrSelf(file) {
|
|
1144
|
+
try {
|
|
1145
|
+
return realpathSync(file);
|
|
1146
|
+
} catch {
|
|
1147
|
+
return file;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function filesystemOnly(cwd, glob) {
|
|
1151
|
+
const files = discoverVueFiles(cwd, glob).filter((file) => isUnderComponentsDir(cwd, file));
|
|
1152
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1153
|
+
for (const file of files) {
|
|
1154
|
+
const name = filenameToComponentName(file);
|
|
1155
|
+
const list = byName.get(name) ?? [];
|
|
1156
|
+
list.push(file);
|
|
1157
|
+
byName.set(name, list);
|
|
1158
|
+
}
|
|
1159
|
+
const out = [];
|
|
1160
|
+
for (const file of files) {
|
|
1161
|
+
const name = filenameToComponentName(file);
|
|
1162
|
+
const sameName = byName.get(name) ?? [];
|
|
1163
|
+
const isFirst = sameName[0] === file;
|
|
1164
|
+
out.push({
|
|
1165
|
+
id: file,
|
|
1166
|
+
name,
|
|
1167
|
+
registeredName: null,
|
|
1168
|
+
aliases: [hyphenateVueName(name)],
|
|
1169
|
+
file,
|
|
1170
|
+
rel: relative(cwd, file),
|
|
1171
|
+
kind: "sfc",
|
|
1172
|
+
scope: "auto-import",
|
|
1173
|
+
shadowed: !isFirst,
|
|
1174
|
+
shadowedBy: isFirst ? void 0 : sameName[0],
|
|
1175
|
+
source: "filesystem"
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
return out;
|
|
1179
|
+
}
|
|
1180
|
+
function discoverVueFiles(cwd, glob) {
|
|
1181
|
+
return rgFiles("", {
|
|
1182
|
+
cwd,
|
|
1183
|
+
glob,
|
|
1184
|
+
listAll: true
|
|
1185
|
+
}).filter((f) => f.endsWith(".vue"));
|
|
1186
|
+
}
|
|
1187
|
+
function isUnderComponentsDir(cwd, file) {
|
|
1188
|
+
const rel = relative(cwd, file).replace(/\\/g, "/");
|
|
1189
|
+
if (!rel || rel.startsWith("..")) return false;
|
|
1190
|
+
return rel.split("/").some((seg) => COMPONENT_DIR_NAMES.has(seg));
|
|
1191
|
+
}
|
|
1192
|
+
function filenameToComponentName(file) {
|
|
1193
|
+
return basename(file).replace(/\.vue$/, "").split(/[-_.]/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("");
|
|
1194
|
+
}
|
|
1195
|
+
function sortComponents(a, b) {
|
|
1196
|
+
return a.name.localeCompare(b.name) || a.rel.localeCompare(b.rel);
|
|
1197
|
+
}
|
|
1198
|
+
const NUXT_ENTRY_PATTERNS = [
|
|
1199
|
+
"nuxt.config.ts",
|
|
1200
|
+
"nuxt.config.js",
|
|
1201
|
+
"nuxt.config.mjs",
|
|
1202
|
+
"nuxt.config.mts",
|
|
1203
|
+
"app.vue",
|
|
1204
|
+
"error.vue",
|
|
1205
|
+
"app.config.ts",
|
|
1206
|
+
"vitest.config.ts",
|
|
1207
|
+
"vitest.config.mts"
|
|
1208
|
+
];
|
|
1209
|
+
const NUXT_ENTRY_DIRS = [
|
|
1210
|
+
"pages",
|
|
1211
|
+
"layouts",
|
|
1212
|
+
"middleware",
|
|
1213
|
+
"plugins",
|
|
1214
|
+
"server",
|
|
1215
|
+
"modules",
|
|
1216
|
+
"app/middleware",
|
|
1217
|
+
"app/plugins",
|
|
1218
|
+
"app/pages",
|
|
1219
|
+
"app/layouts"
|
|
1220
|
+
];
|
|
1221
|
+
const NUXT_AUTOIMPORT_DIRS = [
|
|
1222
|
+
"components",
|
|
1223
|
+
"composables",
|
|
1224
|
+
"utils",
|
|
1225
|
+
"shared",
|
|
1226
|
+
"server/utils",
|
|
1227
|
+
"server/middleware",
|
|
1228
|
+
"server/api",
|
|
1229
|
+
"server/routes",
|
|
1230
|
+
"server/plugins",
|
|
1231
|
+
"app/components",
|
|
1232
|
+
"app/composables",
|
|
1233
|
+
"app/utils"
|
|
1234
|
+
];
|
|
1235
|
+
const ENTRY_EXTS = [
|
|
1236
|
+
".ts",
|
|
1237
|
+
".tsx",
|
|
1238
|
+
".js",
|
|
1239
|
+
".mjs",
|
|
1240
|
+
".cjs",
|
|
1241
|
+
".vue"
|
|
1242
|
+
];
|
|
1243
|
+
function isNuxtProject$1(cwd) {
|
|
1244
|
+
for (const p of NUXT_ENTRY_PATTERNS) if (p.startsWith("nuxt.config") && existsSync(join(cwd, p))) return true;
|
|
1245
|
+
if (existsSync(join(cwd, ".nuxt"))) return true;
|
|
1246
|
+
const pkgPath = join(cwd, "package.json");
|
|
1247
|
+
if (!existsSync(pkgPath)) return false;
|
|
1248
|
+
try {
|
|
1249
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
1250
|
+
const deps = {
|
|
1251
|
+
...pkg.dependencies,
|
|
1252
|
+
...pkg.devDependencies,
|
|
1253
|
+
...pkg.peerDependencies
|
|
1254
|
+
};
|
|
1255
|
+
return Boolean(deps.nuxt || deps["@nuxt/kit"]);
|
|
1256
|
+
} catch {
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
function listLayerRoots(cwd) {
|
|
1261
|
+
const roots = [cwd];
|
|
1262
|
+
for (const parent of [
|
|
1263
|
+
"layers",
|
|
1264
|
+
"apps",
|
|
1265
|
+
"packages"
|
|
1266
|
+
]) {
|
|
1267
|
+
const dir = join(cwd, parent);
|
|
1268
|
+
if (!existsSync(dir)) continue;
|
|
1269
|
+
for (const name of readdirSync(dir)) {
|
|
1270
|
+
const abs = join(dir, name);
|
|
1271
|
+
try {
|
|
1272
|
+
if (statSync(abs).isDirectory()) roots.push(abs);
|
|
1273
|
+
} catch {}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return roots;
|
|
1277
|
+
}
|
|
1278
|
+
const ROOT_CONFIG_FILES = [
|
|
1279
|
+
"eslint.config.js",
|
|
1280
|
+
"eslint.config.ts",
|
|
1281
|
+
"eslint.config.mjs",
|
|
1282
|
+
"drizzle.config.ts",
|
|
1283
|
+
"tailwind.config.ts",
|
|
1284
|
+
"tailwind.config.js",
|
|
1285
|
+
"content.config.ts",
|
|
1286
|
+
"mdc.config.ts",
|
|
1287
|
+
"router.options.ts",
|
|
1288
|
+
"vitest.config.e2e.ts",
|
|
1289
|
+
"vitest.config.ts",
|
|
1290
|
+
"app.config.ts"
|
|
1291
|
+
];
|
|
1292
|
+
const CONFIG_FILE_RE = /(?:^|\/)(?:eslint\.config\.[mc]?[jt]s|drizzle\.config\.[mc]?[jt]s|tailwind\.config\.[mc]?[jt]s|content\.config\.[mc]?[jt]s|mdc\.config\.[mc]?[jt]s|router\.options\.[mc]?[jt]s|vitest\.config(?:\.\w+)?\.[mc]?[jt]s|app\.config\.[mc]?[jt]s|.*\.d\.ts)$/;
|
|
1293
|
+
function walkDir(dir, out) {
|
|
1294
|
+
let entries;
|
|
1295
|
+
try {
|
|
1296
|
+
entries = readdirSync(dir);
|
|
1297
|
+
} catch {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
for (const name of entries) {
|
|
1301
|
+
const abs = join(dir, name);
|
|
1302
|
+
let s;
|
|
1303
|
+
try {
|
|
1304
|
+
s = statSync(abs);
|
|
1305
|
+
} catch {
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
if (s.isDirectory()) {
|
|
1309
|
+
walkDir(abs, out);
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
if (ENTRY_EXTS.some((ext) => name.endsWith(ext))) out.push(abs);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function isNuxtModulePackage(root) {
|
|
1316
|
+
const moduleTs = join(root, "src", "module.ts");
|
|
1317
|
+
if (!existsSync(moduleTs)) return false;
|
|
1318
|
+
try {
|
|
1319
|
+
return readFileSync(moduleTs, "utf8").includes("defineNuxtModule");
|
|
1320
|
+
} catch {
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function collectEntries(cwd) {
|
|
1325
|
+
const abs = [];
|
|
1326
|
+
for (const root of listLayerRoots(cwd)) {
|
|
1327
|
+
for (const p of [...NUXT_ENTRY_PATTERNS, ...ROOT_CONFIG_FILES]) {
|
|
1328
|
+
const f = join(root, p);
|
|
1329
|
+
if (existsSync(f)) abs.push(f);
|
|
1330
|
+
}
|
|
1331
|
+
for (const d of [...NUXT_ENTRY_DIRS, ...NUXT_AUTOIMPORT_DIRS]) {
|
|
1332
|
+
const dir = join(root, d);
|
|
1333
|
+
if (existsSync(dir)) walkDir(dir, abs);
|
|
1334
|
+
}
|
|
1335
|
+
if (isNuxtModulePackage(root)) {
|
|
1336
|
+
const runtime = join(root, "src", "runtime");
|
|
1337
|
+
if (existsSync(runtime)) walkDir(runtime, abs);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
return [...new Set(abs.map((p) => relative(cwd, p)))];
|
|
1341
|
+
}
|
|
1342
|
+
function isAutoImportRel(rel) {
|
|
1343
|
+
return NUXT_AUTOIMPORT_DIRS.some((d) => rel.includes(`/${d}/`) || rel.startsWith(`${d}/`)) || rel.includes("/server/api/") || rel.includes("/server/routes/") || rel.includes("/server/middleware/") || rel.includes("/server/plugins/") || rel.includes("/pages/") || rel.includes("/layouts/") || rel.includes("/middleware/") || rel.includes("/plugins/") || rel.endsWith("nuxt.config.ts") || rel.endsWith("app.vue") || rel.endsWith("error.vue");
|
|
1344
|
+
}
|
|
1345
|
+
const doctor = {
|
|
1346
|
+
entryFiles(cwd) {
|
|
1347
|
+
if (!isNuxtProject$1(cwd)) return [];
|
|
1348
|
+
return collectEntries(cwd);
|
|
1349
|
+
},
|
|
1350
|
+
filterFinding(_cwd, finding) {
|
|
1351
|
+
if (finding.check === "orphan-file" && (isAutoImportRel(finding.file) || CONFIG_FILE_RE.test(finding.file))) return false;
|
|
1352
|
+
return true;
|
|
1353
|
+
},
|
|
1354
|
+
extraFindings(cwd, ctx) {
|
|
1355
|
+
if (!isNuxtProject$1(cwd)) return [];
|
|
1356
|
+
const out = [];
|
|
1357
|
+
try {
|
|
1358
|
+
const components = listComponents(cwd, { source: "auto" });
|
|
1359
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1360
|
+
for (const c of components) {
|
|
1361
|
+
const arr = byName.get(c.name) ?? [];
|
|
1362
|
+
arr.push(c);
|
|
1363
|
+
byName.set(c.name, arr);
|
|
1364
|
+
}
|
|
1365
|
+
for (const [name, group] of byName) {
|
|
1366
|
+
if (group.length < 2) continue;
|
|
1367
|
+
const winner = group.find((c) => !c.shadowed) ?? group[0];
|
|
1368
|
+
for (const c of group) {
|
|
1369
|
+
if (!c.shadowed) continue;
|
|
1370
|
+
out.push({
|
|
1371
|
+
check: "shadowed-component",
|
|
1372
|
+
file: c.rel,
|
|
1373
|
+
message: `component "${name}" is shadowed by ${winner.rel}`,
|
|
1374
|
+
detail: {
|
|
1375
|
+
name,
|
|
1376
|
+
winner: winner.rel,
|
|
1377
|
+
shadowed: c.rel
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
} catch {}
|
|
1383
|
+
try {
|
|
1384
|
+
out.push(...findPhantomComponents(cwd));
|
|
1385
|
+
} catch {}
|
|
1386
|
+
if (ctx) try {
|
|
1387
|
+
out.push(...findCrossRealmImports(cwd, ctx));
|
|
1388
|
+
} catch {}
|
|
1389
|
+
try {
|
|
1390
|
+
out.push(...findStaleNuxtConfigRefs(cwd));
|
|
1391
|
+
} catch {}
|
|
1392
|
+
return out;
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
const VUE_BUILTINS = new Set([
|
|
1396
|
+
"Transition",
|
|
1397
|
+
"TransitionGroup",
|
|
1398
|
+
"KeepAlive",
|
|
1399
|
+
"Teleport",
|
|
1400
|
+
"Suspense",
|
|
1401
|
+
"Component",
|
|
1402
|
+
"Slot",
|
|
1403
|
+
"Template",
|
|
1404
|
+
"ClientOnly"
|
|
1405
|
+
]);
|
|
1406
|
+
const NUXT_BUILTINS = new Set([
|
|
1407
|
+
"NuxtPage",
|
|
1408
|
+
"NuxtLink",
|
|
1409
|
+
"NuxtLayout",
|
|
1410
|
+
"NuxtLoadingIndicator",
|
|
1411
|
+
"NuxtErrorBoundary",
|
|
1412
|
+
"NuxtWelcome",
|
|
1413
|
+
"NuxtIsland",
|
|
1414
|
+
"NuxtClientFallback",
|
|
1415
|
+
"NuxtImg",
|
|
1416
|
+
"NuxtPicture",
|
|
1417
|
+
"NuxtRouteAnnouncer",
|
|
1418
|
+
"DevOnly",
|
|
1419
|
+
"ServerOnly",
|
|
1420
|
+
"RouterView",
|
|
1421
|
+
"RouterLink",
|
|
1422
|
+
"Icon",
|
|
1423
|
+
"Html",
|
|
1424
|
+
"Head",
|
|
1425
|
+
"Body",
|
|
1426
|
+
"Title",
|
|
1427
|
+
"Meta",
|
|
1428
|
+
"Link",
|
|
1429
|
+
"Style",
|
|
1430
|
+
"NoScript",
|
|
1431
|
+
"Base"
|
|
1432
|
+
]);
|
|
1433
|
+
const PASCAL = /^[A-Z][A-Z0-9a-z]*$/;
|
|
1434
|
+
const NODE_ELEMENT = 1;
|
|
1435
|
+
function extractTemplateBlock(source) {
|
|
1436
|
+
const openStart = source.indexOf("<template");
|
|
1437
|
+
if (openStart === -1) return void 0;
|
|
1438
|
+
const contentStart = source.indexOf(">", openStart);
|
|
1439
|
+
if (contentStart === -1) return void 0;
|
|
1440
|
+
const start = contentStart + 1;
|
|
1441
|
+
const end = source.indexOf("</template>", start);
|
|
1442
|
+
if (end === -1) return void 0;
|
|
1443
|
+
return {
|
|
1444
|
+
content: source.slice(start, end),
|
|
1445
|
+
start
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function findPhantomComponents(cwd) {
|
|
1449
|
+
const files = rgFilesMany(["*.vue"], { cwd });
|
|
1450
|
+
const known = new Set([...VUE_BUILTINS, ...NUXT_BUILTINS]);
|
|
1451
|
+
for (const c of listComponents(cwd, { source: "auto" })) {
|
|
1452
|
+
known.add(c.name);
|
|
1453
|
+
for (const alias of c.aliases ?? []) known.add(alias);
|
|
1454
|
+
}
|
|
1455
|
+
const out = [];
|
|
1456
|
+
for (const abs of files) {
|
|
1457
|
+
let source;
|
|
1458
|
+
try {
|
|
1459
|
+
source = readFileSync(abs, "utf8");
|
|
1460
|
+
} catch {
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
const template = extractTemplateBlock(source);
|
|
1464
|
+
if (!template) continue;
|
|
1465
|
+
const templateStart = template.start;
|
|
1466
|
+
const ast = parseVueTemplateAst(template.content);
|
|
1467
|
+
if (!ast) continue;
|
|
1468
|
+
const localImports = collectLocalImports(source);
|
|
1469
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1470
|
+
walkTpl(ast, (node) => {
|
|
1471
|
+
if (node?.type !== NODE_ELEMENT) return;
|
|
1472
|
+
const tag = node.tag;
|
|
1473
|
+
if (typeof tag !== "string") return;
|
|
1474
|
+
if (!PASCAL.test(tag)) return;
|
|
1475
|
+
if (known.has(tag) || known.has(hyphenateVueName(tag))) return;
|
|
1476
|
+
if (localImports.has(tag)) return;
|
|
1477
|
+
const key = `${abs}::${tag}::${node.loc?.start?.offset ?? 0}`;
|
|
1478
|
+
if (seen.has(key)) return;
|
|
1479
|
+
seen.add(key);
|
|
1480
|
+
const offset = templateStart + (node.loc?.start?.offset ?? 0);
|
|
1481
|
+
const { line, col } = posToLineCol(source, offset);
|
|
1482
|
+
out.push({
|
|
1483
|
+
check: "phantom-component",
|
|
1484
|
+
file: relative(cwd, abs),
|
|
1485
|
+
line,
|
|
1486
|
+
message: `<${tag}> is not a known component (auto-import scope, local import, or built-in)`,
|
|
1487
|
+
detail: {
|
|
1488
|
+
name: tag,
|
|
1489
|
+
col
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
return out;
|
|
1495
|
+
}
|
|
1496
|
+
function walkTpl(node, visit) {
|
|
1497
|
+
if (!node || typeof node !== "object") return;
|
|
1498
|
+
if (typeof node.type === "number") visit(node);
|
|
1499
|
+
for (const child of node.children ?? []) walkTpl(child, visit);
|
|
1500
|
+
}
|
|
1501
|
+
function collectLocalImports(source) {
|
|
1502
|
+
const out = /* @__PURE__ */ new Set();
|
|
1503
|
+
const scriptMatch = source.match(/<script[^>]*>([\s\S]*?)<\/script>/g);
|
|
1504
|
+
if (!scriptMatch) return out;
|
|
1505
|
+
for (const block of scriptMatch) for (const m of block.matchAll(/import\s+(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+))\s+from/g)) if (m[1]) for (const part of m[1].split(",")) {
|
|
1506
|
+
const name = part.trim().replace(/^type\s+/, "").split(/\s+as\s+/).pop().trim();
|
|
1507
|
+
if (name) out.add(name);
|
|
1508
|
+
}
|
|
1509
|
+
else if (m[2]) out.add(m[2]);
|
|
1510
|
+
else if (m[3]) out.add(m[3]);
|
|
1511
|
+
return out;
|
|
1512
|
+
}
|
|
1513
|
+
const APP_DIR_RE = /(?:^|\/)(?:app|pages|layouts|components|composables|middleware|plugins)(?:\/|$)/;
|
|
1514
|
+
const SERVER_DIR_RE = /(?:^|\/)server(?:\/|$)/;
|
|
1515
|
+
const SERVER_SUFFIX_RE = /\.server\.(?:[mc]?[jt]sx?|vue)$/;
|
|
1516
|
+
const CLIENT_SUFFIX_RE = /\.client\.(?:[mc]?[jt]sx?|vue)$/;
|
|
1517
|
+
function fileRealm(rel) {
|
|
1518
|
+
if (SERVER_SUFFIX_RE.test(rel)) return "server";
|
|
1519
|
+
if (CLIENT_SUFFIX_RE.test(rel)) return "app";
|
|
1520
|
+
if (SERVER_DIR_RE.test(rel) && !APP_DIR_RE.test(rel)) return "server";
|
|
1521
|
+
if (APP_DIR_RE.test(rel) && !SERVER_DIR_RE.test(rel)) return "app";
|
|
1522
|
+
return null;
|
|
1523
|
+
}
|
|
1524
|
+
function findCrossRealmImports(cwd, ctx) {
|
|
1525
|
+
const out = [];
|
|
1526
|
+
for (const file of ctx.index.files) {
|
|
1527
|
+
const fromRealm = fileRealm(file.file);
|
|
1528
|
+
if (!fromRealm) continue;
|
|
1529
|
+
for (const imp of file.imports) {
|
|
1530
|
+
if (imp.typeOnly) continue;
|
|
1531
|
+
if (!imp.source.startsWith(".")) continue;
|
|
1532
|
+
const resolved = resolveRelativeFs(file.file, imp.source, cwd);
|
|
1533
|
+
if (!resolved) continue;
|
|
1534
|
+
const toRealm = fileRealm(relative(cwd, resolved));
|
|
1535
|
+
if (!toRealm || toRealm === fromRealm) continue;
|
|
1536
|
+
out.push({
|
|
1537
|
+
check: "cross-realm-import",
|
|
1538
|
+
file: file.file,
|
|
1539
|
+
line: imp.line,
|
|
1540
|
+
message: `${fromRealm}-realm file imports runtime value "${imp.imported || "*"}" from ${toRealm}-realm "${imp.source}"`,
|
|
1541
|
+
detail: {
|
|
1542
|
+
fromRealm,
|
|
1543
|
+
toRealm,
|
|
1544
|
+
source: imp.source,
|
|
1545
|
+
imported: imp.imported
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return out;
|
|
1551
|
+
}
|
|
1552
|
+
const RESOLVE_EXTS = [
|
|
1553
|
+
"",
|
|
1554
|
+
".ts",
|
|
1555
|
+
".tsx",
|
|
1556
|
+
".js",
|
|
1557
|
+
".jsx",
|
|
1558
|
+
".mjs",
|
|
1559
|
+
".cjs",
|
|
1560
|
+
".vue",
|
|
1561
|
+
"/index.ts",
|
|
1562
|
+
"/index.tsx",
|
|
1563
|
+
"/index.js"
|
|
1564
|
+
];
|
|
1565
|
+
function resolveRelativeFs(fromRel, specifier, cwd) {
|
|
1566
|
+
if (!specifier.startsWith(".")) return null;
|
|
1567
|
+
const base = resolve(cwd, fromRel, "..", specifier);
|
|
1568
|
+
for (const ext of RESOLVE_EXTS) {
|
|
1569
|
+
const candidate = base + ext;
|
|
1570
|
+
if (existsSync(candidate)) return candidate;
|
|
1571
|
+
}
|
|
1572
|
+
return null;
|
|
1573
|
+
}
|
|
1574
|
+
const CONFIG_NAMES = [
|
|
1575
|
+
"nuxt.config.ts",
|
|
1576
|
+
"nuxt.config.js",
|
|
1577
|
+
"nuxt.config.mjs",
|
|
1578
|
+
"nuxt.config.mts"
|
|
1579
|
+
];
|
|
1580
|
+
const STRING_FIELDS = [
|
|
1581
|
+
"extends",
|
|
1582
|
+
"modules",
|
|
1583
|
+
"css"
|
|
1584
|
+
];
|
|
1585
|
+
const COMPONENT_PATH_RE = /\{\s*path\s*:\s*['"]([^'"]+)['"]/g;
|
|
1586
|
+
const ARRAY_FIELD_RE = (field) => new RegExp(`\\b${field}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
|
|
1587
|
+
const IMPORT_DIRS_RE = /\bimports\s*:\s*\{[\s\S]*?\bdirs\s*:\s*\[([\s\S]*?)\]/;
|
|
1588
|
+
function findStaleNuxtConfigRefs(cwd) {
|
|
1589
|
+
const out = [];
|
|
1590
|
+
for (const root of listLayerRoots(cwd)) for (const name of CONFIG_NAMES) {
|
|
1591
|
+
const abs = join(root, name);
|
|
1592
|
+
if (!existsSync(abs)) continue;
|
|
1593
|
+
let source;
|
|
1594
|
+
try {
|
|
1595
|
+
source = readFileSync(abs, "utf8");
|
|
1596
|
+
} catch {
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
const rel = relative(cwd, abs);
|
|
1600
|
+
for (const field of STRING_FIELDS) {
|
|
1601
|
+
const match = ARRAY_FIELD_RE(field).exec(source);
|
|
1602
|
+
if (!match) continue;
|
|
1603
|
+
for (const lit of match[1].matchAll(/['"`]([^'"`]+)['"`]/g)) {
|
|
1604
|
+
const value = lit[1];
|
|
1605
|
+
if (!value.startsWith(".") && !value.startsWith("/")) continue;
|
|
1606
|
+
const targetPath = resolve(root, value);
|
|
1607
|
+
if (existsSync(targetPath)) continue;
|
|
1608
|
+
let exists = false;
|
|
1609
|
+
for (const ext of RESOLVE_EXTS) if (existsSync(targetPath + ext)) {
|
|
1610
|
+
exists = true;
|
|
1611
|
+
break;
|
|
1612
|
+
}
|
|
1613
|
+
if (exists) continue;
|
|
1614
|
+
out.push({
|
|
1615
|
+
check: "stale-nuxt-config-ref",
|
|
1616
|
+
file: rel,
|
|
1617
|
+
message: `${field}: "${value}" does not resolve from ${rel}`,
|
|
1618
|
+
detail: {
|
|
1619
|
+
field,
|
|
1620
|
+
value,
|
|
1621
|
+
root: relative(cwd, root)
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
const importsMatch = IMPORT_DIRS_RE.exec(source);
|
|
1627
|
+
if (importsMatch) for (const lit of importsMatch[1].matchAll(/['"`]([^'"`]+)['"`]/g)) {
|
|
1628
|
+
const value = lit[1];
|
|
1629
|
+
if (!value.startsWith(".") && !value.startsWith("/")) continue;
|
|
1630
|
+
if (existsSync(resolve(root, value.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "")))) continue;
|
|
1631
|
+
out.push({
|
|
1632
|
+
check: "stale-nuxt-config-ref",
|
|
1633
|
+
file: rel,
|
|
1634
|
+
message: `imports.dirs: "${value}" does not resolve from ${rel}`,
|
|
1635
|
+
detail: {
|
|
1636
|
+
field: "imports.dirs",
|
|
1637
|
+
value,
|
|
1638
|
+
root: relative(cwd, root)
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
for (const m of source.matchAll(COMPONENT_PATH_RE)) {
|
|
1643
|
+
const value = m[1];
|
|
1644
|
+
if (!value.startsWith(".") && !value.startsWith("/")) continue;
|
|
1645
|
+
if (existsSync(resolve(root, value.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "")))) continue;
|
|
1646
|
+
out.push({
|
|
1647
|
+
check: "stale-nuxt-config-ref",
|
|
1648
|
+
file: rel,
|
|
1649
|
+
message: `components: path "${value}" does not resolve from ${rel}`,
|
|
1650
|
+
detail: {
|
|
1651
|
+
field: "components.path",
|
|
1652
|
+
value,
|
|
1653
|
+
root: relative(cwd, root)
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return out;
|
|
391
1659
|
}
|
|
392
1660
|
function addNuxtExplicitImports(ctx) {
|
|
393
1661
|
const { cwd, symbols, toAbs, fromAbs, existingChanges, noScriptError, scan } = ctx;
|
|
@@ -490,6 +1758,7 @@ async function finalizeVueFileRename(cwd, oldAbs, newAbs, existingChanges, autoI
|
|
|
490
1758
|
const oldName = basename(oldAbs, ".vue");
|
|
491
1759
|
const newName = basename(newAbs, ".vue");
|
|
492
1760
|
mergeIntoChanges(changes, rewriteResolveComponentSites(cwd, oldName, newName, existingChanges, oldAbs, newAbs, warnings));
|
|
1761
|
+
if (oldName !== newName) mergeIntoChanges(changes, rewriteIsAttributeSites(cwd, oldName, newName, mergeView(existingChanges, changes), oldAbs, newAbs, warnings));
|
|
493
1762
|
if (autoImportScopes.size && isInsideAutoImportScope(oldAbs, autoImportScopes) && !isInsideAutoImportScope(newAbs, autoImportScopes)) mergeIntoChanges(changes, addExplicitComponentImports(cwd, oldName, newName, newAbs, mergeView(existingChanges, changes), oldAbs));
|
|
494
1763
|
return {
|
|
495
1764
|
changes,
|
|
@@ -602,6 +1871,48 @@ function addExplicitComponentImports(cwd, oldName, newName, newAbs, changes, old
|
|
|
602
1871
|
function escapeRe(s) {
|
|
603
1872
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
604
1873
|
}
|
|
1874
|
+
function rewriteIsAttributeSites(cwd, oldName, newName, changes, oldAbs, newAbs, warnings) {
|
|
1875
|
+
const oldKebab = hyphenateVueName(oldName);
|
|
1876
|
+
const newKebab = hyphenateVueName(newName);
|
|
1877
|
+
const candidates = new Set([...rgFiles(oldName, {
|
|
1878
|
+
cwd,
|
|
1879
|
+
glob: "*.vue"
|
|
1880
|
+
}), ...rgFiles(oldKebab, {
|
|
1881
|
+
cwd,
|
|
1882
|
+
glob: "*.vue"
|
|
1883
|
+
})]);
|
|
1884
|
+
if (!candidates.size) return [];
|
|
1885
|
+
const byPath = new Map(changes.map((change) => [change.path, change]));
|
|
1886
|
+
const oldRe = `${escapeRe(oldName)}|${escapeRe(oldKebab)}`;
|
|
1887
|
+
const isStringLiteralRe = new RegExp(`(:is\\s*=\\s*)(?:"\\s*'(${oldRe})'\\s*"|'\\s*"(${oldRe})"\\s*')`, "g");
|
|
1888
|
+
const isStaticRe = new RegExp(`(\\bis\\s*=\\s*)(['"])(${oldRe})\\2`, "g");
|
|
1889
|
+
const dynamicBindingRe = /:is\s*=\s*"\s*([A-Za-z_$][\w$]*)\s*"/g;
|
|
1890
|
+
const out = [];
|
|
1891
|
+
const dynamicBindingWarn = [];
|
|
1892
|
+
const mapToken = (token) => token === oldName ? newName : newKebab;
|
|
1893
|
+
for (const path of candidates) {
|
|
1894
|
+
if (path === oldAbs || path === newAbs || isGeneratedNuxtPath(cwd, path)) continue;
|
|
1895
|
+
const before = byPath.get(path)?.before ?? readFileSync(path, "utf8");
|
|
1896
|
+
let current = byPath.get(path)?.after ?? before;
|
|
1897
|
+
current = current.replace(isStringLiteralRe, (_m, prefix, p1, p2) => {
|
|
1898
|
+
return `${prefix}"'${mapToken(p1 ?? p2)}'"`;
|
|
1899
|
+
});
|
|
1900
|
+
current = current.replace(isStaticRe, (_m, prefix, quote, name) => `${prefix}${quote}${mapToken(name)}${quote}`);
|
|
1901
|
+
for (const m of current.matchAll(dynamicBindingRe)) if (m[1] === oldName) {
|
|
1902
|
+
dynamicBindingWarn.push(relative(cwd, path));
|
|
1903
|
+
break;
|
|
1904
|
+
}
|
|
1905
|
+
if (current === before) continue;
|
|
1906
|
+
out.push({
|
|
1907
|
+
path,
|
|
1908
|
+
rel: relative(cwd, path),
|
|
1909
|
+
before,
|
|
1910
|
+
after: current
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
if (dynamicBindingWarn.length) warnings.push(`rename-file: "${oldName}" may be referenced via dynamic <component :is="ref"> in ${dynamicBindingWarn.length} file(s) [${dynamicBindingWarn.join(", ")}]. These cannot be auto-rewritten; inspect and update manually.`);
|
|
1914
|
+
return out;
|
|
1915
|
+
}
|
|
605
1916
|
function hasDefaultImportFromVue(source, name) {
|
|
606
1917
|
return new RegExp(`\\bimport\\s+${escapeRe(name)}\\s+from\\s+['"\`][^'"\`]+\\.vue['"\`]`).test(source);
|
|
607
1918
|
}
|
|
@@ -624,7 +1935,7 @@ const adapter = {
|
|
|
624
1935
|
const vue = createVueService(tsconfigPath, cwd);
|
|
625
1936
|
try {
|
|
626
1937
|
const edits = await vue.service.getFileRenameEdits(URI.file(oldAbs), URI.file(newAbs));
|
|
627
|
-
return edits ? workspaceEditToChanges(edits, vue, cwd) : [];
|
|
1938
|
+
return rewriteUnportableAliasSpecifiers(cwd, edits ? workspaceEditToChanges(edits, vue, cwd) : [], newAbs);
|
|
628
1939
|
} finally {
|
|
629
1940
|
vue.dispose();
|
|
630
1941
|
}
|
|
@@ -641,7 +1952,10 @@ const adapter = {
|
|
|
641
1952
|
}),
|
|
642
1953
|
async finalizeFileRename(cwd, oldAbs, newAbs, existingChanges) {
|
|
643
1954
|
return finalizeVueFileRename(cwd, oldAbs, newAbs, existingChanges, isNuxtProject(cwd) ? nuxtAutoImportScopes(cwd) : /* @__PURE__ */ new Set());
|
|
644
|
-
}
|
|
1955
|
+
},
|
|
1956
|
+
listComponents,
|
|
1957
|
+
findComponentUsages,
|
|
1958
|
+
doctor
|
|
645
1959
|
};
|
|
646
1960
|
const DEFAULT_NUXT_AUTO_IMPORT_DIRS = [
|
|
647
1961
|
"composables",
|
|
@@ -688,4 +2002,48 @@ function extractConfiguredDirs(source) {
|
|
|
688
2002
|
function stripGlob(dir) {
|
|
689
2003
|
return dir.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "");
|
|
690
2004
|
}
|
|
691
|
-
|
|
2005
|
+
const MODULE_EXT_RE = /\.(?:tsx?|jsx?|mts|cts|mjs|cjs|vue)$/;
|
|
2006
|
+
const ALIAS_PREFIX_RE = /^[~@#]/;
|
|
2007
|
+
const IMPORT_FROM_RE = /\b(?:import|export)\b[^;\n]+from\s*(['"`])([^'"`]+)\1/g;
|
|
2008
|
+
const DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*(['"`])([^'"`]+)\1\s*\)/g;
|
|
2009
|
+
function rewriteUnportableAliasSpecifiers(cwd, changes, newAbs) {
|
|
2010
|
+
for (const change of changes) {
|
|
2011
|
+
if (change.path === newAbs) continue;
|
|
2012
|
+
const localAliases = loadConsumerLocalAliases(change.path, cwd);
|
|
2013
|
+
if (!localAliases.length) continue;
|
|
2014
|
+
const before = change.before;
|
|
2015
|
+
const after = change.after;
|
|
2016
|
+
const beforeSpecs = collectSpecifiers(before);
|
|
2017
|
+
const afterSpecs = collectSpecifiers(after);
|
|
2018
|
+
const changedNew = /* @__PURE__ */ new Set();
|
|
2019
|
+
for (const spec of afterSpecs) if (!beforeSpecs.has(spec) && ALIAS_PREFIX_RE.test(spec)) changedNew.add(spec);
|
|
2020
|
+
if (!changedNew.size) continue;
|
|
2021
|
+
let rewritten = after;
|
|
2022
|
+
for (const spec of changedNew) {
|
|
2023
|
+
if (aliasResolvesToTarget(localAliases, spec, newAbs)) continue;
|
|
2024
|
+
const replacement = toRelativeSpecifier(change.path, newAbs, spec);
|
|
2025
|
+
rewritten = replaceSpecifier(rewritten, spec, replacement);
|
|
2026
|
+
}
|
|
2027
|
+
change.after = rewritten;
|
|
2028
|
+
}
|
|
2029
|
+
return changes;
|
|
2030
|
+
}
|
|
2031
|
+
function collectSpecifiers(source) {
|
|
2032
|
+
const out = /* @__PURE__ */ new Set();
|
|
2033
|
+
IMPORT_FROM_RE.lastIndex = 0;
|
|
2034
|
+
for (const m of source.matchAll(IMPORT_FROM_RE)) out.add(m[2]);
|
|
2035
|
+
for (const m of source.matchAll(DYNAMIC_IMPORT_RE)) out.add(m[2]);
|
|
2036
|
+
return out;
|
|
2037
|
+
}
|
|
2038
|
+
function replaceSpecifier(source, oldSpec, newSpec) {
|
|
2039
|
+
const escaped = oldSpec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2040
|
+
return source.replace(new RegExp(`(['"\`])${escaped}\\1`, "g"), `$1${newSpec}$1`);
|
|
2041
|
+
}
|
|
2042
|
+
function toRelativeSpecifier(consumerFile, targetFile, oldSpec) {
|
|
2043
|
+
const hasExt = MODULE_EXT_RE.test(oldSpec);
|
|
2044
|
+
let rel = relative(dirname(consumerFile), targetFile).replace(/\\/g, "/");
|
|
2045
|
+
if (!hasExt) rel = rel.replace(MODULE_EXT_RE, "");
|
|
2046
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
2047
|
+
return rel;
|
|
2048
|
+
}
|
|
2049
|
+
export { adapter as default, findComponentUsage, findComponentUsages, listComponents, parseComponent, parseComponentSource };
|