@menapo/vi18n 0.0.0-stage → 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/LICENSE +49 -0
- package/README.md +110 -2
- package/dist/apply-Bre-lY9J.js +228 -0
- package/dist/captured-Bj8bO6vd.js +1034 -0
- package/dist/check-D34P-S--.js +1 -0
- package/dist/check-DyrAbv73.js +201 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +41 -0
- package/dist/codec-DuvU2VQy.js +207 -0
- package/dist/collect-Cx4_SKOe.js +13 -0
- package/dist/collisions-CfpZU8e4.js +217 -0
- package/dist/compile-B5Mz6WJm.js +245 -0
- package/dist/compile-Cc97JaW2.js +2502 -0
- package/dist/concurrency-C9UUWuDC.js +96 -0
- package/dist/concurrency-DONL7t4V.js +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +39 -0
- package/dist/dir-zw6bGMKQ.js +290 -0
- package/dist/duplicates-yuRljQ6h.js +185 -0
- package/dist/engine-B7p40M__.js +531 -0
- package/dist/engine.d.ts +43 -0
- package/dist/engine.js +1241 -0
- package/dist/exclude-Du-Zd_qI.js +26 -0
- package/dist/export-DGsMbcTZ.js +103 -0
- package/dist/extract-Dujlsgnk.js +298 -0
- package/dist/formats-KPIg-08V.js +781 -0
- package/dist/i18next-CrUnKFfh.js +1596 -0
- package/dist/i18next-Rgo-v9l0.js +1 -0
- package/dist/import-Cwdhcywt.js +225 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +239 -0
- package/dist/init-BecCTvKi.js +792 -0
- package/dist/library-D5CWvTQs.js +1882 -0
- package/dist/lingui--PcOOYry.js +833 -0
- package/dist/lint.d.ts +105 -0
- package/dist/lint.js +971 -0
- package/dist/load-CQRz4PpM.js +2950 -0
- package/dist/load-gW-oJt1Q.js +747 -0
- package/dist/locales-DX4MOBFE.js +827 -0
- package/dist/lsp-Cq1Nuu-Q.js +31 -0
- package/dist/match-BGr8EXB3.js +89 -0
- package/dist/mcp-Cf0T7qPW.js +28 -0
- package/dist/merge-driver-CpiiSVIc.js +422 -0
- package/dist/messages-Cxa-5tR0.js +166 -0
- package/dist/migrate-CntqvrW4.js +1 -0
- package/dist/migrate-dPgq9uzy.js +1484 -0
- package/dist/openai-CTaMDcl-.js +346 -0
- package/dist/output-DsaHKHEt.js +164 -0
- package/dist/parse.d.ts +62 -0
- package/dist/parse.js +156 -0
- package/dist/pipeline-C7g11THL.js +2031 -0
- package/dist/pipeline-wxS0SApG.js +1 -0
- package/dist/project-Dwx7iPm6.js +381 -0
- package/dist/project-pXipT8iG.js +1046 -0
- package/dist/pseudo-DRv30iN5.js +145 -0
- package/dist/raw-C4Y6vyGY.js +4708 -0
- package/dist/react-intl-CaBVwJ9u.js +519 -0
- package/dist/restore-Btq66Tid.js +175 -0
- package/dist/rewrite-CiTRAICl.js +803 -0
- package/dist/rich-DCUKl5HM.js +97 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/run-DYD_VXKy.js +694 -0
- package/dist/runtime-DTMmliT1.js +64 -0
- package/dist/scan-worker.d.ts +62 -0
- package/dist/scan-worker.js +1 -0
- package/dist/scoped-BMYpSthj.js +44 -0
- package/dist/server-BDkykMjb.js +11823 -0
- package/dist/server-qv3mz70d.js +10485 -0
- package/dist/server.d.ts +169 -0
- package/dist/server.js +1234 -0
- package/dist/sitemap-DV3o8QD7.js +159 -0
- package/dist/sources-D19czDSA.js +2242 -0
- package/dist/status-DZArW5aE.js +149 -0
- package/dist/stdio-CVaJfxA7.js +29 -0
- package/dist/stdio-FPOKQAgu.js +95 -0
- package/dist/sync-CcVzger_.js +658 -0
- package/dist/t-DC-c9zE5.js +454 -0
- package/dist/test.d.ts +116 -0
- package/dist/test.js +319 -0
- package/dist/translate-DZR3MkSA.js +299 -0
- package/dist/ts-BaU5yOf0.js +4863 -0
- package/dist/types-CgUCkFU1.d.ts +707 -0
- package/dist/types-D3QGM8XO.js +146 -0
- package/dist/types-DsdWBG0b.d.ts +558 -0
- package/dist/types-DtvQ7UdD.js +1948 -0
- package/dist/types-wSh9fCBY.js +11 -0
- package/dist/units-CQ5Q99RC.js +150 -0
- package/dist/url-CRfCOhUI.js +172 -0
- package/dist/url-CsBkRaJy.js +225 -0
- package/dist/validate-BxpupdAf.js +2615 -0
- package/dist/validate-D6DJepZv.js +365 -0
- package/dist/version-BlyLGlOa.js +28 -0
- package/dist/vite-scan-B_YW4X_a.js +242 -0
- package/dist/vite.d.ts +34 -0
- package/dist/vite.js +7558 -0
- package/dist/vue-i18n-CEMnpv7k.js +651 -0
- package/dist/which-C6PFI1Dm.js +116 -0
- package/dist/worker-84U6uYB3.js +1656 -0
- package/package.json +97 -3
- package/schemas/catalog.json +57 -0
|
@@ -0,0 +1,1656 @@
|
|
|
1
|
+
import { a as diagnosticError, o as isDiagnosticError } from "./types-D3QGM8XO.js";
|
|
2
|
+
import { t as createCodec } from "./codec-DuvU2VQy.js";
|
|
3
|
+
import { r as libraryIds } from "./library-D5CWvTQs.js";
|
|
4
|
+
import { c as unwrap, d as listImports, l as lineColumn, n as sourceSpecifiers, r as scanModule, s as keyName, t as sourceNeedles, u as locateLiteral } from "./sources-D19czDSA.js";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { dirname, join, sep } from "node:path";
|
|
7
|
+
import { hash } from "node:crypto";
|
|
8
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { isMainThread, parentPort, workerData } from "node:worker_threads";
|
|
11
|
+
import MagicString from "magic-string";
|
|
12
|
+
//#region src/core/extract/parser.ts
|
|
13
|
+
/**
|
|
14
|
+
* Parsing for extraction (INTERNALS §2.5.4–§2.5.5). The parser is Oxc as Vite
|
|
15
|
+
* ships it (`parseSync`), so there is no parser dependency of our own (§6 D16)
|
|
16
|
+
* and the CLI and the plugin see the same ESTree. Once raw transfer starts
|
|
17
|
+
* ({@link armRawParsing}, {@link enableRawParsing}), the same binding parses
|
|
18
|
+
* through it (`raw.ts`, §6 D104) when its Oxc version allows: the same tree,
|
|
19
|
+
* faster.
|
|
20
|
+
*/
|
|
21
|
+
let parseSync = null;
|
|
22
|
+
/** The raw-transfer parser, once {@link enableRawParsing} found one usable. */
|
|
23
|
+
let parseRaw = null;
|
|
24
|
+
let rawReady = null;
|
|
25
|
+
function loadParser(filename) {
|
|
26
|
+
if (parseSync !== null) return parseSync;
|
|
27
|
+
let mod;
|
|
28
|
+
try {
|
|
29
|
+
mod = createRequire(import.meta.url)("vite");
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw diagnosticError({
|
|
32
|
+
code: "extract-parse-error",
|
|
33
|
+
severity: "error",
|
|
34
|
+
message: `Cannot load the parser from "vite" (${error.message}); install vite 8 as a dev dependency`,
|
|
35
|
+
...filename === void 0 ? {} : { file: filename }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (typeof mod.parseSync !== "function") throw diagnosticError({
|
|
39
|
+
code: "extract-parse-error",
|
|
40
|
+
severity: "error",
|
|
41
|
+
message: `The installed "vite" does not export parseSync; install vite 8 or later`,
|
|
42
|
+
...filename === void 0 ? {} : { file: filename }
|
|
43
|
+
});
|
|
44
|
+
parseSync = mod.parseSync;
|
|
45
|
+
return parseSync;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Names of the platform packages a binding loads from. An owner's other
|
|
49
|
+
* optional dependencies are not where its binding lives, and some load a
|
|
50
|
+
* `.node` file of their own (`fsevents`, which Vite+ declares, on macOS).
|
|
51
|
+
*/
|
|
52
|
+
const PLATFORM_PACKAGE = /^(?:@voidzero-dev\/vite-plus|@rolldown\/binding)-/;
|
|
53
|
+
/**
|
|
54
|
+
* {@link BindingOwner} of the `vite` that `from` resolves, or null when it
|
|
55
|
+
* cannot be told for certain. Only what the owner declares counts: a
|
|
56
|
+
* hoisted `rolldown` or `@oxc-project/types` that belongs to another package
|
|
57
|
+
* (Astro's or Nuxt's Rolldown in the same tree) would vouch for a layout
|
|
58
|
+
* the binding in use does not write. The owner must pin
|
|
59
|
+
* `@oxc-project/types` exactly, and the copy it resolves must be that
|
|
60
|
+
* version.
|
|
61
|
+
*/
|
|
62
|
+
function bindingOwner(from = import.meta.url) {
|
|
63
|
+
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) return null;
|
|
64
|
+
try {
|
|
65
|
+
let dir = dirname(realpathSync(createRequire(from).resolve("vite/package.json")));
|
|
66
|
+
let pkg = readPackage(dir);
|
|
67
|
+
if (pkg.dependencies?.["@oxc-project/types"] === void 0) {
|
|
68
|
+
if (pkg.dependencies?.rolldown === void 0) return null;
|
|
69
|
+
dir = dirname(realpathSync(createRequire(join(dir, "package.json")).resolve("rolldown/package.json")));
|
|
70
|
+
pkg = readPackage(dir);
|
|
71
|
+
}
|
|
72
|
+
const pinned = /^=?(\d+\.\d+\.\d+)$/.exec(pkg.dependencies?.["@oxc-project/types"] ?? "")?.[1];
|
|
73
|
+
if (pinned === void 0) return null;
|
|
74
|
+
const ownerRequire = createRequire(join(dir, "package.json"));
|
|
75
|
+
if (ownerRequire("@oxc-project/types/package.json").version !== pinned) return null;
|
|
76
|
+
const dirs = [dir];
|
|
77
|
+
for (const name of Object.keys(pkg.optionalDependencies ?? {})) {
|
|
78
|
+
if (!PLATFORM_PACKAGE.test(name)) continue;
|
|
79
|
+
try {
|
|
80
|
+
dirs.push(dirname(realpathSync(ownerRequire.resolve(`${name}/package.json`))));
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
dirs,
|
|
85
|
+
oxcVersion: pinned
|
|
86
|
+
};
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function readPackage(dir) {
|
|
92
|
+
return JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The native binding of `owner` among the loaded native modules, if it has
|
|
96
|
+
* raw transfer. `vite` exports no handle to it, but loading it put it in
|
|
97
|
+
* the module cache; other bindings sit there too (another Rolldown, the
|
|
98
|
+
* `oxc-parser` devDependency's) and are told apart by where they live, never
|
|
99
|
+
* by load order or file name.
|
|
100
|
+
*/
|
|
101
|
+
function ownBinding(owner, cache = createRequire(import.meta.url).cache) {
|
|
102
|
+
for (const key in cache) {
|
|
103
|
+
if (!key.endsWith(".node")) continue;
|
|
104
|
+
let real;
|
|
105
|
+
try {
|
|
106
|
+
real = realpathSync(key);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!owner.dirs.some((dir) => real.startsWith(dir + sep))) continue;
|
|
111
|
+
const exports = cache[key]?.exports;
|
|
112
|
+
if (typeof exports?.parseRawSync === "function" && typeof exports.getBufferOffset === "function" && typeof exports.rawTransferSupported === "function") return exports;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
let parses = 0;
|
|
117
|
+
let rawAfter = Infinity;
|
|
118
|
+
/**
|
|
119
|
+
* Starts raw transfer once {@link RAW_AFTER} (or `after`) modules more have
|
|
120
|
+
* parsed with `parseSync`, without delaying any of them; `after` 0, or
|
|
121
|
+
* `VI18N_RAW_PARSE=1`, starts it now. Idempotent.
|
|
122
|
+
*/
|
|
123
|
+
function armRawParsing(after = 500) {
|
|
124
|
+
if (rawReady !== null || process.env.VI18N_RAW_PARSE === "0") return;
|
|
125
|
+
if (process.env.VI18N_RAW_PARSE === "1") after = 0;
|
|
126
|
+
if (after > 0) rawAfter = Math.min(rawAfter, parses + after);
|
|
127
|
+
else startRaw();
|
|
128
|
+
}
|
|
129
|
+
function startRaw() {
|
|
130
|
+
rawReady ??= (async () => {
|
|
131
|
+
if (process.env.VI18N_RAW_PARSE === "0") return false;
|
|
132
|
+
try {
|
|
133
|
+
const sync = loadParser(void 0);
|
|
134
|
+
const owner = bindingOwner();
|
|
135
|
+
const binding = owner === null ? null : ownBinding(owner);
|
|
136
|
+
if (owner === null || binding === null) return false;
|
|
137
|
+
const raw = await import("./raw-C4Y6vyGY.js");
|
|
138
|
+
if (!raw.LAYOUT_VERSIONS.has(owner.oxcVersion)) return false;
|
|
139
|
+
parseRaw = raw.createRawParser(binding, sync);
|
|
140
|
+
return parseRaw !== null;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
})();
|
|
145
|
+
return rawReady;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The parser language for a module id, by the extension of its path (query
|
|
149
|
+
* and `\0` prefix ignored). Framework files (`.vue`, `.svelte`, `.astro`,
|
|
150
|
+
* `.mdx`) reach the post pass as plain JS.
|
|
151
|
+
*/
|
|
152
|
+
function langFromId(id) {
|
|
153
|
+
const query = id.indexOf("?");
|
|
154
|
+
const end = query === -1 ? id.length : query;
|
|
155
|
+
const dot = id.lastIndexOf(".", end);
|
|
156
|
+
if (dot === -1 || id.lastIndexOf("/", end) > dot) return "js";
|
|
157
|
+
switch (id.slice(dot + 1, end).toLowerCase()) {
|
|
158
|
+
case "ts":
|
|
159
|
+
case "mts":
|
|
160
|
+
case "cts": return "ts";
|
|
161
|
+
case "tsx": return "tsx";
|
|
162
|
+
case "jsx": return "jsx";
|
|
163
|
+
default: return "js";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Parses `code` as an ES module. Throws a `DiagnosticError`
|
|
168
|
+
* (`extract-parse-error`) on the first syntax error: a partially recovered
|
|
169
|
+
* tree could make the rewriter edit the wrong ranges.
|
|
170
|
+
*/
|
|
171
|
+
function parseModule(code, lang, filename) {
|
|
172
|
+
if (parseRaw === null && ++parses >= rawAfter) {
|
|
173
|
+
rawAfter = Infinity;
|
|
174
|
+
startRaw();
|
|
175
|
+
}
|
|
176
|
+
const result = (parseRaw ?? loadParser(filename))(filename ?? `module.${lang}`, code, {
|
|
177
|
+
lang: lang === "js" ? "jsx" : lang,
|
|
178
|
+
sourceType: "module",
|
|
179
|
+
preserveParens: false
|
|
180
|
+
});
|
|
181
|
+
const first = result.errors[0];
|
|
182
|
+
if (first !== void 0) {
|
|
183
|
+
const offset = first.labels?.[0]?.start;
|
|
184
|
+
throw diagnosticError({
|
|
185
|
+
code: "extract-parse-error",
|
|
186
|
+
severity: "error",
|
|
187
|
+
message: `Cannot parse${filename === void 0 ? "" : ` ${filename}`}: ${first.message}`,
|
|
188
|
+
...filename === void 0 ? {} : { file: filename },
|
|
189
|
+
...offset === void 0 ? {} : lineColumn(code, offset)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const parsed = {
|
|
193
|
+
code,
|
|
194
|
+
lang,
|
|
195
|
+
program: result.program
|
|
196
|
+
};
|
|
197
|
+
if (result.comments !== void 0) parsed.comments = result.comments;
|
|
198
|
+
return parsed;
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/core/extract/entities.ts
|
|
202
|
+
/** HTML entities JSX text may spell; React and Solid decode them the same way. */
|
|
203
|
+
const ENTITIES = {
|
|
204
|
+
amp: "&",
|
|
205
|
+
lt: "<",
|
|
206
|
+
gt: ">",
|
|
207
|
+
quot: "\"",
|
|
208
|
+
apos: "'",
|
|
209
|
+
nbsp: "\xA0",
|
|
210
|
+
ndash: "–",
|
|
211
|
+
mdash: "—",
|
|
212
|
+
hellip: "…",
|
|
213
|
+
lsquo: "‘",
|
|
214
|
+
rsquo: "’",
|
|
215
|
+
ldquo: "“",
|
|
216
|
+
rdquo: "”",
|
|
217
|
+
copy: "©",
|
|
218
|
+
reg: "®",
|
|
219
|
+
trade: "™",
|
|
220
|
+
middot: "·",
|
|
221
|
+
times: "×"
|
|
222
|
+
};
|
|
223
|
+
function decodeEntities(text) {
|
|
224
|
+
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, name) => {
|
|
225
|
+
if (name[0] === "#") {
|
|
226
|
+
const code = name[1] === "x" || name[1] === "X" ? parseInt(name.slice(2), 16) : +name.slice(1);
|
|
227
|
+
return code <= 1114111 ? String.fromCodePoint(code) : whole;
|
|
228
|
+
}
|
|
229
|
+
return Object.hasOwn(ENTITIES, name) ? ENTITIES[name] : whole;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/core/extract/macro.ts
|
|
234
|
+
/** The local the expansion calls, imported once per expanded module. */
|
|
235
|
+
const MACRO_RICH = "__vi18nRich";
|
|
236
|
+
const IMPORT = `import { rich as ${MACRO_RICH} } from "@menapo/vi18n";`;
|
|
237
|
+
/** The handlers' children parameter. */
|
|
238
|
+
const CHILDREN = "__vi18nChildren";
|
|
239
|
+
const TYPE_KEYS = /* @__PURE__ */ new Set([
|
|
240
|
+
"typeAnnotation",
|
|
241
|
+
"typeParameters",
|
|
242
|
+
"typeArguments",
|
|
243
|
+
"returnType"
|
|
244
|
+
]);
|
|
245
|
+
/** Local names bound to `T` imported from a vi18n import source. */
|
|
246
|
+
function macroNames(imports, sources) {
|
|
247
|
+
const names = /* @__PURE__ */ new Set();
|
|
248
|
+
for (const record of imports) {
|
|
249
|
+
if (!sources.has(record.specifier)) continue;
|
|
250
|
+
for (const { local, imported } of record.bindings) if (imported === "T") names.add(local);
|
|
251
|
+
}
|
|
252
|
+
return names;
|
|
253
|
+
}
|
|
254
|
+
var MacroError = class extends Error {
|
|
255
|
+
code;
|
|
256
|
+
node;
|
|
257
|
+
constructor(code, message, node) {
|
|
258
|
+
super(message);
|
|
259
|
+
this.code = code;
|
|
260
|
+
this.node = node;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
/** JSX's whitespace rule (Babel's `cleanJSXElementLiteralChild`, which Solid follows). */
|
|
264
|
+
function cleanText(raw) {
|
|
265
|
+
const lines = raw.split(/\r\n|\n|\r/);
|
|
266
|
+
let lastNonEmpty = 0;
|
|
267
|
+
for (let i = 0; i < lines.length; i++) if (/[^ \t]/.test(lines[i])) lastNonEmpty = i;
|
|
268
|
+
let out = "";
|
|
269
|
+
for (let i = 0; i < lines.length; i++) {
|
|
270
|
+
let line = lines[i].replace(/\t/g, " ");
|
|
271
|
+
if (i !== 0) line = line.replace(/^ +/, "");
|
|
272
|
+
if (i !== lines.length - 1) line = line.replace(/ +$/, "");
|
|
273
|
+
if (line) out += i === lastNonEmpty ? line : line + " ";
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
/** Literal text as ICU source: syntax characters quoted, every apostrophe doubled. */
|
|
278
|
+
function icuText(text) {
|
|
279
|
+
return text.replace(/'/g, "''").replace(/[{}<>]+/g, (run) => `'${run}'`);
|
|
280
|
+
}
|
|
281
|
+
/** Tag names the compiler accepts (INTERNALS §2.1): an ASCII letter, then name characters. */
|
|
282
|
+
const TAG_NAME = /^[A-Za-z][\w.-]*$/;
|
|
283
|
+
var Expander = class {
|
|
284
|
+
code;
|
|
285
|
+
constructor(code) {
|
|
286
|
+
this.code = code;
|
|
287
|
+
}
|
|
288
|
+
/** `<T id? context? maxLength?>children</T>` → the `rich()` call. */
|
|
289
|
+
expand(element) {
|
|
290
|
+
const opening = element.openingElement;
|
|
291
|
+
const descriptor = [];
|
|
292
|
+
let spread = null;
|
|
293
|
+
for (const attribute of opening.attributes) {
|
|
294
|
+
if (attribute.type !== "JSXAttribute") throw new MacroError("extract-non-literal-option", "<T> takes only literal id, context and maxLength attributes and values={…}, not a spread", attribute);
|
|
295
|
+
const name = keyName(attribute.name, false);
|
|
296
|
+
const value = attribute.value;
|
|
297
|
+
if (name === "values" && value?.type === "JSXExpressionContainer") {
|
|
298
|
+
const expression = value.expression;
|
|
299
|
+
spread = `...(${this.code.slice(expression.start, expression.end)})`;
|
|
300
|
+
} else descriptor.push(this.attribute(name, attribute));
|
|
301
|
+
}
|
|
302
|
+
const out = {
|
|
303
|
+
message: "",
|
|
304
|
+
values: [],
|
|
305
|
+
tags: /* @__PURE__ */ new Map(),
|
|
306
|
+
args: /* @__PURE__ */ new Map()
|
|
307
|
+
};
|
|
308
|
+
if (spread !== null) out.values.push(spread);
|
|
309
|
+
this.children(element.children, out);
|
|
310
|
+
if (out.message.trim() === "") throw new MacroError("extract-dynamic-message", "<T> needs text to translate", element);
|
|
311
|
+
const message = JSON.stringify(out.message);
|
|
312
|
+
const key = descriptor.length === 0 ? message : `{ message: ${message}, ${descriptor.join(", ")} }`;
|
|
313
|
+
const values = out.values.length === 0 ? "" : `, { ${out.values.join(", ")} }`;
|
|
314
|
+
return `${MACRO_RICH}(${key}${values})`;
|
|
315
|
+
}
|
|
316
|
+
attribute(name, attribute) {
|
|
317
|
+
const value = attribute.value;
|
|
318
|
+
const inner = value?.type === "JSXExpressionContainer" ? unwrap(value.expression) : value;
|
|
319
|
+
const invalid = (what) => new MacroError("extract-non-literal-option", `<T> takes only literal id, context and maxLength attributes and values={…}: ${what}`, attribute);
|
|
320
|
+
if (name === "id" || name === "context") {
|
|
321
|
+
if (inner?.type !== "Literal" || typeof inner.value !== "string") throw invalid(`${name} is not a string literal`);
|
|
322
|
+
return `${name}: ${JSON.stringify(inner.value)}`;
|
|
323
|
+
}
|
|
324
|
+
if (name === "maxLength") {
|
|
325
|
+
if (inner?.type !== "Literal" || typeof inner.value !== "number") throw invalid("maxLength is not a number literal");
|
|
326
|
+
return `maxLength: ${inner.value}`;
|
|
327
|
+
}
|
|
328
|
+
throw invalid(`unknown attribute ${name ?? "?"}`);
|
|
329
|
+
}
|
|
330
|
+
children(children, out) {
|
|
331
|
+
for (const child of children) switch (child.type) {
|
|
332
|
+
case "JSXText":
|
|
333
|
+
out.message += icuText(cleanText(decodeEntities(this.code.slice(child.start, child.end))));
|
|
334
|
+
break;
|
|
335
|
+
case "JSXExpressionContainer":
|
|
336
|
+
this.expression(child.expression, out);
|
|
337
|
+
break;
|
|
338
|
+
case "JSXFragment":
|
|
339
|
+
this.children(child.children, out);
|
|
340
|
+
break;
|
|
341
|
+
case "JSXElement":
|
|
342
|
+
this.element(child, out);
|
|
343
|
+
break;
|
|
344
|
+
default: throw new MacroError("extract-dynamic-message", "<T> children are text, elements, {name} arguments and ICU strings", child);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* `{name}` and `{user.name}` are arguments named by their last identifier,
|
|
349
|
+
* `{{ count: items.length }}` names one explicitly, and a string literal
|
|
350
|
+
* is ICU source as written, the way to a plural or a select. Anything else
|
|
351
|
+
* would hide text from translators.
|
|
352
|
+
*/
|
|
353
|
+
expression(expression, out) {
|
|
354
|
+
if (expression.type === "JSXEmptyExpression") return;
|
|
355
|
+
const node = unwrap(expression);
|
|
356
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
357
|
+
out.message += node.value;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
|
|
361
|
+
out.message += node.quasis[0].value.cooked;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
let name = null;
|
|
365
|
+
let value = node;
|
|
366
|
+
if (node.type === "Identifier") name = node.name;
|
|
367
|
+
else if (node.type === "MemberExpression" && node.computed !== true) name = keyName(node.property, false);
|
|
368
|
+
else if (node.type === "ObjectExpression") {
|
|
369
|
+
const properties = node.properties;
|
|
370
|
+
const only = properties[0];
|
|
371
|
+
if (properties.length === 1 && only?.type === "Property" && only.computed !== true) {
|
|
372
|
+
name = keyName(only.key, false);
|
|
373
|
+
value = only.value;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (name === null || !/^[\p{L}_$][\p{L}\p{N}_$]*$/u.test(name)) throw new MacroError("extract-dynamic-message", `<T> takes {name}, {obj.name} or {{ name: value }} as an argument, not {${this.code.slice(node.start, node.end)}}`, node);
|
|
377
|
+
const source = this.code.slice(value.start, value.end);
|
|
378
|
+
const previous = out.args.get(name);
|
|
379
|
+
if (previous !== void 0 && previous !== source) throw new MacroError("extract-dynamic-message", `<T> argument {${name}} stands for two values (${previous}, ${source}); name one with {{ other: … }}`, node);
|
|
380
|
+
if (previous === void 0) {
|
|
381
|
+
out.args.set(name, source);
|
|
382
|
+
out.values.push(source === name ? name : `${JSON.stringify(name)}: ${source}`);
|
|
383
|
+
}
|
|
384
|
+
out.message += `{${name}}`;
|
|
385
|
+
}
|
|
386
|
+
/** A nested element: a tag named after it, its markup moved into the handler. */
|
|
387
|
+
element(element, out) {
|
|
388
|
+
const opening = element.openingElement;
|
|
389
|
+
const closing = element.closingElement;
|
|
390
|
+
const base = this.tagName(opening.name);
|
|
391
|
+
const children = element.children;
|
|
392
|
+
const standalone = closing === null || children.length === 0;
|
|
393
|
+
const open = this.code.slice(opening.start, opening.end);
|
|
394
|
+
const handler = standalone ? `() => ${this.code.slice(element.start, element.end)}` : `(${CHILDREN}) => ${open}{${CHILDREN}}${this.code.slice(closing.start, closing.end)}`;
|
|
395
|
+
let name = base;
|
|
396
|
+
for (let n = 2; out.tags.has(name) && out.tags.get(name) !== handler; n++) name = base + n;
|
|
397
|
+
if (!out.tags.has(name)) {
|
|
398
|
+
out.tags.set(name, handler);
|
|
399
|
+
out.values.push(`${JSON.stringify(name)}: ${handler}`);
|
|
400
|
+
}
|
|
401
|
+
if (standalone) {
|
|
402
|
+
out.message += `<${name}/>`;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
out.message += `<${name}>`;
|
|
406
|
+
this.children(children, out);
|
|
407
|
+
out.message += `</${name}>`;
|
|
408
|
+
}
|
|
409
|
+
tagName(name) {
|
|
410
|
+
const text = name.type === "JSXIdentifier" ? name.name : name.type === "JSXMemberExpression" ? this.code.slice(name.start, name.end) : "";
|
|
411
|
+
if (!TAG_NAME.test(text)) throw new MacroError("extract-dynamic-message", `<T> cannot name a tag after <${this.code.slice(name.start, name.end)}>: wrap it in an element or component with a plain name`, name);
|
|
412
|
+
return text;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* Expands every `<T>` whose name is in `options.names` (a `<T>` inside
|
|
417
|
+
* another is a build error). Returns the diagnostics; the sink holds the
|
|
418
|
+
* edits, and the module gets one `rich` import when anything expanded.
|
|
419
|
+
*/
|
|
420
|
+
function expandMacros(parsed, sink, options) {
|
|
421
|
+
const code = parsed.code;
|
|
422
|
+
const program = parsed.program;
|
|
423
|
+
const expander = new Expander(code);
|
|
424
|
+
const diagnostics = [];
|
|
425
|
+
let edited = false;
|
|
426
|
+
const isMacro = (node) => {
|
|
427
|
+
const name = node.openingElement.name;
|
|
428
|
+
return name.type === "JSXIdentifier" && options.names.has(name.name);
|
|
429
|
+
};
|
|
430
|
+
const stack = [{
|
|
431
|
+
node: program,
|
|
432
|
+
child: false
|
|
433
|
+
}];
|
|
434
|
+
for (let next = stack.pop(); next !== void 0; next = stack.pop()) {
|
|
435
|
+
const { node, child } = next;
|
|
436
|
+
if (node.type === "JSXElement" && isMacro(node)) {
|
|
437
|
+
try {
|
|
438
|
+
const nested = findNested(node, isMacro);
|
|
439
|
+
if (nested !== null) throw new MacroError("extract-dynamic-message", "<T> cannot contain another <T>", nested);
|
|
440
|
+
let call = expander.expand(node);
|
|
441
|
+
if (options.keepLines === true) call += "\n".repeat(code.slice(node.start, node.end).split("\n").length - 1);
|
|
442
|
+
sink.overwrite(node.start, node.end, child ? `{${call}}` : `<>{${call}}</>`);
|
|
443
|
+
edited = true;
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (!(error instanceof MacroError)) throw error;
|
|
446
|
+
diagnostics.push({
|
|
447
|
+
code: error.code,
|
|
448
|
+
severity: "error",
|
|
449
|
+
message: error.message,
|
|
450
|
+
file: options.refFile,
|
|
451
|
+
...lineColumn(code, error.node.start)
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const jsx = node.type === "JSXElement" || node.type === "JSXFragment";
|
|
457
|
+
for (const key in node) {
|
|
458
|
+
if (key === "type" || key === "start" || key === "end" || TYPE_KEYS.has(key)) continue;
|
|
459
|
+
const value = node[key];
|
|
460
|
+
if (value === null || typeof value !== "object") continue;
|
|
461
|
+
const isChildren = jsx && key === "children";
|
|
462
|
+
if (Array.isArray(value)) for (let i = value.length - 1; i >= 0; i--) {
|
|
463
|
+
const item = value[i];
|
|
464
|
+
if (item !== null && typeof item?.type === "string") stack.push({
|
|
465
|
+
node: item,
|
|
466
|
+
child: isChildren
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
else if (typeof value.type === "string") stack.push({
|
|
470
|
+
node: value,
|
|
471
|
+
child: false
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (edited) sink.appendLeft(afterDirectives(program), IMPORT);
|
|
476
|
+
return {
|
|
477
|
+
edited,
|
|
478
|
+
diagnostics
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/** A `<T>` inside `element`'s children, at any depth. */
|
|
482
|
+
function findNested(element, isMacro) {
|
|
483
|
+
const stack = [...element.children];
|
|
484
|
+
for (let node = stack.pop(); node !== void 0; node = stack.pop()) {
|
|
485
|
+
if (node.type === "JSXElement" && isMacro(node)) return node;
|
|
486
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment") stack.push(...node.children);
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
/** Where an import may go: after `"use client"` and other directives. */
|
|
491
|
+
function afterDirectives(program) {
|
|
492
|
+
let at = 0;
|
|
493
|
+
for (const statement of program.body) {
|
|
494
|
+
if (statement.type !== "ExpressionStatement" || typeof statement.directive !== "string") break;
|
|
495
|
+
at = statement.end;
|
|
496
|
+
}
|
|
497
|
+
return at;
|
|
498
|
+
}
|
|
499
|
+
//#endregion
|
|
500
|
+
//#region src/core/extract/sfc-parse.ts
|
|
501
|
+
/** Thrown inside a walk for a shape parse-only extraction does not cover. */
|
|
502
|
+
var Unproven = class extends Error {};
|
|
503
|
+
function unproven(reason) {
|
|
504
|
+
throw new Unproven(reason);
|
|
505
|
+
}
|
|
506
|
+
function pick$1(module, name) {
|
|
507
|
+
return module[name] ?? module.default?.[name];
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* An expression from the template on lines of its own, so a trailing `//`
|
|
511
|
+
* comment cannot swallow what follows.
|
|
512
|
+
*/
|
|
513
|
+
function expr(text) {
|
|
514
|
+
return "(\n" + text + "\n)";
|
|
515
|
+
}
|
|
516
|
+
/** `code` with `ranges` replaced by spaces, line breaks kept. */
|
|
517
|
+
function blank(code, ranges) {
|
|
518
|
+
if (ranges.length === 0) return code;
|
|
519
|
+
const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
|
|
520
|
+
let out = "";
|
|
521
|
+
let at = 0;
|
|
522
|
+
for (const [start, end] of sorted) {
|
|
523
|
+
if (start < at) continue;
|
|
524
|
+
out += code.slice(at, start) + code.slice(start, end).replace(/[^\n]/g, " ");
|
|
525
|
+
at = end;
|
|
526
|
+
}
|
|
527
|
+
return out + code.slice(at);
|
|
528
|
+
}
|
|
529
|
+
/** Top-level binding names of a program (imports and declarations). */
|
|
530
|
+
function topLevelNames(program, into) {
|
|
531
|
+
const pattern = (node) => {
|
|
532
|
+
if (node === null || node === void 0) return;
|
|
533
|
+
switch (node.type) {
|
|
534
|
+
case "Identifier":
|
|
535
|
+
into.add(node.name);
|
|
536
|
+
return;
|
|
537
|
+
case "ObjectPattern":
|
|
538
|
+
for (const property of node.properties) pattern(property.type === "RestElement" ? property.argument : property.value);
|
|
539
|
+
return;
|
|
540
|
+
case "ArrayPattern":
|
|
541
|
+
for (const element of node.elements) pattern(element);
|
|
542
|
+
return;
|
|
543
|
+
case "AssignmentPattern":
|
|
544
|
+
pattern(node.left);
|
|
545
|
+
return;
|
|
546
|
+
case "RestElement":
|
|
547
|
+
pattern(node.argument);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
for (let statement of program.body) {
|
|
552
|
+
if ((statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration") && statement.declaration !== null && statement.declaration !== void 0) statement = statement.declaration;
|
|
553
|
+
switch (statement.type) {
|
|
554
|
+
case "ImportDeclaration":
|
|
555
|
+
for (const specifier of statement.specifiers) into.add(specifier.local.name);
|
|
556
|
+
break;
|
|
557
|
+
case "VariableDeclaration":
|
|
558
|
+
for (const declarator of statement.declarations) pattern(declarator.id);
|
|
559
|
+
break;
|
|
560
|
+
case "FunctionDeclaration":
|
|
561
|
+
case "ClassDeclaration":
|
|
562
|
+
case "TSEnumDeclaration": if (statement.id !== null && statement.id !== void 0) into.add(statement.id.name);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/** `@vue/compiler-core` NodeTypes and ElementTypes of the raw template AST. */
|
|
567
|
+
const ELEMENT = 1;
|
|
568
|
+
const TEXT = 2;
|
|
569
|
+
const COMMENT = 3;
|
|
570
|
+
const INTERPOLATION = 5;
|
|
571
|
+
const ATTRIBUTE = 6;
|
|
572
|
+
const DIRECTIVE = 7;
|
|
573
|
+
const COMPONENT = 1;
|
|
574
|
+
/** The setup macros whose arguments Vue moves into the component options, at module level. */
|
|
575
|
+
const VUE_MACROS = /* @__PURE__ */ new Set([
|
|
576
|
+
"defineProps",
|
|
577
|
+
"withDefaults",
|
|
578
|
+
"defineEmits",
|
|
579
|
+
"defineOptions",
|
|
580
|
+
"defineModel"
|
|
581
|
+
]);
|
|
582
|
+
/** Components Vue resolves before setup bindings (compiler-core `resolveComponentType`). */
|
|
583
|
+
const VUE_BUILT_INS = /* @__PURE__ */ new Set([
|
|
584
|
+
"component",
|
|
585
|
+
"Component",
|
|
586
|
+
"teleport",
|
|
587
|
+
"Teleport",
|
|
588
|
+
"suspense",
|
|
589
|
+
"Suspense",
|
|
590
|
+
"keep-alive",
|
|
591
|
+
"KeepAlive",
|
|
592
|
+
"base-transition",
|
|
593
|
+
"BaseTransition",
|
|
594
|
+
"transition",
|
|
595
|
+
"Transition",
|
|
596
|
+
"transition-group",
|
|
597
|
+
"TransitionGroup"
|
|
598
|
+
]);
|
|
599
|
+
/** `v-on` values Vue passes as they are instead of wrapping them in `$event => (…)`. */
|
|
600
|
+
const VUE_FN_EXPRESSION = /^\s*(?:async\s*)?(?:\([^)]*?\)|[\w$]+)\s*(?::[^=]+)?=>|^\s*(?:async\s+)?function(?:\s+[\w$]+)?\s*\(/;
|
|
601
|
+
/** `v-for`'s `alias in source`, as compiler-core splits it. */
|
|
602
|
+
const VUE_FOR = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/;
|
|
603
|
+
const camelize = (name) => name.replace(/-(\w)/g, (_all, c) => c.toUpperCase());
|
|
604
|
+
const capitalize = (name) => name.charAt(0).toUpperCase() + name.slice(1);
|
|
605
|
+
function modifierNames(prop) {
|
|
606
|
+
return (prop.modifiers ?? []).map((m) => typeof m === "string" ? m : m.content);
|
|
607
|
+
}
|
|
608
|
+
var VueTemplate = class {
|
|
609
|
+
out = [];
|
|
610
|
+
/**
|
|
611
|
+
* `bindings` holds the top-level names of `<script setup>` mode, where a
|
|
612
|
+
* component tag naming one is that binding (`_createVNode(T, …)`); null
|
|
613
|
+
* outside it, where Vue resolves components by name at runtime.
|
|
614
|
+
*/
|
|
615
|
+
bindings;
|
|
616
|
+
constructor(bindings) {
|
|
617
|
+
this.bindings = bindings;
|
|
618
|
+
}
|
|
619
|
+
children(nodes) {
|
|
620
|
+
for (const node of nodes ?? []) this.node(node);
|
|
621
|
+
}
|
|
622
|
+
node(node) {
|
|
623
|
+
switch (node.type) {
|
|
624
|
+
case TEXT:
|
|
625
|
+
case COMMENT: return;
|
|
626
|
+
case INTERPOLATION:
|
|
627
|
+
this.out.push(expr(node.content.content) + ";");
|
|
628
|
+
return;
|
|
629
|
+
case ELEMENT:
|
|
630
|
+
this.element(node);
|
|
631
|
+
return;
|
|
632
|
+
default: unproven(`vue-node-${node.type}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/** The binding a component tag resolves to in `<script setup>` mode, as compiler-core finds it. */
|
|
636
|
+
componentBinding(tag) {
|
|
637
|
+
if (this.bindings === null || VUE_BUILT_INS.has(tag)) return null;
|
|
638
|
+
for (const name of [
|
|
639
|
+
tag,
|
|
640
|
+
camelize(tag),
|
|
641
|
+
capitalize(camelize(tag))
|
|
642
|
+
]) if (this.bindings.has(name)) return name;
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
element(node) {
|
|
646
|
+
const props = node.props ?? [];
|
|
647
|
+
let closers = 0;
|
|
648
|
+
for (const prop of props) if (prop.type === DIRECTIVE && (prop.name === "if" || prop.name === "else-if")) {
|
|
649
|
+
if (prop.exp !== void 0) this.out.push(expr(prop.exp.content) + ";");
|
|
650
|
+
}
|
|
651
|
+
const loop = props.find((p) => p.type === DIRECTIVE && p.name === "for");
|
|
652
|
+
if (loop !== void 0) {
|
|
653
|
+
const match = VUE_FOR.exec(loop.exp?.content ?? "");
|
|
654
|
+
if (match === null) unproven("vue-for");
|
|
655
|
+
const alias = match[1].trim().replace(/^\(|\)$/g, "");
|
|
656
|
+
this.out.push(expr(match[2].trim()) + ";");
|
|
657
|
+
this.out.push(`((${alias}) => {`);
|
|
658
|
+
closers++;
|
|
659
|
+
}
|
|
660
|
+
const component = node.tagType === COMPONENT ? this.componentBinding(node.tag) : null;
|
|
661
|
+
if (this.bindings === null && node.tagType === COMPONENT && hasProp(props, "message")) unproven("vue-options-component");
|
|
662
|
+
const entries = [];
|
|
663
|
+
const statements = [];
|
|
664
|
+
const merged = [];
|
|
665
|
+
let slot;
|
|
666
|
+
for (const prop of props) {
|
|
667
|
+
if (prop.type === ATTRIBUTE) {
|
|
668
|
+
if (component !== null) entries.push(`${JSON.stringify(prop.name)}: ${JSON.stringify(prop.value?.content ?? "")}`);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (prop.type !== DIRECTIVE) unproven(`vue-prop-${prop.type}`);
|
|
672
|
+
const exp = prop.exp?.content;
|
|
673
|
+
const arg = prop.arg;
|
|
674
|
+
const dynamic = arg !== void 0 && arg.isStatic === false;
|
|
675
|
+
if (dynamic) statements.push(expr(arg.content) + ";");
|
|
676
|
+
switch (prop.name) {
|
|
677
|
+
case "if":
|
|
678
|
+
case "else-if":
|
|
679
|
+
case "else":
|
|
680
|
+
case "for": break;
|
|
681
|
+
case "slot":
|
|
682
|
+
slot = prop;
|
|
683
|
+
break;
|
|
684
|
+
case "bind": {
|
|
685
|
+
if (arg === void 0) {
|
|
686
|
+
if (exp !== void 0) merged.push(expr(exp));
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
if (dynamic) {
|
|
690
|
+
merged.push(`{ [${expr(arg.content)}]: ${exp === void 0 ? "0" : expr(exp)} }`);
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
const modifiers = modifierNames(prop);
|
|
694
|
+
let key = modifiers.includes("camel") ? camelize(arg.content) : arg.content;
|
|
695
|
+
if (modifiers.includes("prop")) key = "." + key;
|
|
696
|
+
else if (modifiers.includes("attr")) key = "^" + key;
|
|
697
|
+
entries.push(`${JSON.stringify(key)}: ${exp === void 0 ? camelize(arg.content) : expr(exp)}`);
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
case "on": {
|
|
701
|
+
const handler = exp === void 0 ? "null" : VUE_FN_EXPRESSION.test(exp) || /^\s*[\w$]+(?:\s*\.\s*[\w$]+)*\s*$/.test(exp) ? expr(exp) : `($event) => {\n${exp}\n}`;
|
|
702
|
+
if (arg === void 0 || dynamic) merged.push(handler);
|
|
703
|
+
else entries.push(`${JSON.stringify("on:" + arg.content)}: ${handler}`);
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
case "model":
|
|
707
|
+
if (exp !== void 0) {
|
|
708
|
+
const key = arg === void 0 || dynamic ? "modelValue" : arg.content;
|
|
709
|
+
if (dynamic) merged.push(expr(exp));
|
|
710
|
+
else entries.push(`${JSON.stringify(key)}: ${expr(exp)}`);
|
|
711
|
+
}
|
|
712
|
+
break;
|
|
713
|
+
case "html":
|
|
714
|
+
if (exp !== void 0) entries.push(`innerHTML: ${expr(exp)}`);
|
|
715
|
+
break;
|
|
716
|
+
case "text":
|
|
717
|
+
if (exp !== void 0) entries.push(`textContent: ${expr(exp)}`);
|
|
718
|
+
break;
|
|
719
|
+
default: if (exp !== void 0) statements.push(expr(exp) + ";");
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
let props_ = `{${entries.length === 0 ? "" : "\n" + entries.join(",\n") + "\n"}}`;
|
|
723
|
+
if (merged.length > 0) props_ = `__vi18n_merge(${props_}, ${merged.join(", ")})`;
|
|
724
|
+
this.out.push(component === null ? `__vi18n_node(${props_});` : `__vi18n_vnode(${component}, ${props_});`);
|
|
725
|
+
for (const statement of statements) this.out.push(statement);
|
|
726
|
+
if (slot?.exp !== void 0) {
|
|
727
|
+
this.out.push(`((${slot.exp.content}) => {`);
|
|
728
|
+
closers++;
|
|
729
|
+
}
|
|
730
|
+
this.children(node.children);
|
|
731
|
+
for (let i = 0; i < closers; i++) this.out.push("});");
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
function hasProp(props, name) {
|
|
735
|
+
return props.some((p) => p.type === ATTRIBUTE ? p.name === name : p.arg !== void 0 && (p.arg.isStatic === false || camelize(p.arg.content) === name));
|
|
736
|
+
}
|
|
737
|
+
function vueLang(lang) {
|
|
738
|
+
return lang === "ts" || lang === "tsx" || lang === "jsx" ? lang : "js";
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Script setup statements that Vue's compileScript moves: imports to module
|
|
742
|
+
* scope, and the arguments of the options macros into the component options
|
|
743
|
+
* (module scope too, so a call directly in them is at module scope there).
|
|
744
|
+
*/
|
|
745
|
+
function setupLayout(setup, lang, file) {
|
|
746
|
+
let program;
|
|
747
|
+
try {
|
|
748
|
+
program = parseModule(setup, lang, file).program;
|
|
749
|
+
} catch {
|
|
750
|
+
unproven("script-syntax");
|
|
751
|
+
}
|
|
752
|
+
const imports = [];
|
|
753
|
+
const hoisted = [];
|
|
754
|
+
const blanks = [];
|
|
755
|
+
const macro = (node) => {
|
|
756
|
+
if (node === null || node === void 0) return;
|
|
757
|
+
let call = node;
|
|
758
|
+
while (call.type === "TSAsExpression" || call.type === "TSSatisfiesExpression" || call.type === "TSNonNullExpression") call = call.expression;
|
|
759
|
+
if (call.type !== "CallExpression") return;
|
|
760
|
+
const callee = call.callee;
|
|
761
|
+
if (callee.type !== "Identifier" || !VUE_MACROS.has(callee.name)) return;
|
|
762
|
+
const args = call.arguments;
|
|
763
|
+
if (callee.name === "withDefaults") macro(args[0]);
|
|
764
|
+
const moved = callee.name === "withDefaults" ? args.slice(1) : args;
|
|
765
|
+
if (moved.length === 0) return;
|
|
766
|
+
const start = moved[0].start;
|
|
767
|
+
const end = moved[moved.length - 1].end;
|
|
768
|
+
hoisted.push(expr(setup.slice(start, end)) + ";");
|
|
769
|
+
blanks.push([start, end]);
|
|
770
|
+
};
|
|
771
|
+
for (const statement of program.body) switch (statement.type) {
|
|
772
|
+
case "ImportDeclaration":
|
|
773
|
+
imports.push(setup.slice(statement.start, statement.end));
|
|
774
|
+
blanks.push([statement.start, statement.end]);
|
|
775
|
+
break;
|
|
776
|
+
case "ExportNamedDeclaration":
|
|
777
|
+
case "ExportDefaultDeclaration":
|
|
778
|
+
case "ExportAllDeclaration":
|
|
779
|
+
unproven("vue-setup-export");
|
|
780
|
+
break;
|
|
781
|
+
case "ExpressionStatement":
|
|
782
|
+
macro(statement.expression);
|
|
783
|
+
break;
|
|
784
|
+
case "VariableDeclaration": for (const declarator of statement.declarations) {
|
|
785
|
+
const init = declarator.init;
|
|
786
|
+
const id = declarator.id;
|
|
787
|
+
if (init !== null && id.type === "ObjectPattern" && /\b(?:defineProps|withDefaults)\s*[<(]/.test(setup.slice(init.start, init.end)) && id.properties.some((p) => p.value?.type === "AssignmentPattern")) unproven("vue-props-destructure-default");
|
|
788
|
+
macro(init);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
imports,
|
|
793
|
+
hoisted,
|
|
794
|
+
body: blank(setup, blanks),
|
|
795
|
+
program
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* The module parse-only extraction scans for a `.vue` file: `<script>` as it
|
|
800
|
+
* is, `<script setup>` imports and macro arguments at module level, its
|
|
801
|
+
* statements inside a setup function that ends with the template's
|
|
802
|
+
* expressions (the inline template of a production build), or the template
|
|
803
|
+
* in a render function of its own.
|
|
804
|
+
*/
|
|
805
|
+
function vueParseOnly(compiler, file, code) {
|
|
806
|
+
const parse = pick$1(compiler, "parse");
|
|
807
|
+
if (parse === void 0) return { fallback: "vue-api" };
|
|
808
|
+
try {
|
|
809
|
+
const { descriptor, errors } = parse(code, {
|
|
810
|
+
filename: file,
|
|
811
|
+
sourceMap: false,
|
|
812
|
+
templateParseOptions: {}
|
|
813
|
+
});
|
|
814
|
+
if (errors.length > 0) return { fallback: "vue-parse-error" };
|
|
815
|
+
const { script, scriptSetup, template } = descriptor;
|
|
816
|
+
if (script?.src !== void 0 || scriptSetup?.src !== void 0 || template?.src !== void 0) return { fallback: "vue-src" };
|
|
817
|
+
if (template !== null && template.lang !== void 0 && template.lang !== "html") return { fallback: "vue-template-lang" };
|
|
818
|
+
if (template !== null && template.ast === void 0) return { fallback: "vue-api" };
|
|
819
|
+
if (template?.content.includes("vi18n-") === true) return { fallback: "template-marker" };
|
|
820
|
+
if (script !== null && scriptSetup !== null && vueLang(script.lang) !== vueLang(scriptSetup.lang)) return { fallback: "vue-script-lang" };
|
|
821
|
+
const lang = vueLang(scriptSetup?.lang ?? script?.lang);
|
|
822
|
+
const parts = [];
|
|
823
|
+
if (script !== null) parts.push(script.content);
|
|
824
|
+
if (scriptSetup === null) {
|
|
825
|
+
const walk = new VueTemplate(null);
|
|
826
|
+
walk.children(template?.ast?.children);
|
|
827
|
+
parts.push("function __vi18n_render() {", ...walk.out, "}");
|
|
828
|
+
return {
|
|
829
|
+
code: parts.join("\n"),
|
|
830
|
+
lang
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
const layout = setupLayout(scriptSetup.content, lang, file);
|
|
834
|
+
const bindings = /* @__PURE__ */ new Set();
|
|
835
|
+
topLevelNames(layout.program, bindings);
|
|
836
|
+
if (script !== null) try {
|
|
837
|
+
topLevelNames(parseModule(script.content, lang, file).program, bindings);
|
|
838
|
+
} catch {
|
|
839
|
+
return { fallback: "script-syntax" };
|
|
840
|
+
}
|
|
841
|
+
const walk = new VueTemplate(bindings);
|
|
842
|
+
walk.children(template?.ast?.children);
|
|
843
|
+
parts.push(...layout.imports, ...layout.hoisted);
|
|
844
|
+
parts.push("async function __vi18n_setup() {", layout.body);
|
|
845
|
+
parts.push("function __vi18n_render() {", ...walk.out, "}", "}");
|
|
846
|
+
return {
|
|
847
|
+
code: parts.join("\n"),
|
|
848
|
+
lang
|
|
849
|
+
};
|
|
850
|
+
} catch (error) {
|
|
851
|
+
if (error instanceof Unproven) return { fallback: error.message };
|
|
852
|
+
return { fallback: "vue-parse-error" };
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/** Elements whose attributes and children are plain template code. */
|
|
856
|
+
const SVELTE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
857
|
+
"RegularElement",
|
|
858
|
+
"SvelteElement",
|
|
859
|
+
"SvelteComponent",
|
|
860
|
+
"SvelteSelf",
|
|
861
|
+
"SvelteHead",
|
|
862
|
+
"SvelteBody",
|
|
863
|
+
"SvelteWindow",
|
|
864
|
+
"SvelteDocument",
|
|
865
|
+
"SvelteFragment",
|
|
866
|
+
"SvelteBoundary",
|
|
867
|
+
"SlotElement",
|
|
868
|
+
"TitleElement"
|
|
869
|
+
]);
|
|
870
|
+
/**
|
|
871
|
+
* Svelte 5's runes; any other `$name` reads a store (`$.store_get(…)` in
|
|
872
|
+
* compiled code, which the scanner cannot follow) or is a compile error.
|
|
873
|
+
*/
|
|
874
|
+
const SVELTE_RUNES = /* @__PURE__ */ new Set([
|
|
875
|
+
"$state",
|
|
876
|
+
"$derived",
|
|
877
|
+
"$effect",
|
|
878
|
+
"$props",
|
|
879
|
+
"$bindable",
|
|
880
|
+
"$inspect",
|
|
881
|
+
"$host"
|
|
882
|
+
]);
|
|
883
|
+
/** True when an identifier anywhere in `root` subscribes to a store. */
|
|
884
|
+
function readsStore(root) {
|
|
885
|
+
const stack = [root];
|
|
886
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
887
|
+
while (stack.length > 0) {
|
|
888
|
+
const node = stack.pop();
|
|
889
|
+
if (typeof node !== "object" || node === null || seen.has(node)) continue;
|
|
890
|
+
seen.add(node);
|
|
891
|
+
if (Array.isArray(node)) {
|
|
892
|
+
for (const item of node) stack.push(item);
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
const record = node;
|
|
896
|
+
if (record.type === "Identifier") {
|
|
897
|
+
const name = record.name;
|
|
898
|
+
if (/^\$[A-Za-z_]/.test(name) && !SVELTE_RUNES.has(name)) return true;
|
|
899
|
+
}
|
|
900
|
+
for (const key in record) if (key !== "loc" && key !== "metadata") stack.push(record[key]);
|
|
901
|
+
}
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
/** Text for a template literal: the characters it treats specially, escaped. */
|
|
905
|
+
function templateText(text) {
|
|
906
|
+
return text.replace(/[\\`]|\$\{/g, (c) => "\\" + c);
|
|
907
|
+
}
|
|
908
|
+
var SvelteTemplate = class {
|
|
909
|
+
out = [];
|
|
910
|
+
code;
|
|
911
|
+
constructor(code) {
|
|
912
|
+
this.code = code;
|
|
913
|
+
}
|
|
914
|
+
slice(node) {
|
|
915
|
+
return this.code.slice(node.start, node.end);
|
|
916
|
+
}
|
|
917
|
+
expression(node) {
|
|
918
|
+
return expr(this.slice(node));
|
|
919
|
+
}
|
|
920
|
+
fragment(fragment) {
|
|
921
|
+
for (const node of fragment?.nodes ?? []) this.node(node);
|
|
922
|
+
}
|
|
923
|
+
node(node) {
|
|
924
|
+
switch (node.type) {
|
|
925
|
+
case "Text":
|
|
926
|
+
case "Comment":
|
|
927
|
+
case "DebugTag": return;
|
|
928
|
+
case "ExpressionTag":
|
|
929
|
+
case "RenderTag":
|
|
930
|
+
this.out.push(this.expression(node.expression) + ";");
|
|
931
|
+
return;
|
|
932
|
+
case "ConstTag": {
|
|
933
|
+
const declarator = node.declaration.declarations[0];
|
|
934
|
+
this.out.push(`const ${this.slice(declarator.id)} = __vi18n_derived(() => ${this.expression(declarator.init)});`);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
case "HtmlTag":
|
|
938
|
+
this.out.push(`$.html(null, () => ${this.expression(node.expression)});`);
|
|
939
|
+
return;
|
|
940
|
+
case "IfBlock":
|
|
941
|
+
this.out.push(this.expression(node.test) + ";");
|
|
942
|
+
this.fragment(node.consequent);
|
|
943
|
+
this.fragment(node.alternate);
|
|
944
|
+
return;
|
|
945
|
+
case "EachBlock": {
|
|
946
|
+
this.out.push(this.expression(node.expression) + ";");
|
|
947
|
+
const params = [];
|
|
948
|
+
const context = node.context;
|
|
949
|
+
params.push(context === null ? "__vi18n_item" : this.slice(context));
|
|
950
|
+
if (typeof node.index === "string") params.push(node.index);
|
|
951
|
+
this.out.push(`((${params.join(", ")}) => {`);
|
|
952
|
+
if (node.key !== null && node.key !== void 0) this.out.push(this.expression(node.key) + ";");
|
|
953
|
+
this.fragment(node.body);
|
|
954
|
+
this.out.push("});");
|
|
955
|
+
this.fragment(node.fallback);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
case "AwaitBlock":
|
|
959
|
+
this.out.push(this.expression(node.expression) + ";");
|
|
960
|
+
this.fragment(node.pending);
|
|
961
|
+
this.scoped(node.value, node.then);
|
|
962
|
+
this.scoped(node.error, node.catch);
|
|
963
|
+
return;
|
|
964
|
+
case "KeyBlock":
|
|
965
|
+
this.out.push(this.expression(node.expression) + ";");
|
|
966
|
+
this.fragment(node.fragment);
|
|
967
|
+
return;
|
|
968
|
+
case "SnippetBlock": {
|
|
969
|
+
const parameters = node.parameters;
|
|
970
|
+
const params = parameters.length === 0 ? "" : this.slice({
|
|
971
|
+
start: parameters[0].start,
|
|
972
|
+
end: parameters[parameters.length - 1].end
|
|
973
|
+
});
|
|
974
|
+
const name = node.expression.name;
|
|
975
|
+
this.out.push(`function ${name}(${params}) {`);
|
|
976
|
+
this.fragment(node.body);
|
|
977
|
+
this.out.push("}");
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
case "Component":
|
|
981
|
+
this.component(node);
|
|
982
|
+
return;
|
|
983
|
+
default:
|
|
984
|
+
if (!SVELTE_ELEMENTS.has(node.type)) unproven(`svelte-node-${node.type}`);
|
|
985
|
+
if (node.type === "SvelteElement") this.out.push(this.expression(node.tag) + ";");
|
|
986
|
+
if (node.type === "SvelteComponent") this.out.push(this.expression(node.expression) + ";");
|
|
987
|
+
for (const attribute of node.attributes) this.attribute(attribute);
|
|
988
|
+
this.fragment(node.fragment);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
scoped(pattern, body) {
|
|
992
|
+
if (body === null) return;
|
|
993
|
+
this.out.push(`((${pattern === null ? "" : this.slice(pattern)}) => {`);
|
|
994
|
+
this.fragment(body);
|
|
995
|
+
this.out.push("});");
|
|
996
|
+
}
|
|
997
|
+
/** An attribute's expressions, beside the element. */
|
|
998
|
+
attribute(attribute) {
|
|
999
|
+
switch (attribute.type) {
|
|
1000
|
+
case "Attribute": {
|
|
1001
|
+
const value = attribute.value;
|
|
1002
|
+
if (value === true) return;
|
|
1003
|
+
for (const part of Array.isArray(value) ? value : [value]) if (part.type === "ExpressionTag") this.out.push(this.expression(part.expression) + ";");
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
case "StyleDirective": {
|
|
1007
|
+
const value = attribute.value;
|
|
1008
|
+
if (value === true) return;
|
|
1009
|
+
for (const part of Array.isArray(value) ? value : [value]) if (part.type === "ExpressionTag") this.out.push(this.expression(part.expression) + ";");
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
case "SpreadAttribute":
|
|
1013
|
+
case "OnDirective":
|
|
1014
|
+
case "BindDirective":
|
|
1015
|
+
case "ClassDirective":
|
|
1016
|
+
case "TransitionDirective":
|
|
1017
|
+
case "AnimateDirective":
|
|
1018
|
+
case "UseDirective":
|
|
1019
|
+
case "AttachTag":
|
|
1020
|
+
if (attribute.expression !== null && attribute.expression !== void 0) this.out.push(this.expression(attribute.expression) + ";");
|
|
1021
|
+
return;
|
|
1022
|
+
default: unproven(`svelte-attribute-${attribute.type}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
/** `T(anchor, { props })`, as Svelte calls a component it imports. */
|
|
1026
|
+
component(node) {
|
|
1027
|
+
const entries = [];
|
|
1028
|
+
const merged = [];
|
|
1029
|
+
const statements = [];
|
|
1030
|
+
for (const attribute of node.attributes) {
|
|
1031
|
+
const name = attribute.name;
|
|
1032
|
+
switch (attribute.type) {
|
|
1033
|
+
case "Attribute": {
|
|
1034
|
+
const value = attribute.value;
|
|
1035
|
+
let text;
|
|
1036
|
+
if (value === true) text = "true";
|
|
1037
|
+
else {
|
|
1038
|
+
const parts = Array.isArray(value) ? value : [value];
|
|
1039
|
+
if (parts.length === 1 && parts[0]?.type === "ExpressionTag") text = this.expression(parts[0].expression);
|
|
1040
|
+
else if (parts.every((part) => part.type === "Text")) text = JSON.stringify(parts.map((part) => part.data).join(""));
|
|
1041
|
+
else text = "`" + parts.map((part) => part.type === "Text" ? templateText(part.data) : "${" + this.slice(part.expression) + "}").join("") + "`";
|
|
1042
|
+
}
|
|
1043
|
+
entries.push(`${JSON.stringify(name)}: ${text}`);
|
|
1044
|
+
break;
|
|
1045
|
+
}
|
|
1046
|
+
case "BindDirective":
|
|
1047
|
+
if (name === "this") statements.push(this.expression(attribute.expression) + ";");
|
|
1048
|
+
else entries.push(`${JSON.stringify(name)}: ${this.expression(attribute.expression)}`);
|
|
1049
|
+
break;
|
|
1050
|
+
case "SpreadAttribute":
|
|
1051
|
+
merged.push(this.expression(attribute.expression));
|
|
1052
|
+
break;
|
|
1053
|
+
case "OnDirective":
|
|
1054
|
+
case "AttachTag":
|
|
1055
|
+
if (attribute.expression !== null && attribute.expression !== void 0) statements.push(this.expression(attribute.expression) + ";");
|
|
1056
|
+
break;
|
|
1057
|
+
default: unproven(`svelte-attribute-${attribute.type}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
let props = `{${entries.length === 0 ? "" : "\n" + entries.join(",\n") + "\n"}}`;
|
|
1061
|
+
if (merged.length > 0) props = `__vi18n_merge(${props}, ${merged.join(", ")})`;
|
|
1062
|
+
const name = node.name;
|
|
1063
|
+
if (!/^[\p{L}_$][\p{L}\p{N}_$]*(?:\.[\p{L}_$][\p{L}\p{N}_$]*)*$/u.test(name)) unproven("svelte-component-name");
|
|
1064
|
+
this.out.push(`${name}(null, ${props});`);
|
|
1065
|
+
for (const statement of statements) this.out.push(statement);
|
|
1066
|
+
this.fragment(node.fragment);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
function isTs(code, script) {
|
|
1070
|
+
const open = code.slice(script.start, script.content.start);
|
|
1071
|
+
return /\blang\s*=\s*["']?(?:ts|typescript)\b/.test(open);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* A script block's code with what Svelte's compiler changes for the scan
|
|
1075
|
+
* undone: imports out (they are module statements), `export` keywords off
|
|
1076
|
+
* (component exports and legacy props stay declarations), and a `const`
|
|
1077
|
+
* holding `$state(text)` read as the text, which is how Svelte compiles a
|
|
1078
|
+
* state that can never be reassigned.
|
|
1079
|
+
*/
|
|
1080
|
+
function svelteScript(code, script, hoistImports) {
|
|
1081
|
+
const program = script.content;
|
|
1082
|
+
const base = program.start;
|
|
1083
|
+
const text = code.slice(program.start, program.end);
|
|
1084
|
+
const imports = [];
|
|
1085
|
+
const blanks = [];
|
|
1086
|
+
for (const statement of program.body) {
|
|
1087
|
+
if (statement.type === "ImportDeclaration" && hoistImports) {
|
|
1088
|
+
imports.push(code.slice(statement.start, statement.end));
|
|
1089
|
+
blanks.push([statement.start - base, statement.end - base]);
|
|
1090
|
+
} else if (statement.type === "ExportNamedDeclaration" && hoistImports) {
|
|
1091
|
+
const declaration = statement.declaration;
|
|
1092
|
+
if (declaration === null) blanks.push([statement.start - base, statement.end - base]);
|
|
1093
|
+
else blanks.push([statement.start - base, declaration.start - base]);
|
|
1094
|
+
} else if (statement.type === "ExportDefaultDeclaration" || statement.type === "ExportAllDeclaration") {
|
|
1095
|
+
if (hoistImports) unproven("svelte-export");
|
|
1096
|
+
}
|
|
1097
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration ?? statement : statement;
|
|
1098
|
+
if (declaration.type !== "VariableDeclaration" || declaration.kind !== "const") continue;
|
|
1099
|
+
for (const declarator of declaration.declarations) {
|
|
1100
|
+
const init = declarator.init;
|
|
1101
|
+
if (init?.type !== "CallExpression" || init.arguments.length !== 1) continue;
|
|
1102
|
+
const callee = code.slice(init.callee.start, init.callee.end);
|
|
1103
|
+
if (callee === "$state" || callee === "$state.raw") blanks.push([init.callee.start - base, init.callee.end - base]);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
imports,
|
|
1108
|
+
body: blank(text, blanks)
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* The module parse-only extraction scans for a `.svelte` file (Svelte 5):
|
|
1113
|
+
* `<script module>` at module level, instance imports at module level and
|
|
1114
|
+
* the rest of the instance script inside a component function that ends
|
|
1115
|
+
* with the template's expressions.
|
|
1116
|
+
*/
|
|
1117
|
+
function svelteParseOnly(compiler, file, code) {
|
|
1118
|
+
const parse = pick$1(compiler, "parse");
|
|
1119
|
+
const version = pick$1(compiler, "VERSION") ?? "5";
|
|
1120
|
+
if (parse === void 0 || Number.parseInt(version, 10) < 5) return { fallback: "svelte-api" };
|
|
1121
|
+
try {
|
|
1122
|
+
let root;
|
|
1123
|
+
try {
|
|
1124
|
+
root = parse(code, {
|
|
1125
|
+
modern: true,
|
|
1126
|
+
filename: file
|
|
1127
|
+
});
|
|
1128
|
+
} catch {
|
|
1129
|
+
return { fallback: "svelte-parse-error" };
|
|
1130
|
+
}
|
|
1131
|
+
const module = root.module ?? null;
|
|
1132
|
+
const scripts = [module, root.instance ?? null].filter((s) => s !== null);
|
|
1133
|
+
let template = code;
|
|
1134
|
+
for (const script of scripts) template = template.slice(0, script.start) + " ".repeat(script.end - script.start) + template.slice(script.end);
|
|
1135
|
+
if (template.includes("vi18n-")) return { fallback: "template-marker" };
|
|
1136
|
+
if (readsStore([root.fragment, ...scripts])) return { fallback: "svelte-store" };
|
|
1137
|
+
const lang = scripts.some((script) => isTs(code, script)) ? "ts" : "js";
|
|
1138
|
+
const parts = [];
|
|
1139
|
+
if (module !== null) parts.push(svelteScript(code, module, false).body);
|
|
1140
|
+
const instance = root.instance ? svelteScript(code, root.instance, true) : null;
|
|
1141
|
+
const walk = new SvelteTemplate(code);
|
|
1142
|
+
walk.fragment(root.fragment);
|
|
1143
|
+
if (instance !== null) parts.push(...instance.imports);
|
|
1144
|
+
parts.push("function __vi18n_component() {");
|
|
1145
|
+
if (instance !== null) parts.push(instance.body);
|
|
1146
|
+
parts.push(...walk.out, "}");
|
|
1147
|
+
return {
|
|
1148
|
+
code: parts.join("\n"),
|
|
1149
|
+
lang
|
|
1150
|
+
};
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
if (error instanceof Unproven) return { fallback: error.message };
|
|
1153
|
+
return { fallback: "svelte-parse-error" };
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
//#endregion
|
|
1157
|
+
//#region src/core/extract/sfc.ts
|
|
1158
|
+
/**
|
|
1159
|
+
* Framework files for the CLI scan (INTERNALS §2.5.6): `.vue`, `.svelte`,
|
|
1160
|
+
* `.astro` and `.mdx` are compiled with the project's own compilers, so the
|
|
1161
|
+
* scanner sees the call shapes the plugin sees after the framework plugins
|
|
1162
|
+
* (`_unref(t)(…)`, `$.html(…, () => t(…))`, `$$render`…). Compilers are
|
|
1163
|
+
* resolved from the file being compiled and imported on first use: a project
|
|
1164
|
+
* without Svelte never loads it, and vi18n depends on none of them.
|
|
1165
|
+
*/
|
|
1166
|
+
/**
|
|
1167
|
+
* Where each compiler may be found, in order: the package the project
|
|
1168
|
+
* installs directly, else the framework package that depends on it (pnpm
|
|
1169
|
+
* only exposes direct dependencies, and most Vue apps depend on `vue`, which
|
|
1170
|
+
* re-exports its compiler as `vue/compiler-sfc`).
|
|
1171
|
+
*/
|
|
1172
|
+
const COMPILERS = {
|
|
1173
|
+
vue: {
|
|
1174
|
+
name: "Vue",
|
|
1175
|
+
label: "@vue/compiler-sfc (or vue)",
|
|
1176
|
+
candidates: ["@vue/compiler-sfc", "vue/compiler-sfc"]
|
|
1177
|
+
},
|
|
1178
|
+
svelte: {
|
|
1179
|
+
name: "Svelte",
|
|
1180
|
+
label: "svelte",
|
|
1181
|
+
candidates: ["svelte/compiler"]
|
|
1182
|
+
},
|
|
1183
|
+
astro: {
|
|
1184
|
+
name: "Astro",
|
|
1185
|
+
label: "@astrojs/compiler (or astro)",
|
|
1186
|
+
candidates: [
|
|
1187
|
+
"@astrojs/compiler",
|
|
1188
|
+
["astro", "@astrojs/compiler"],
|
|
1189
|
+
"@astrojs/compiler-rs",
|
|
1190
|
+
["astro", "@astrojs/compiler-rs"]
|
|
1191
|
+
]
|
|
1192
|
+
},
|
|
1193
|
+
mdx: {
|
|
1194
|
+
name: "MDX",
|
|
1195
|
+
label: "@mdx-js/mdx",
|
|
1196
|
+
candidates: [
|
|
1197
|
+
"@mdx-js/mdx",
|
|
1198
|
+
["@mdx-js/rollup", "@mdx-js/mdx"],
|
|
1199
|
+
["@astrojs/mdx", "@mdx-js/mdx"]
|
|
1200
|
+
]
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
/** The SFC kind of a file by extension, or null for files the scanner parses directly. */
|
|
1204
|
+
function sfcKind(file) {
|
|
1205
|
+
const dot = file.lastIndexOf(".");
|
|
1206
|
+
switch (dot === -1 ? "" : file.slice(dot + 1)) {
|
|
1207
|
+
case "vue": return "vue";
|
|
1208
|
+
case "svelte": return "svelte";
|
|
1209
|
+
case "astro": return "astro";
|
|
1210
|
+
case "mdx": return "mdx";
|
|
1211
|
+
default: return null;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Resolved compiler path per directory and kind; loaded modules per path.
|
|
1216
|
+
* Only hits are kept: a long-lived process (`--watch`, the dev server's
|
|
1217
|
+
* background scan) must find a compiler installed after it asked.
|
|
1218
|
+
*/
|
|
1219
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1220
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
1221
|
+
function resolveFrom(from, candidate) {
|
|
1222
|
+
try {
|
|
1223
|
+
if (typeof candidate === "string") return createRequire(from).resolve(candidate);
|
|
1224
|
+
const host = createRequire(from).resolve(candidate[0] + "/package.json");
|
|
1225
|
+
return createRequire(host).resolve(candidate[1]);
|
|
1226
|
+
} catch {
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
async function loadCompiler(kind, file) {
|
|
1231
|
+
const key = kind + "\0" + dirname(file);
|
|
1232
|
+
let path = resolved.get(key) ?? null;
|
|
1233
|
+
if (path === null) {
|
|
1234
|
+
for (const candidate of COMPILERS[kind].candidates) {
|
|
1235
|
+
path = resolveFrom(file, candidate);
|
|
1236
|
+
if (path !== null) break;
|
|
1237
|
+
}
|
|
1238
|
+
if (path === null) return null;
|
|
1239
|
+
resolved.set(key, path);
|
|
1240
|
+
}
|
|
1241
|
+
let module = loaded.get(path);
|
|
1242
|
+
if (module === void 0) {
|
|
1243
|
+
module = import(pathToFileURL(path).href);
|
|
1244
|
+
loaded.set(path, module);
|
|
1245
|
+
}
|
|
1246
|
+
return module;
|
|
1247
|
+
}
|
|
1248
|
+
/** A named export of a compiler loaded through `import()`, ESM or CommonJS. */
|
|
1249
|
+
function pick(module, name) {
|
|
1250
|
+
return module[name] ?? module.default?.[name];
|
|
1251
|
+
}
|
|
1252
|
+
function compileError(file, detail) {
|
|
1253
|
+
const message = typeof detail === "string" ? detail : detail instanceof Error || typeof detail === "object" && detail !== null && "message" in detail ? String(detail.message) : String(detail);
|
|
1254
|
+
return diagnosticError({
|
|
1255
|
+
code: "extract-parse-error",
|
|
1256
|
+
severity: "error",
|
|
1257
|
+
message: `Cannot compile ${file}: ${message.split("\n")[0]}`,
|
|
1258
|
+
file
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
function missingCompiler(kind, file) {
|
|
1262
|
+
return diagnosticError({
|
|
1263
|
+
code: "extract-missing-compiler",
|
|
1264
|
+
severity: "error",
|
|
1265
|
+
message: `No ${COMPILERS[kind].name} compiler found to extract .${kind} files; install ${COMPILERS[kind].label} in the project`,
|
|
1266
|
+
file
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
function scriptLang(lang) {
|
|
1270
|
+
return lang === "ts" || lang === "tsx" || lang === "jsx" ? lang : "js";
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* File access for type-based `defineProps<…>()` that imports its types:
|
|
1274
|
+
* compiler-sfc resolves them through this or a registered TypeScript.
|
|
1275
|
+
*/
|
|
1276
|
+
const VUE_FS = {
|
|
1277
|
+
fileExists: (file) => statSync(file, { throwIfNoEntry: false })?.isFile() === true,
|
|
1278
|
+
readFile: (file) => {
|
|
1279
|
+
try {
|
|
1280
|
+
return readFileSync(file, "utf8");
|
|
1281
|
+
} catch {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
const tsRegistered = /* @__PURE__ */ new WeakSet();
|
|
1287
|
+
/**
|
|
1288
|
+
* Lets compiler-sfc resolve types imported from packages, as plugin-vue does,
|
|
1289
|
+
* when the project has a TypeScript with a JS API (TypeScript 7 has none, and
|
|
1290
|
+
* then only relative type imports resolve).
|
|
1291
|
+
*/
|
|
1292
|
+
function registerTypeScript(compiler, file) {
|
|
1293
|
+
if (tsRegistered.has(compiler)) return;
|
|
1294
|
+
tsRegistered.add(compiler);
|
|
1295
|
+
const register = pick(compiler, "registerTS");
|
|
1296
|
+
if (register === void 0) return;
|
|
1297
|
+
try {
|
|
1298
|
+
const ts = createRequire(file)("typescript");
|
|
1299
|
+
if (ts.sys !== void 0) register(() => ts);
|
|
1300
|
+
} catch {}
|
|
1301
|
+
}
|
|
1302
|
+
function compileVue(compiler, file, code) {
|
|
1303
|
+
const parse = pick(compiler, "parse");
|
|
1304
|
+
const compileScript = pick(compiler, "compileScript");
|
|
1305
|
+
const compileTemplate = pick(compiler, "compileTemplate");
|
|
1306
|
+
if (parse === void 0 || compileScript === void 0 || compileTemplate === void 0) throw compileError(file, "the installed Vue compiler does not export parse/compileScript/compileTemplate");
|
|
1307
|
+
const { descriptor, errors } = parse(code, {
|
|
1308
|
+
filename: file,
|
|
1309
|
+
sourceMap: false
|
|
1310
|
+
});
|
|
1311
|
+
if (errors.length > 0) throw compileError(file, errors[0]);
|
|
1312
|
+
registerTypeScript(compiler, file);
|
|
1313
|
+
const id = hash("sha256", file, "hex").slice(0, 8);
|
|
1314
|
+
if (descriptor.scriptSetup !== null) {
|
|
1315
|
+
const script = compileScript(descriptor, {
|
|
1316
|
+
id,
|
|
1317
|
+
inlineTemplate: true,
|
|
1318
|
+
sourceMap: false,
|
|
1319
|
+
fs: VUE_FS
|
|
1320
|
+
});
|
|
1321
|
+
return [{
|
|
1322
|
+
code: script.content,
|
|
1323
|
+
lang: scriptLang(script.lang)
|
|
1324
|
+
}];
|
|
1325
|
+
}
|
|
1326
|
+
let out = "";
|
|
1327
|
+
let lang = "js";
|
|
1328
|
+
let bindings;
|
|
1329
|
+
if (descriptor.script !== null) {
|
|
1330
|
+
const script = compileScript(descriptor, {
|
|
1331
|
+
id,
|
|
1332
|
+
sourceMap: false,
|
|
1333
|
+
fs: VUE_FS
|
|
1334
|
+
});
|
|
1335
|
+
out = script.content;
|
|
1336
|
+
lang = scriptLang(script.lang);
|
|
1337
|
+
bindings = script.bindings;
|
|
1338
|
+
}
|
|
1339
|
+
const template = descriptor.template;
|
|
1340
|
+
if (template !== null && template.src === void 0) {
|
|
1341
|
+
const compiled = compileTemplate({
|
|
1342
|
+
source: template.content,
|
|
1343
|
+
filename: file,
|
|
1344
|
+
id,
|
|
1345
|
+
...template.lang === void 0 ? {} : { preprocessLang: template.lang },
|
|
1346
|
+
compilerOptions: bindings === void 0 ? {} : { bindingMetadata: bindings }
|
|
1347
|
+
});
|
|
1348
|
+
if (compiled.errors.length > 0) throw compileError(file, compiled.errors[0]);
|
|
1349
|
+
out += (out === "" ? "" : "\n") + compiled.code;
|
|
1350
|
+
}
|
|
1351
|
+
return [{
|
|
1352
|
+
code: out,
|
|
1353
|
+
lang
|
|
1354
|
+
}];
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Style blocks in another language (`lang="scss"`) need preprocessors the
|
|
1358
|
+
* scan does not run, and Svelte's compiler would reject them. Their content
|
|
1359
|
+
* cannot hold calls, so it is blanked; line breaks stay for readable errors.
|
|
1360
|
+
*/
|
|
1361
|
+
function blankForeignStyles(code) {
|
|
1362
|
+
return code.replace(/(<style\b[^>]*\b(?:lang|type)\s*=\s*(?!["']?(?:text\/)?css["'\s>])[^>]*>)([\s\S]*?)(<\/style\s*>)/gi, (_all, open, body, close) => open + body.replace(/[^\n]/g, " ") + close);
|
|
1363
|
+
}
|
|
1364
|
+
function compileSvelte(compiler, file, code) {
|
|
1365
|
+
const compile = pick(compiler, "compile");
|
|
1366
|
+
if (compile === void 0) throw compileError(file, "the installed svelte/compiler does not export compile");
|
|
1367
|
+
const version = pick(compiler, "VERSION") ?? "5";
|
|
1368
|
+
const generate = Number.parseInt(version, 10) < 5 ? "dom" : "client";
|
|
1369
|
+
try {
|
|
1370
|
+
return [{
|
|
1371
|
+
code: compile(blankForeignStyles(code), {
|
|
1372
|
+
filename: file,
|
|
1373
|
+
generate
|
|
1374
|
+
}).js.code,
|
|
1375
|
+
lang: "js"
|
|
1376
|
+
}];
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
throw compileError(file, error);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
async function compileAstro(compiler, file, code) {
|
|
1382
|
+
const transform = pick(compiler, "transform");
|
|
1383
|
+
if (transform === void 0) throw compileError(file, "the installed Astro compiler does not export transform");
|
|
1384
|
+
let result;
|
|
1385
|
+
try {
|
|
1386
|
+
result = await transform(code, {
|
|
1387
|
+
filename: file,
|
|
1388
|
+
normalizedFilename: file,
|
|
1389
|
+
internalURL: "astro/runtime/server/index.js"
|
|
1390
|
+
});
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
throw compileError(file, error);
|
|
1393
|
+
}
|
|
1394
|
+
const failure = result.diagnostics?.find((d) => d.severity === 1);
|
|
1395
|
+
if (failure !== void 0) throw compileError(file, failure.text ?? failure.message ?? "syntax error");
|
|
1396
|
+
const modules = [{
|
|
1397
|
+
code: result.code,
|
|
1398
|
+
lang: "ts"
|
|
1399
|
+
}];
|
|
1400
|
+
for (const script of result.scripts ?? []) if (script.type === "inline" && typeof script.code === "string") modules.push({
|
|
1401
|
+
code: script.code,
|
|
1402
|
+
lang: "ts"
|
|
1403
|
+
});
|
|
1404
|
+
return modules;
|
|
1405
|
+
}
|
|
1406
|
+
async function compileMdx(compiler, file, code) {
|
|
1407
|
+
const compile = pick(compiler, "compile");
|
|
1408
|
+
if (compile === void 0) throw compileError(file, "the installed @mdx-js/mdx does not export compile");
|
|
1409
|
+
try {
|
|
1410
|
+
const output = await compile({
|
|
1411
|
+
value: code,
|
|
1412
|
+
path: file
|
|
1413
|
+
}, { development: false });
|
|
1414
|
+
return [{
|
|
1415
|
+
code: String(output),
|
|
1416
|
+
lang: "js"
|
|
1417
|
+
}];
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
throw compileError(file, error);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Every module `file` compiles to, component first. Throws a
|
|
1424
|
+
* `DiagnosticError`: `extract-missing-compiler` when the project has no
|
|
1425
|
+
* compiler for the file, `extract-parse-error` when compilation fails.
|
|
1426
|
+
*/
|
|
1427
|
+
async function compileModules(file, code) {
|
|
1428
|
+
const kind = sfcKind(file);
|
|
1429
|
+
if (kind === null) throw compileError(file, "not a .vue, .svelte, .astro or .mdx file");
|
|
1430
|
+
const compiler = await loadCompiler(kind, file).catch((error) => {
|
|
1431
|
+
throw compileError(file, error);
|
|
1432
|
+
});
|
|
1433
|
+
if (compiler === null) throw missingCompiler(kind, file);
|
|
1434
|
+
switch (kind) {
|
|
1435
|
+
case "vue": try {
|
|
1436
|
+
return compileVue(compiler, file, code);
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
throw isDiagnosticError(error) ? error : compileError(file, error);
|
|
1439
|
+
}
|
|
1440
|
+
case "svelte": return compileSvelte(compiler, file, code);
|
|
1441
|
+
case "astro": return compileAstro(compiler, file, code);
|
|
1442
|
+
case "mdx": return compileMdx(compiler, file, code);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Files extracted by parse-only extraction and files compiled, by fallback
|
|
1447
|
+
* reason, since the process started (`compiled` counts every reason). For
|
|
1448
|
+
* tests and benchmarks; nothing reads them to decide anything.
|
|
1449
|
+
*/
|
|
1450
|
+
const sfcStats = {
|
|
1451
|
+
parsed: 0,
|
|
1452
|
+
compiled: 0,
|
|
1453
|
+
fallbacks: /* @__PURE__ */ new Map()
|
|
1454
|
+
};
|
|
1455
|
+
/** Counts a compiled `.vue`/`.svelte` file under the reason parse-only extraction gave up. */
|
|
1456
|
+
function countFallback(reason) {
|
|
1457
|
+
sfcStats.compiled++;
|
|
1458
|
+
sfcStats.fallbacks.set(reason, (sfcStats.fallbacks.get(reason) ?? 0) + 1);
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* The module parse-only extraction scans for a `.vue` or `.svelte` file
|
|
1462
|
+
* (§2.5.6, §6 D232), or the reason it cannot prove the result equal to
|
|
1463
|
+
* compiling the file: `.astro` and `.mdx` files, a missing compiler or a
|
|
1464
|
+
* file the parser rejects included, which {@link compileModules} then
|
|
1465
|
+
* reports.
|
|
1466
|
+
*/
|
|
1467
|
+
async function parseOnlyModule(file, code) {
|
|
1468
|
+
const kind = sfcKind(file);
|
|
1469
|
+
if (kind !== "vue" && kind !== "svelte") return { fallback: kind ?? "not-sfc" };
|
|
1470
|
+
let compiler;
|
|
1471
|
+
try {
|
|
1472
|
+
compiler = await loadCompiler(kind, file);
|
|
1473
|
+
} catch {
|
|
1474
|
+
return { fallback: "compiler" };
|
|
1475
|
+
}
|
|
1476
|
+
if (compiler === null) return { fallback: "compiler" };
|
|
1477
|
+
return kind === "vue" ? vueParseOnly(compiler, file, code) : svelteParseOnly(compiler, file, blankForeignStyles(code));
|
|
1478
|
+
}
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region src/core/extract/worker.ts
|
|
1481
|
+
/**
|
|
1482
|
+
* One file's scan, and the `worker_threads` entry of the scan pool
|
|
1483
|
+
* (INTERNALS §2.5.6). `scanProject` runs `scanSource` in-process for small
|
|
1484
|
+
* scans and in this module's workers when many files miss the cache; both
|
|
1485
|
+
* paths run the same function, so the pool cannot change a result.
|
|
1486
|
+
*
|
|
1487
|
+
* Kept free of the cache, the config loader and `VERSION` (a JSON import),
|
|
1488
|
+
* so a worker loads only what scanning needs.
|
|
1489
|
+
*/
|
|
1490
|
+
/** The scan's part of `config`, for a worker. */
|
|
1491
|
+
function scanConfig(config) {
|
|
1492
|
+
const { root, sourceLocale, project, extraction, strict } = config;
|
|
1493
|
+
return {
|
|
1494
|
+
root,
|
|
1495
|
+
sourceLocale,
|
|
1496
|
+
project,
|
|
1497
|
+
extraction,
|
|
1498
|
+
strict
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
/** Diagnostics that make `ProjectScan.aborted` true. */
|
|
1502
|
+
const ABORT_CODES = /* @__PURE__ */ new Set([
|
|
1503
|
+
"extract-parse-error",
|
|
1504
|
+
"extract-missing-compiler",
|
|
1505
|
+
"extract-empty-root"
|
|
1506
|
+
]);
|
|
1507
|
+
/**
|
|
1508
|
+
* Assigns original-file positions to occurrences found in compiled output
|
|
1509
|
+
* (§2.5.1): each literal is searched after the previous occurrence of the
|
|
1510
|
+
* same literal, so repeated strings get successive positions. When the
|
|
1511
|
+
* compiler emitted a literal more often than the file spells it, the extra
|
|
1512
|
+
* occurrence keeps the last position found.
|
|
1513
|
+
*/
|
|
1514
|
+
function locate(text, occurrences) {
|
|
1515
|
+
const last = /* @__PURE__ */ new Map();
|
|
1516
|
+
for (const occurrence of occurrences) {
|
|
1517
|
+
const previous = last.get(occurrence.source);
|
|
1518
|
+
const found = locateLiteral(text, occurrence.source, previous === void 0 ? 0 : previous.offset + 1);
|
|
1519
|
+
const at = found ?? previous;
|
|
1520
|
+
if (at === void 0) continue;
|
|
1521
|
+
occurrence.line = at.line;
|
|
1522
|
+
occurrence.column = at.column;
|
|
1523
|
+
if (found !== null) last.set(occurrence.source, found);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* The modules of a `.vue`/`.svelte`/`.astro`/`.mdx` file: the parse-only
|
|
1528
|
+
* module when it can stand for compilation (§6 D232), otherwise what the
|
|
1529
|
+
* project's compiler produces. A parse-only module the parser rejects is
|
|
1530
|
+
* compiled too, so a syntax error is reported as compiling reports it.
|
|
1531
|
+
*/
|
|
1532
|
+
async function sfcModules(task, compile) {
|
|
1533
|
+
if (!compile) {
|
|
1534
|
+
const quick = await parseOnlyModule(task.file, task.text);
|
|
1535
|
+
if ("code" in quick) try {
|
|
1536
|
+
const parsed = parseModule(quick.code, quick.lang, task.refFile);
|
|
1537
|
+
sfcStats.parsed++;
|
|
1538
|
+
return [{
|
|
1539
|
+
code: quick.code,
|
|
1540
|
+
lang: quick.lang,
|
|
1541
|
+
parsed
|
|
1542
|
+
}];
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
if (!isDiagnosticError(error)) throw error;
|
|
1545
|
+
countFallback("syntax");
|
|
1546
|
+
}
|
|
1547
|
+
else countFallback(quick.fallback);
|
|
1548
|
+
}
|
|
1549
|
+
return compileModules(task.file, task.text);
|
|
1550
|
+
}
|
|
1551
|
+
function mentionsVi18n(text, needles) {
|
|
1552
|
+
for (const needle of needles) if (text.includes(needle)) return true;
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Scans one file: framework files are read parse-only or compiled first
|
|
1557
|
+
* (each resulting module scanned on its own, positions recovered by
|
|
1558
|
+
* search), scripts are parsed as they are. A file whose text names no
|
|
1559
|
+
* import source, wrapper or global cannot bind a vi18n call and is not
|
|
1560
|
+
* parsed at all.
|
|
1561
|
+
*/
|
|
1562
|
+
async function scanSource(task, config, codec, options = {}) {
|
|
1563
|
+
const result = {
|
|
1564
|
+
occurrences: [],
|
|
1565
|
+
diagnostics: [],
|
|
1566
|
+
aborted: false
|
|
1567
|
+
};
|
|
1568
|
+
if (!mentionsVi18n(task.text, sourceNeedles(config))) return result;
|
|
1569
|
+
const sfc = sfcKind(task.file) !== null;
|
|
1570
|
+
try {
|
|
1571
|
+
const modules = sfc ? await sfcModules(task, options.compile === true) : [{
|
|
1572
|
+
code: task.text,
|
|
1573
|
+
lang: langFromId(task.file)
|
|
1574
|
+
}];
|
|
1575
|
+
for (const module of modules) {
|
|
1576
|
+
let parsed = module.parsed ?? parseModule(module.code, module.lang, task.refFile);
|
|
1577
|
+
let specifiers = sourceSpecifiers(listImports(parsed), task.file, config);
|
|
1578
|
+
const names = module.lang.endsWith("x") ? macroNames(listImports(parsed), specifiers) : null;
|
|
1579
|
+
if (names !== null && names.size > 0) {
|
|
1580
|
+
const sink = new MagicString(module.code);
|
|
1581
|
+
const expanded = expandMacros(parsed, sink, {
|
|
1582
|
+
names,
|
|
1583
|
+
refFile: task.refFile,
|
|
1584
|
+
keepLines: true
|
|
1585
|
+
});
|
|
1586
|
+
for (const diagnostic of expanded.diagnostics) result.diagnostics.push(diagnostic);
|
|
1587
|
+
if (expanded.edited) {
|
|
1588
|
+
parsed = parseModule(sink.toString(), module.lang, task.refFile);
|
|
1589
|
+
specifiers = sourceSpecifiers(listImports(parsed), task.file, config);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
const scan = scanModule(parsed, {
|
|
1593
|
+
codec,
|
|
1594
|
+
refFile: task.refFile,
|
|
1595
|
+
sourceSpecifiers: specifiers,
|
|
1596
|
+
aliases: config.extraction.aliases,
|
|
1597
|
+
globals: config.extraction.globals,
|
|
1598
|
+
moduleScope: config.strict.moduleScope,
|
|
1599
|
+
warnings: true,
|
|
1600
|
+
exactPositions: !sfc,
|
|
1601
|
+
sourceLocale: config.sourceLocale,
|
|
1602
|
+
...config.extraction.dependencies.length === 0 ? {} : { libraryIds: libraryIds(config) }
|
|
1603
|
+
});
|
|
1604
|
+
for (const occurrence of scan.occurrences) result.occurrences.push(occurrence);
|
|
1605
|
+
for (const diagnostic of scan.diagnostics) result.diagnostics.push(diagnostic);
|
|
1606
|
+
}
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
if (!isDiagnosticError(error)) throw error;
|
|
1609
|
+
const diagnostic = {
|
|
1610
|
+
...error.diagnostic,
|
|
1611
|
+
file: task.refFile
|
|
1612
|
+
};
|
|
1613
|
+
if (sfc) {
|
|
1614
|
+
delete diagnostic.line;
|
|
1615
|
+
delete diagnostic.column;
|
|
1616
|
+
diagnostic.message = diagnostic.message.replaceAll(task.file, task.refFile);
|
|
1617
|
+
}
|
|
1618
|
+
result.occurrences.length = 0;
|
|
1619
|
+
result.diagnostics.push(diagnostic);
|
|
1620
|
+
}
|
|
1621
|
+
if (sfc) locate(task.text, result.occurrences);
|
|
1622
|
+
for (const diagnostic of result.diagnostics) if (ABORT_CODES.has(diagnostic.code)) result.aborted = true;
|
|
1623
|
+
return result;
|
|
1624
|
+
}
|
|
1625
|
+
/** Marks this module's own workers, so importing it in any other thread starts nothing. */
|
|
1626
|
+
const WORKER_MARKER = "vi18n:scan-worker";
|
|
1627
|
+
const init = workerData;
|
|
1628
|
+
if (!isMainThread && parentPort !== null && init?.marker === "vi18n:scan-worker") {
|
|
1629
|
+
const port = parentPort;
|
|
1630
|
+
const config = init.config;
|
|
1631
|
+
const codec = createCodec(init.idLength);
|
|
1632
|
+
armRawParsing();
|
|
1633
|
+
port.on("message", (request) => {
|
|
1634
|
+
(async () => {
|
|
1635
|
+
try {
|
|
1636
|
+
const results = [];
|
|
1637
|
+
for (const task of request.tasks) results.push(await scanSource(task, config, codec));
|
|
1638
|
+
port.postMessage({
|
|
1639
|
+
batch: request.batch,
|
|
1640
|
+
results
|
|
1641
|
+
});
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
const { message, stack } = error instanceof Error ? error : new Error(String(error));
|
|
1644
|
+
port.postMessage({
|
|
1645
|
+
batch: request.batch,
|
|
1646
|
+
error: stack === void 0 ? { message } : {
|
|
1647
|
+
message,
|
|
1648
|
+
stack
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
})();
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
//#endregion
|
|
1656
|
+
export { expandMacros as a, armRawParsing as c, scanSource as i, langFromId as l, WORKER_MARKER as n, macroNames as o, scanConfig as r, decodeEntities as s, ABORT_CODES as t, parseModule as u };
|