@ripast/vue 0.2.2 → 0.4.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.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
- error: process.stderr.write.bind(process.stderr),
76
- info() {}
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$1(cwd, pattern) {
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
- const MODULE_EXT_RE = /\.(?:tsx?|jsx?|mts|cts|mjs|cjs|vue)$/;
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,704 @@ 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 listAppRoots(cwd) {
1436
+ const roots = [];
1437
+ const parent = join(cwd, "apps");
1438
+ if (!existsSync(parent)) return roots;
1439
+ for (const name of readdirSync(parent)) {
1440
+ const abs = join(parent, name);
1441
+ try {
1442
+ if (statSync(abs).isDirectory() && existsSync(join(abs, ".nuxt", "components.d.ts"))) roots.push(abs);
1443
+ } catch {}
1444
+ }
1445
+ return roots;
1446
+ }
1447
+ function scopeForFile(abs, roots, cwd) {
1448
+ let best = cwd;
1449
+ let bestLen = -1;
1450
+ for (const root of roots) if ((abs === root || abs.startsWith(`${root}/`)) && root.length > bestLen) {
1451
+ best = root;
1452
+ bestLen = root.length;
1453
+ }
1454
+ return best;
1455
+ }
1456
+ function buildKnownSet(root) {
1457
+ const known = new Set([...VUE_BUILTINS, ...NUXT_BUILTINS]);
1458
+ for (const c of listComponents(root, {
1459
+ source: "auto",
1460
+ warn: () => {}
1461
+ })) {
1462
+ known.add(c.name);
1463
+ for (const alias of c.aliases ?? []) known.add(alias);
1464
+ }
1465
+ return known;
1466
+ }
1467
+ function buildUnionKnownSet(roots, cwd) {
1468
+ const known = new Set([...VUE_BUILTINS, ...NUXT_BUILTINS]);
1469
+ const sources = roots.length ? roots : [cwd];
1470
+ for (const root of sources) for (const c of listComponents(root, {
1471
+ source: "auto",
1472
+ warn: () => {}
1473
+ })) {
1474
+ known.add(c.name);
1475
+ for (const alias of c.aliases ?? []) known.add(alias);
1476
+ }
1477
+ return known;
1478
+ }
1479
+ function findPhantomComponents(cwd) {
1480
+ const files = rgFiles("", {
1481
+ cwd,
1482
+ glob: ["*.vue"],
1483
+ listAll: true
1484
+ });
1485
+ const appRoots = listAppRoots(cwd);
1486
+ const knownByRoot = /* @__PURE__ */ new Map();
1487
+ if (appRoots.length) knownByRoot.set(cwd, buildUnionKnownSet(appRoots, cwd));
1488
+ const out = [];
1489
+ for (const abs of files) {
1490
+ let source;
1491
+ try {
1492
+ source = readFileSync(abs, "utf8");
1493
+ } catch {
1494
+ continue;
1495
+ }
1496
+ const ast = parseVueTemplateAst(source);
1497
+ if (!ast) continue;
1498
+ const root = scopeForFile(abs, appRoots, cwd);
1499
+ let known = knownByRoot.get(root);
1500
+ if (!known) {
1501
+ known = buildKnownSet(root);
1502
+ knownByRoot.set(root, known);
1503
+ }
1504
+ const localImports = collectLocalImports(source);
1505
+ const seen = /* @__PURE__ */ new Set();
1506
+ walkTpl(ast, (node) => {
1507
+ if (node?.type !== NODE_ELEMENT) return;
1508
+ const tag = node.tag;
1509
+ if (typeof tag !== "string") return;
1510
+ if (!PASCAL.test(tag)) return;
1511
+ if (known.has(tag) || known.has(hyphenateVueName(tag))) return;
1512
+ if (localImports.has(tag)) return;
1513
+ const key = `${abs}::${tag}::${node.loc?.start?.offset ?? 0}`;
1514
+ if (seen.has(key)) return;
1515
+ seen.add(key);
1516
+ const offset = node.loc?.start?.offset ?? 0;
1517
+ const { line, col } = posToLineCol(source, offset);
1518
+ out.push({
1519
+ check: "phantom-component",
1520
+ file: relative(cwd, abs),
1521
+ line,
1522
+ message: `<${tag}> is not a known component (auto-import scope, local import, or built-in)`,
1523
+ detail: {
1524
+ name: tag,
1525
+ col,
1526
+ scope: relative(cwd, root) || "."
1527
+ }
1528
+ });
1529
+ });
1530
+ }
1531
+ return out;
1532
+ }
1533
+ function walkTpl(node, visit) {
1534
+ if (!node || typeof node !== "object") return;
1535
+ if (typeof node.type === "number") visit(node);
1536
+ for (const child of node.children ?? []) walkTpl(child, visit);
1537
+ }
1538
+ function collectLocalImports(source) {
1539
+ const out = /* @__PURE__ */ new Set();
1540
+ const scriptMatch = source.match(/<script[^>]*>([\s\S]*?)<\/script>/g);
1541
+ if (!scriptMatch) return out;
1542
+ 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(",")) {
1543
+ const name = part.trim().replace(/^type\s+/, "").split(/\s+as\s+/).pop().trim();
1544
+ if (name) out.add(name);
1545
+ }
1546
+ else if (m[2]) out.add(m[2]);
1547
+ else if (m[3]) out.add(m[3]);
1548
+ return out;
1549
+ }
1550
+ const APP_DIR_RE = /(?:^|\/)(?:app|pages|layouts|components|composables|middleware|plugins)(?:\/|$)/;
1551
+ const SERVER_DIR_RE = /(?:^|\/)server(?:\/|$)/;
1552
+ const SERVER_SUFFIX_RE = /\.server\.(?:[mc]?[jt]sx?|vue)$/;
1553
+ const CLIENT_SUFFIX_RE = /\.client\.(?:[mc]?[jt]sx?|vue)$/;
1554
+ function fileRealm(rel) {
1555
+ if (SERVER_SUFFIX_RE.test(rel)) return "server";
1556
+ if (CLIENT_SUFFIX_RE.test(rel)) return "app";
1557
+ if (SERVER_DIR_RE.test(rel) && !APP_DIR_RE.test(rel)) return "server";
1558
+ if (APP_DIR_RE.test(rel) && !SERVER_DIR_RE.test(rel)) return "app";
1559
+ return null;
1560
+ }
1561
+ function findCrossRealmImports(cwd, ctx) {
1562
+ const out = [];
1563
+ for (const file of ctx.index.files) {
1564
+ const fromRealm = fileRealm(file.file);
1565
+ if (!fromRealm) continue;
1566
+ for (const imp of file.imports) {
1567
+ if (imp.typeOnly) continue;
1568
+ if (!imp.source.startsWith(".")) continue;
1569
+ const resolved = resolveRelativeFs(file.file, imp.source, cwd);
1570
+ if (!resolved) continue;
1571
+ const toRealm = fileRealm(relative(cwd, resolved));
1572
+ if (!toRealm || toRealm === fromRealm) continue;
1573
+ out.push({
1574
+ check: "cross-realm-import",
1575
+ file: file.file,
1576
+ line: imp.line,
1577
+ message: `${fromRealm}-realm file imports runtime value "${imp.imported || "*"}" from ${toRealm}-realm "${imp.source}"`,
1578
+ detail: {
1579
+ fromRealm,
1580
+ toRealm,
1581
+ source: imp.source,
1582
+ imported: imp.imported
1583
+ }
1584
+ });
1585
+ }
1586
+ }
1587
+ return out;
1588
+ }
1589
+ const RESOLVE_EXTS = [
1590
+ "",
1591
+ ".ts",
1592
+ ".tsx",
1593
+ ".js",
1594
+ ".jsx",
1595
+ ".mjs",
1596
+ ".cjs",
1597
+ ".vue",
1598
+ "/index.ts",
1599
+ "/index.tsx",
1600
+ "/index.js"
1601
+ ];
1602
+ function resolveRelativeFs(fromRel, specifier, cwd) {
1603
+ if (!specifier.startsWith(".")) return null;
1604
+ const base = resolve(cwd, fromRel, "..", specifier);
1605
+ for (const ext of RESOLVE_EXTS) {
1606
+ const candidate = base + ext;
1607
+ if (existsSync(candidate)) return candidate;
1608
+ }
1609
+ return null;
1610
+ }
1611
+ const CONFIG_NAMES = [
1612
+ "nuxt.config.ts",
1613
+ "nuxt.config.js",
1614
+ "nuxt.config.mjs",
1615
+ "nuxt.config.mts"
1616
+ ];
1617
+ const STRING_FIELDS = [
1618
+ "extends",
1619
+ "modules",
1620
+ "css"
1621
+ ];
1622
+ const COMPONENT_PATH_RE = /\{\s*path\s*:\s*['"]([^'"]+)['"]/g;
1623
+ const ARRAY_FIELD_RE = (field) => new RegExp(`\\b${field}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
1624
+ const IMPORT_DIRS_RE = /\bimports\s*:\s*\{[\s\S]*?\bdirs\s*:\s*\[([\s\S]*?)\]/;
1625
+ function findStaleNuxtConfigRefs(cwd) {
1626
+ const out = [];
1627
+ for (const root of listLayerRoots(cwd)) for (const name of CONFIG_NAMES) {
1628
+ const abs = join(root, name);
1629
+ if (!existsSync(abs)) continue;
1630
+ let source;
1631
+ try {
1632
+ source = readFileSync(abs, "utf8");
1633
+ } catch {
1634
+ continue;
1635
+ }
1636
+ const rel = relative(cwd, abs);
1637
+ for (const field of STRING_FIELDS) {
1638
+ const match = ARRAY_FIELD_RE(field).exec(source);
1639
+ if (!match) continue;
1640
+ for (const lit of match[1].matchAll(/['"`]([^'"`]+)['"`]/g)) {
1641
+ const value = lit[1];
1642
+ if (!value.startsWith(".") && !value.startsWith("/")) continue;
1643
+ const targetPath = resolve(root, value);
1644
+ if (existsSync(targetPath)) continue;
1645
+ let exists = false;
1646
+ for (const ext of RESOLVE_EXTS) if (existsSync(targetPath + ext)) {
1647
+ exists = true;
1648
+ break;
1649
+ }
1650
+ if (exists) continue;
1651
+ out.push({
1652
+ check: "stale-nuxt-config-ref",
1653
+ file: rel,
1654
+ message: `${field}: "${value}" does not resolve from ${rel}`,
1655
+ detail: {
1656
+ field,
1657
+ value,
1658
+ root: relative(cwd, root)
1659
+ }
1660
+ });
1661
+ }
1662
+ }
1663
+ const importsMatch = IMPORT_DIRS_RE.exec(source);
1664
+ if (importsMatch) for (const lit of importsMatch[1].matchAll(/['"`]([^'"`]+)['"`]/g)) {
1665
+ const value = lit[1];
1666
+ if (!value.startsWith(".") && !value.startsWith("/")) continue;
1667
+ if (existsSync(resolve(root, value.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "")))) continue;
1668
+ out.push({
1669
+ check: "stale-nuxt-config-ref",
1670
+ file: rel,
1671
+ message: `imports.dirs: "${value}" does not resolve from ${rel}`,
1672
+ detail: {
1673
+ field: "imports.dirs",
1674
+ value,
1675
+ root: relative(cwd, root)
1676
+ }
1677
+ });
1678
+ }
1679
+ for (const m of source.matchAll(COMPONENT_PATH_RE)) {
1680
+ const value = m[1];
1681
+ if (!value.startsWith(".") && !value.startsWith("/")) continue;
1682
+ if (existsSync(resolve(root, value.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "")))) continue;
1683
+ out.push({
1684
+ check: "stale-nuxt-config-ref",
1685
+ file: rel,
1686
+ message: `components: path "${value}" does not resolve from ${rel}`,
1687
+ detail: {
1688
+ field: "components.path",
1689
+ value,
1690
+ root: relative(cwd, root)
1691
+ }
1692
+ });
1693
+ }
1694
+ }
1695
+ return out;
391
1696
  }
392
1697
  function addNuxtExplicitImports(ctx) {
393
1698
  const { cwd, symbols, toAbs, fromAbs, existingChanges, noScriptError, scan } = ctx;
@@ -490,6 +1795,7 @@ async function finalizeVueFileRename(cwd, oldAbs, newAbs, existingChanges, autoI
490
1795
  const oldName = basename(oldAbs, ".vue");
491
1796
  const newName = basename(newAbs, ".vue");
492
1797
  mergeIntoChanges(changes, rewriteResolveComponentSites(cwd, oldName, newName, existingChanges, oldAbs, newAbs, warnings));
1798
+ if (oldName !== newName) mergeIntoChanges(changes, rewriteIsAttributeSites(cwd, oldName, newName, mergeView(existingChanges, changes), oldAbs, newAbs, warnings));
493
1799
  if (autoImportScopes.size && isInsideAutoImportScope(oldAbs, autoImportScopes) && !isInsideAutoImportScope(newAbs, autoImportScopes)) mergeIntoChanges(changes, addExplicitComponentImports(cwd, oldName, newName, newAbs, mergeView(existingChanges, changes), oldAbs));
494
1800
  return {
495
1801
  changes,
@@ -602,6 +1908,48 @@ function addExplicitComponentImports(cwd, oldName, newName, newAbs, changes, old
602
1908
  function escapeRe(s) {
603
1909
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
604
1910
  }
1911
+ function rewriteIsAttributeSites(cwd, oldName, newName, changes, oldAbs, newAbs, warnings) {
1912
+ const oldKebab = hyphenateVueName(oldName);
1913
+ const newKebab = hyphenateVueName(newName);
1914
+ const candidates = new Set([...rgFiles(oldName, {
1915
+ cwd,
1916
+ glob: "*.vue"
1917
+ }), ...rgFiles(oldKebab, {
1918
+ cwd,
1919
+ glob: "*.vue"
1920
+ })]);
1921
+ if (!candidates.size) return [];
1922
+ const byPath = new Map(changes.map((change) => [change.path, change]));
1923
+ const oldRe = `${escapeRe(oldName)}|${escapeRe(oldKebab)}`;
1924
+ const isStringLiteralRe = new RegExp(`(:is\\s*=\\s*)(?:"\\s*'(${oldRe})'\\s*"|'\\s*"(${oldRe})"\\s*')`, "g");
1925
+ const isStaticRe = new RegExp(`(\\bis\\s*=\\s*)(['"])(${oldRe})\\2`, "g");
1926
+ const dynamicBindingRe = /:is\s*=\s*"\s*([A-Za-z_$][\w$]*)\s*"/g;
1927
+ const out = [];
1928
+ const dynamicBindingWarn = [];
1929
+ const mapToken = (token) => token === oldName ? newName : newKebab;
1930
+ for (const path of candidates) {
1931
+ if (path === oldAbs || path === newAbs || isGeneratedNuxtPath(cwd, path)) continue;
1932
+ const before = byPath.get(path)?.before ?? readFileSync(path, "utf8");
1933
+ let current = byPath.get(path)?.after ?? before;
1934
+ current = current.replace(isStringLiteralRe, (_m, prefix, p1, p2) => {
1935
+ return `${prefix}"'${mapToken(p1 ?? p2)}'"`;
1936
+ });
1937
+ current = current.replace(isStaticRe, (_m, prefix, quote, name) => `${prefix}${quote}${mapToken(name)}${quote}`);
1938
+ for (const m of current.matchAll(dynamicBindingRe)) if (m[1] === oldName) {
1939
+ dynamicBindingWarn.push(relative(cwd, path));
1940
+ break;
1941
+ }
1942
+ if (current === before) continue;
1943
+ out.push({
1944
+ path,
1945
+ rel: relative(cwd, path),
1946
+ before,
1947
+ after: current
1948
+ });
1949
+ }
1950
+ 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.`);
1951
+ return out;
1952
+ }
605
1953
  function hasDefaultImportFromVue(source, name) {
606
1954
  return new RegExp(`\\bimport\\s+${escapeRe(name)}\\s+from\\s+['"\`][^'"\`]+\\.vue['"\`]`).test(source);
607
1955
  }
@@ -624,7 +1972,7 @@ const adapter = {
624
1972
  const vue = createVueService(tsconfigPath, cwd);
625
1973
  try {
626
1974
  const edits = await vue.service.getFileRenameEdits(URI.file(oldAbs), URI.file(newAbs));
627
- return edits ? workspaceEditToChanges(edits, vue, cwd) : [];
1975
+ return rewriteUnportableAliasSpecifiers(cwd, edits ? workspaceEditToChanges(edits, vue, cwd) : [], newAbs);
628
1976
  } finally {
629
1977
  vue.dispose();
630
1978
  }
@@ -641,7 +1989,10 @@ const adapter = {
641
1989
  }),
642
1990
  async finalizeFileRename(cwd, oldAbs, newAbs, existingChanges) {
643
1991
  return finalizeVueFileRename(cwd, oldAbs, newAbs, existingChanges, isNuxtProject(cwd) ? nuxtAutoImportScopes(cwd) : /* @__PURE__ */ new Set());
644
- }
1992
+ },
1993
+ listComponents,
1994
+ findComponentUsages,
1995
+ doctor
645
1996
  };
646
1997
  const DEFAULT_NUXT_AUTO_IMPORT_DIRS = [
647
1998
  "composables",
@@ -688,4 +2039,48 @@ function extractConfiguredDirs(source) {
688
2039
  function stripGlob(dir) {
689
2040
  return dir.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "");
690
2041
  }
691
- export { adapter as default };
2042
+ const MODULE_EXT_RE = /\.(?:tsx?|jsx?|mts|cts|mjs|cjs|vue)$/;
2043
+ const ALIAS_PREFIX_RE = /^[~@#]/;
2044
+ const IMPORT_FROM_RE = /\b(?:import|export)\b[^;\n]+from\s*(['"`])([^'"`]+)\1/g;
2045
+ const DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*(['"`])([^'"`]+)\1\s*\)/g;
2046
+ function rewriteUnportableAliasSpecifiers(cwd, changes, newAbs) {
2047
+ for (const change of changes) {
2048
+ if (change.path === newAbs) continue;
2049
+ const localAliases = loadConsumerLocalAliases(change.path, cwd);
2050
+ if (!localAliases.length) continue;
2051
+ const before = change.before;
2052
+ const after = change.after;
2053
+ const beforeSpecs = collectSpecifiers(before);
2054
+ const afterSpecs = collectSpecifiers(after);
2055
+ const changedNew = /* @__PURE__ */ new Set();
2056
+ for (const spec of afterSpecs) if (!beforeSpecs.has(spec) && ALIAS_PREFIX_RE.test(spec)) changedNew.add(spec);
2057
+ if (!changedNew.size) continue;
2058
+ let rewritten = after;
2059
+ for (const spec of changedNew) {
2060
+ if (aliasResolvesToTarget(localAliases, spec, newAbs)) continue;
2061
+ const replacement = toRelativeSpecifier(change.path, newAbs, spec);
2062
+ rewritten = replaceSpecifier(rewritten, spec, replacement);
2063
+ }
2064
+ change.after = rewritten;
2065
+ }
2066
+ return changes;
2067
+ }
2068
+ function collectSpecifiers(source) {
2069
+ const out = /* @__PURE__ */ new Set();
2070
+ IMPORT_FROM_RE.lastIndex = 0;
2071
+ for (const m of source.matchAll(IMPORT_FROM_RE)) out.add(m[2]);
2072
+ for (const m of source.matchAll(DYNAMIC_IMPORT_RE)) out.add(m[2]);
2073
+ return out;
2074
+ }
2075
+ function replaceSpecifier(source, oldSpec, newSpec) {
2076
+ const escaped = oldSpec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2077
+ return source.replace(new RegExp(`(['"\`])${escaped}\\1`, "g"), `$1${newSpec}$1`);
2078
+ }
2079
+ function toRelativeSpecifier(consumerFile, targetFile, oldSpec) {
2080
+ const hasExt = MODULE_EXT_RE.test(oldSpec);
2081
+ let rel = relative(dirname(consumerFile), targetFile).replace(/\\/g, "/");
2082
+ if (!hasExt) rel = rel.replace(MODULE_EXT_RE, "");
2083
+ if (!rel.startsWith(".")) rel = `./${rel}`;
2084
+ return rel;
2085
+ }
2086
+ export { adapter as default, findComponentUsage, findComponentUsages, listComponents, parseComponent, parseComponentSource };