@ripast/vue 0.0.1 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.mjs +111 -2
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { extractTemplateExpressions, posToLineCol, rewriteTemplateReferences } from "@ripast/core/adapter";
2
+ import { basename, join, relative, resolve } from "node:path";
3
+ import { extractTemplateExpressions, hyphenateVueName, isGeneratedNuxtPath, isInsideNuxtAutoImportScope, loadNuxtPathAliases, posToLineCol, resolveBestImportSpecifier, rewriteTemplateReferences, rgFiles } 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";
@@ -273,6 +273,112 @@ async function getDiags(vue, fileName) {
273
273
  function diagKey(d) {
274
274
  return `${d.range.start.line}:${d.range.start.character}:${d.code ?? ""}:${d.message}`;
275
275
  }
276
+ async function finalizeVueFileRename(cwd, oldAbs, newAbs, existingChanges, autoImportScopes) {
277
+ const changes = [];
278
+ const warnings = [];
279
+ if (!oldAbs.endsWith(".vue") || !newAbs.endsWith(".vue")) return {
280
+ changes,
281
+ warnings
282
+ };
283
+ const oldName = basename(oldAbs, ".vue");
284
+ const newName = basename(newAbs, ".vue");
285
+ mergeIntoChanges(changes, rewriteResolveComponentSites(cwd, oldName, newName, existingChanges, oldAbs, newAbs, warnings));
286
+ if (autoImportScopes.size && isInsideNuxtAutoImportScope(oldAbs, autoImportScopes) && !isInsideNuxtAutoImportScope(newAbs, autoImportScopes)) mergeIntoChanges(changes, addExplicitComponentImports(cwd, oldName, newName, newAbs, mergeView(existingChanges, changes), oldAbs));
287
+ return {
288
+ changes,
289
+ warnings
290
+ };
291
+ }
292
+ function mergeView(a, b) {
293
+ const map = /* @__PURE__ */ new Map();
294
+ for (const c of a) map.set(c.path, c);
295
+ for (const c of b) map.set(c.path, c);
296
+ return [...map.values()];
297
+ }
298
+ function mergeIntoChanges(target, incoming) {
299
+ for (const change of incoming) {
300
+ const existing = target.find((c) => c.path === change.path);
301
+ if (existing) existing.after = change.after;
302
+ else target.push(change);
303
+ }
304
+ }
305
+ function rewriteResolveComponentSites(cwd, oldName, newName, changes, oldAbs, newAbs, warnings) {
306
+ const candidates = new Set(rgFiles("resolveComponent", { cwd }));
307
+ if (!candidates.size) return [];
308
+ const byPath = new Map(changes.map((change) => [change.path, change]));
309
+ const escaped = escapeRe(oldName);
310
+ const literalRe = new RegExp(`resolveComponent\\s*\\(\\s*(['"\`])${escaped}\\1`, "g");
311
+ const out = [];
312
+ const warnFiles = [];
313
+ for (const path of candidates) {
314
+ if (path === oldAbs || path === newAbs || isGeneratedNuxtPath(cwd, path)) continue;
315
+ const before = byPath.get(path)?.before ?? readFileSync(path, "utf8");
316
+ const current = byPath.get(path)?.after ?? before;
317
+ literalRe.lastIndex = 0;
318
+ if (!literalRe.test(current)) continue;
319
+ warnFiles.push(relative(cwd, path));
320
+ if (oldName === newName) continue;
321
+ literalRe.lastIndex = 0;
322
+ const after = current.replace(literalRe, (match, quote) => match.replace(`${quote}${oldName}${quote}`, `${quote}${newName}${quote}`));
323
+ if (after === current) continue;
324
+ out.push({
325
+ path,
326
+ rel: relative(cwd, path),
327
+ before,
328
+ after
329
+ });
330
+ }
331
+ if (warnFiles.length) warnings.push(`rename-file: "${oldName}" is referenced via resolveComponent() in ${warnFiles.length} file(s) [${warnFiles.join(", ")}]. The component must stay globally registered for these sites to resolve (plugin app.component(...) or Nuxt components dir).`);
332
+ return out;
333
+ }
334
+ function addExplicitComponentImports(cwd, oldName, newName, newAbs, changes, oldAbs) {
335
+ const tokens = new Set([
336
+ oldName,
337
+ newName,
338
+ hyphenateVueName(oldName),
339
+ hyphenateVueName(newName)
340
+ ]);
341
+ const candidates = /* @__PURE__ */ new Set();
342
+ for (const token of tokens) for (const path of rgFiles(token, {
343
+ cwd,
344
+ glob: "*.vue"
345
+ })) candidates.add(path);
346
+ if (!candidates.size) return [];
347
+ const aliases = loadNuxtPathAliases(cwd);
348
+ const byPath = new Map(changes.map((change) => [change.path, change]));
349
+ const tagRe = new RegExp(`<\\/?(?:${escapeRe(newName)}|${escapeRe(hyphenateVueName(newName))})(?=[\\s/>])`);
350
+ const out = [];
351
+ for (const path of candidates) {
352
+ if (path === oldAbs || path === newAbs || isGeneratedNuxtPath(cwd, path)) continue;
353
+ const before = byPath.get(path)?.before ?? readFileSync(path, "utf8");
354
+ const current = byPath.get(path)?.after ?? before;
355
+ if (!tagRe.test(current)) continue;
356
+ if (hasDefaultImportFromVue(current, newName)) continue;
357
+ const after = insertVueComponentImport(current, newName, resolveBestImportSpecifier(path, newAbs, aliases, "./placeholder.vue"));
358
+ if (after === current) continue;
359
+ out.push({
360
+ path,
361
+ rel: relative(cwd, path),
362
+ before,
363
+ after
364
+ });
365
+ }
366
+ return out;
367
+ }
368
+ function escapeRe(s) {
369
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
370
+ }
371
+ function hasDefaultImportFromVue(source, name) {
372
+ return new RegExp(`\\bimport\\s+${escapeRe(name)}\\s+from\\s+['"\`][^'"\`]+\\.vue['"\`]`).test(source);
373
+ }
374
+ function insertVueComponentImport(source, name, specifier) {
375
+ const match = source.match(/<script(?:\s[^>]*)?>/);
376
+ if (!match || match.index === void 0) throw new Error(`ripast rename-file: "${name}" is auto-imported in Nuxt; the new path falls outside auto-import scope but a consumer has no <script> block to receive an explicit import. Add a <script setup> block first.`);
377
+ const insertAt = match.index + match[0].length;
378
+ const importLine = `\nimport ${name} from '${specifier}'`;
379
+ if (source.slice(insertAt, insertAt + 1) === "\n") return `${source.slice(0, insertAt)}${importLine}${source.slice(insertAt)}`;
380
+ return `${source.slice(0, insertAt)}${importLine}\n${source.slice(insertAt)}`;
381
+ }
276
382
  const adapter = {
277
383
  name: "vue",
278
384
  hasFilesContaining: hasVueFilesContaining,
@@ -292,6 +398,9 @@ const adapter = {
292
398
  autoImportScopes(cwd) {
293
399
  if (!isNuxtProject(cwd)) return /* @__PURE__ */ new Set();
294
400
  return nuxtAutoImportScopes(cwd);
401
+ },
402
+ async finalizeFileRename(cwd, oldAbs, newAbs, existingChanges) {
403
+ return finalizeVueFileRename(cwd, oldAbs, newAbs, existingChanges, isNuxtProject(cwd) ? nuxtAutoImportScopes(cwd) : /* @__PURE__ */ new Set());
295
404
  }
296
405
  };
297
406
  const DEFAULT_NUXT_AUTO_IMPORT_DIRS = [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ripast/vue",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.1.0",
5
5
  "description": "Vue (and Nuxt) framework adapter for ripast. Provides Volar/Vue Language Service-backed rename, move, and template rewriting for .vue SFCs.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "peerDependencies": {
44
44
  "typescript": ">=5.4",
45
- "@ripast/core": "0.0.1"
45
+ "@ripast/core": "0.1.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "@volar/language-service": "^2.4.28",
@@ -55,7 +55,7 @@
55
55
  "vscode-uri": "^3.1.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@ripast/core": "0.0.1"
58
+ "@ripast/core": "0.1.0"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "obuild",