@ripast/vue 0.0.0 → 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.
- package/dist/index.mjs +142 -39
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
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
|
-
import { spawnSync } from "node:child_process";
|
|
6
5
|
import process from "node:process";
|
|
7
6
|
import { FileType, createLanguage, createLanguageService, createUriMap } from "@volar/language-service";
|
|
8
7
|
import { createLanguageServiceHost, resolveFileLanguageId } from "@volar/typescript";
|
|
@@ -11,6 +10,7 @@ import { createVueLanguageServicePlugins } from "@vue/language-service";
|
|
|
11
10
|
import ts from "typescript";
|
|
12
11
|
import { create } from "volar-service-typescript";
|
|
13
12
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
14
|
function createVueService(tsconfigPath, cwd) {
|
|
15
15
|
const commandLine = createParsedCommandLine(ts, ts.sys, tsconfigPath);
|
|
16
16
|
const vueExts = getAllExtensions(commandLine.vueOptions).map((ext) => {
|
|
@@ -119,7 +119,7 @@ function workspaceEditToChanges(edit, vue, cwd, filter) {
|
|
|
119
119
|
if (after === before) return;
|
|
120
120
|
out.push({
|
|
121
121
|
path: fileName,
|
|
122
|
-
rel:
|
|
122
|
+
rel: workspaceRelativePath(fileName, cwd),
|
|
123
123
|
before,
|
|
124
124
|
after
|
|
125
125
|
});
|
|
@@ -130,23 +130,37 @@ function workspaceEditToChanges(edit, vue, cwd, filter) {
|
|
|
130
130
|
}
|
|
131
131
|
return out;
|
|
132
132
|
}
|
|
133
|
-
function
|
|
133
|
+
function workspaceRelativePath(fileName, cwd) {
|
|
134
134
|
return fileName.startsWith(cwd) ? fileName.slice(cwd.length).replace(/^[/\\]/, "") : fileName;
|
|
135
135
|
}
|
|
136
|
-
function hasVueFilesContaining(cwd, pattern) {
|
|
137
|
-
return
|
|
136
|
+
function hasVueFilesContaining$1(cwd, pattern) {
|
|
137
|
+
return listVueFilesContaining(cwd, pattern).length > 0;
|
|
138
|
+
}
|
|
139
|
+
function listVueFiles(cwd) {
|
|
140
|
+
return rgVueFiles(cwd, ["--files"]);
|
|
141
|
+
}
|
|
142
|
+
function listVueFilesContaining(cwd, pattern) {
|
|
143
|
+
return rgVueFiles(cwd, [
|
|
138
144
|
"--files-with-matches",
|
|
145
|
+
"--fixed-strings",
|
|
146
|
+
pattern
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
function rgVueFiles(cwd, args) {
|
|
150
|
+
return spawnSync("rg", [
|
|
151
|
+
...args,
|
|
139
152
|
"--hidden",
|
|
140
153
|
"--no-messages",
|
|
141
|
-
"--fixed-strings",
|
|
142
154
|
"-g",
|
|
143
155
|
"*.vue",
|
|
144
|
-
pattern,
|
|
145
156
|
"."
|
|
146
157
|
], {
|
|
147
158
|
cwd,
|
|
148
159
|
encoding: "utf8"
|
|
149
|
-
}).stdout.
|
|
160
|
+
}).stdout.split("\n").filter(Boolean).map((path) => resolve(cwd, path));
|
|
161
|
+
}
|
|
162
|
+
function hasVueFilesContaining(cwd, pattern) {
|
|
163
|
+
return hasVueFilesContaining$1(cwd, pattern);
|
|
150
164
|
}
|
|
151
165
|
async function applyVueRename(tsconfigPath, cwd, from, to, sites) {
|
|
152
166
|
const vue = createVueService(tsconfigPath, cwd);
|
|
@@ -173,11 +187,12 @@ async function applyVueRename(tsconfigPath, cwd, from, to, sites) {
|
|
|
173
187
|
const before = existing?.before ?? safeReadFile(path);
|
|
174
188
|
if (before === void 0) continue;
|
|
175
189
|
const baseAfter = existing?.after ?? before;
|
|
190
|
+
if (hasLocalScriptBinding(baseAfter, from)) continue;
|
|
176
191
|
const rewritten = rewriteTemplateReferences(baseAfter, from, to);
|
|
177
192
|
if (rewritten === baseAfter) continue;
|
|
178
193
|
byPath.set(path, {
|
|
179
194
|
path,
|
|
180
|
-
rel: existing?.rel ??
|
|
195
|
+
rel: existing?.rel ?? workspaceRelativePath(path, cwd),
|
|
181
196
|
before,
|
|
182
197
|
after: rewritten
|
|
183
198
|
});
|
|
@@ -187,20 +202,15 @@ async function applyVueRename(tsconfigPath, cwd, from, to, sites) {
|
|
|
187
202
|
vue.dispose();
|
|
188
203
|
}
|
|
189
204
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"."
|
|
200
|
-
], {
|
|
201
|
-
cwd,
|
|
202
|
-
encoding: "utf8"
|
|
203
|
-
}).stdout.split("\n").filter(Boolean).map((p) => resolve(cwd, p));
|
|
205
|
+
function hasLocalScriptBinding(source, name) {
|
|
206
|
+
const script = extractScript(source);
|
|
207
|
+
if (!script?.includes(name)) return false;
|
|
208
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
209
|
+
const withoutImports = script.replace(/^\s*import [^;\n]*;?$/gm, "");
|
|
210
|
+
return new RegExp(`\\b(?:const|let|var|function|class|interface|type|enum)\\s+${escaped}\\b`).test(withoutImports);
|
|
211
|
+
}
|
|
212
|
+
function extractScript(source) {
|
|
213
|
+
return /<script(?:\s[^>]*)?>([\s\S]*?)<\/script>/i.exec(source)?.[1] ?? null;
|
|
204
214
|
}
|
|
205
215
|
function safeReadFile(path) {
|
|
206
216
|
try {
|
|
@@ -209,9 +219,6 @@ function safeReadFile(path) {
|
|
|
209
219
|
return;
|
|
210
220
|
}
|
|
211
221
|
}
|
|
212
|
-
function relPath(fileName, cwd) {
|
|
213
|
-
return fileName.startsWith(cwd) ? fileName.slice(cwd.length).replace(/^[/\\]/, "") : fileName;
|
|
214
|
-
}
|
|
215
222
|
async function applyVueImportRewrite(tsconfigPath, cwd, fromAbs, toAbs) {
|
|
216
223
|
const vue = createVueService(tsconfigPath, cwd);
|
|
217
224
|
try {
|
|
@@ -266,18 +273,111 @@ async function getDiags(vue, fileName) {
|
|
|
266
273
|
function diagKey(d) {
|
|
267
274
|
return `${d.range.start.line}:${d.range.start.character}:${d.code ?? ""}:${d.message}`;
|
|
268
275
|
}
|
|
269
|
-
function
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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, {
|
|
278
343
|
cwd,
|
|
279
|
-
|
|
280
|
-
})
|
|
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)}`;
|
|
281
381
|
}
|
|
282
382
|
const adapter = {
|
|
283
383
|
name: "vue",
|
|
@@ -298,6 +398,9 @@ const adapter = {
|
|
|
298
398
|
autoImportScopes(cwd) {
|
|
299
399
|
if (!isNuxtProject(cwd)) return /* @__PURE__ */ new Set();
|
|
300
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());
|
|
301
404
|
}
|
|
302
405
|
};
|
|
303
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.
|
|
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.
|
|
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.
|
|
58
|
+
"@ripast/core": "0.1.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "obuild",
|