@menapo/vi18n 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +110 -2
  3. package/dist/apply-Bre-lY9J.js +228 -0
  4. package/dist/captured-Bj8bO6vd.js +1034 -0
  5. package/dist/check-BIKEuu6B.js +201 -0
  6. package/dist/check-HsmmpJZg.js +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +41 -0
  9. package/dist/codec-DuvU2VQy.js +207 -0
  10. package/dist/collect-Cx4_SKOe.js +13 -0
  11. package/dist/collisions-CfpZU8e4.js +217 -0
  12. package/dist/compile-BaDh0cHG.js +245 -0
  13. package/dist/compile-Cc97JaW2.js +2502 -0
  14. package/dist/concurrency-C9UUWuDC.js +96 -0
  15. package/dist/concurrency-DONL7t4V.js +1 -0
  16. package/dist/config.d.ts +21 -0
  17. package/dist/config.js +39 -0
  18. package/dist/dir-zw6bGMKQ.js +290 -0
  19. package/dist/duplicates-yuRljQ6h.js +185 -0
  20. package/dist/engine-C2ENBRJe.js +531 -0
  21. package/dist/engine.d.ts +43 -0
  22. package/dist/engine.js +1241 -0
  23. package/dist/exclude-Du-Zd_qI.js +26 -0
  24. package/dist/export-C9Sy6UWv.js +103 -0
  25. package/dist/extract-C3S8btx0.js +298 -0
  26. package/dist/formats-9unqj_fZ.js +781 -0
  27. package/dist/i18next-9HAoxwUI.js +1 -0
  28. package/dist/i18next-BEJgv_S8.js +1596 -0
  29. package/dist/import-nANvQutw.js +225 -0
  30. package/dist/index.d.ts +156 -0
  31. package/dist/index.js +239 -0
  32. package/dist/init-C2ScuW6a.js +792 -0
  33. package/dist/library-D5CWvTQs.js +1882 -0
  34. package/dist/lingui-zUiCl9Um.js +833 -0
  35. package/dist/lint.d.ts +105 -0
  36. package/dist/lint.js +971 -0
  37. package/dist/load-CQRz4PpM.js +2950 -0
  38. package/dist/load-gW-oJt1Q.js +747 -0
  39. package/dist/locales-DX4MOBFE.js +827 -0
  40. package/dist/lsp-RMMMc88P.js +31 -0
  41. package/dist/match-BGr8EXB3.js +89 -0
  42. package/dist/mcp-CHVwBoRP.js +28 -0
  43. package/dist/merge-driver-BTX87aVm.js +422 -0
  44. package/dist/messages-BfjIExyB.js +166 -0
  45. package/dist/migrate-IpLDMXmq.js +1484 -0
  46. package/dist/migrate-rCQuOGsf.js +1 -0
  47. package/dist/openai-CTaMDcl-.js +346 -0
  48. package/dist/output-DWE3SIUA.js +164 -0
  49. package/dist/parse.d.ts +62 -0
  50. package/dist/parse.js +156 -0
  51. package/dist/pipeline-C9Dbotkh.js +2031 -0
  52. package/dist/pipeline-DbX6rlo5.js +1 -0
  53. package/dist/project-Bw9UPIdx.js +1046 -0
  54. package/dist/project-C4GpaN-n.js +381 -0
  55. package/dist/pseudo-DRv30iN5.js +145 -0
  56. package/dist/raw-C4Y6vyGY.js +4708 -0
  57. package/dist/react-intl-BP0u-D5m.js +519 -0
  58. package/dist/restore-CiYv-jxS.js +175 -0
  59. package/dist/rewrite-EsIw_lsq.js +803 -0
  60. package/dist/rich-DCUKl5HM.js +97 -0
  61. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  62. package/dist/run-BEcnBbu-.js +694 -0
  63. package/dist/runtime-DTMmliT1.js +64 -0
  64. package/dist/scan-worker.d.ts +62 -0
  65. package/dist/scan-worker.js +1 -0
  66. package/dist/scoped-BMYpSthj.js +44 -0
  67. package/dist/server-Da1Ot9IN.js +10485 -0
  68. package/dist/server-deBOq2vq.js +11823 -0
  69. package/dist/server.d.ts +169 -0
  70. package/dist/server.js +1234 -0
  71. package/dist/sitemap-DJXzKoza.js +159 -0
  72. package/dist/sources-D19czDSA.js +2242 -0
  73. package/dist/status-zr4jY59Y.js +149 -0
  74. package/dist/stdio-CVaJfxA7.js +29 -0
  75. package/dist/stdio-FPOKQAgu.js +95 -0
  76. package/dist/sync-B9V2KFwb.js +658 -0
  77. package/dist/t-DC-c9zE5.js +454 -0
  78. package/dist/test.d.ts +116 -0
  79. package/dist/test.js +319 -0
  80. package/dist/translate-CQnJfLjO.js +299 -0
  81. package/dist/ts-BaU5yOf0.js +4863 -0
  82. package/dist/types-CgUCkFU1.d.ts +707 -0
  83. package/dist/types-D3QGM8XO.js +146 -0
  84. package/dist/types-DsdWBG0b.d.ts +558 -0
  85. package/dist/types-DtvQ7UdD.js +1948 -0
  86. package/dist/types-wSh9fCBY.js +11 -0
  87. package/dist/units-CQ5Q99RC.js +150 -0
  88. package/dist/url-CRfCOhUI.js +172 -0
  89. package/dist/url-CsBkRaJy.js +225 -0
  90. package/dist/validate-BxpupdAf.js +2615 -0
  91. package/dist/validate-D6DJepZv.js +365 -0
  92. package/dist/version-Byqi2guX.js +28 -0
  93. package/dist/vite-scan-CT_vsp4g.js +242 -0
  94. package/dist/vite.d.ts +34 -0
  95. package/dist/vite.js +7558 -0
  96. package/dist/vue-i18n-g0iGy6CZ.js +651 -0
  97. package/dist/which-BOemZ5Q8.js +116 -0
  98. package/dist/worker-84U6uYB3.js +1656 -0
  99. package/package.json +97 -3
  100. package/schemas/catalog.json +57 -0
@@ -0,0 +1,2242 @@
1
+ import { a as diagnosticError, o as isDiagnosticError, r as DEFAULT_IMPORT_SOURCES } from "./types-D3QGM8XO.js";
2
+ import { n as validateSource, o as isValidManualId } from "./validate-BxpupdAf.js";
3
+ import { createRequire } from "node:module";
4
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { readFileSync, realpathSync, statSync } from "node:fs";
6
+ //#region src/core/extract/imports.ts
7
+ const memo = /* @__PURE__ */ new WeakMap();
8
+ function exportedName(node) {
9
+ return node.type === "Literal" ? String(node.value) : node.name;
10
+ }
11
+ /**
12
+ * Every value import of the module in source order, with its bindings.
13
+ * Type-only declarations and specifiers (`import type`, `import { type T }`)
14
+ * are left out: they bind nothing at runtime. A side-effect import has no
15
+ * bindings. The returned array is shared between calls and must not be mutated.
16
+ */
17
+ function listImports(parsed) {
18
+ const known = memo.get(parsed);
19
+ if (known !== void 0) return known;
20
+ const records = [];
21
+ for (const statement of parsed.program.body) {
22
+ if (statement.type !== "ImportDeclaration" || statement.importKind === "type") continue;
23
+ const bindings = [];
24
+ for (const specifier of statement.specifiers) {
25
+ if (specifier.importKind === "type") continue;
26
+ const local = specifier.local.name;
27
+ switch (specifier.type) {
28
+ case "ImportDefaultSpecifier":
29
+ bindings.push({
30
+ local,
31
+ imported: "default"
32
+ });
33
+ break;
34
+ case "ImportNamespaceSpecifier":
35
+ bindings.push({
36
+ local,
37
+ imported: "*"
38
+ });
39
+ break;
40
+ default: bindings.push({
41
+ local,
42
+ imported: exportedName(specifier.imported)
43
+ });
44
+ }
45
+ }
46
+ if (bindings.length === 0 && statement.specifiers.length > 0) continue;
47
+ records.push({
48
+ specifier: String(statement.source.value),
49
+ start: statement.start,
50
+ end: statement.end,
51
+ bindings
52
+ });
53
+ }
54
+ memo.set(parsed, records);
55
+ return records;
56
+ }
57
+ //#endregion
58
+ //#region src/core/extract/locate.ts
59
+ /**
60
+ * Positions in original files (INTERNALS §2.5.1). Post-compile offsets of
61
+ * `.vue`/`.svelte`/`.astro`/`.mdx` output mean nothing in the file the user
62
+ * edits, and composing source maps per occurrence costs too much, so a
63
+ * literal's position is recovered by searching the original text (§6 D14).
64
+ */
65
+ let cachedText = null;
66
+ let cachedStarts = [];
67
+ function lineStarts(text) {
68
+ if (text === cachedText) return cachedStarts;
69
+ const starts = [0];
70
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) starts.push(i + 1);
71
+ cachedText = text;
72
+ cachedStarts = starts;
73
+ return starts;
74
+ }
75
+ /**
76
+ * 1-based line and column of a UTF-16 offset. Lines end at `\n` (a `\r`
77
+ * before it belongs to the line it ends); columns count UTF-16 code units, as
78
+ * the parser's offsets do.
79
+ */
80
+ function lineColumn(text, offset) {
81
+ const starts = lineStarts(text);
82
+ let low = 0;
83
+ let high = starts.length - 1;
84
+ while (low < high) {
85
+ const mid = low + high + 1 >>> 1;
86
+ if (starts[mid] <= offset) low = mid;
87
+ else high = mid - 1;
88
+ }
89
+ return {
90
+ line: low + 1,
91
+ column: offset - starts[low] + 1
92
+ };
93
+ }
94
+ /**
95
+ * The body of a JS string literal for `value` between `quote`s, escaped as
96
+ * people and compilers write it. Template literals hold newlines and tabs raw
97
+ * unless `escapeWhitespace`, the way `\`a\nb\`` is typed.
98
+ */
99
+ function quoted(value, quote, escapeWhitespace = quote === "'") {
100
+ let out = "";
101
+ for (let i = 0; i < value.length; i++) {
102
+ const ch = value[i];
103
+ switch (ch) {
104
+ case "\\":
105
+ out += "\\\\";
106
+ break;
107
+ case quote:
108
+ out += "\\" + quote;
109
+ break;
110
+ case "$":
111
+ out += quote === "`" && value[i + 1] === "{" ? "\\$" : "$";
112
+ break;
113
+ case "\n":
114
+ out += escapeWhitespace ? "\\n" : "\n";
115
+ break;
116
+ case "\r":
117
+ out += "\\r";
118
+ break;
119
+ case " ":
120
+ out += escapeWhitespace ? "\\t" : " ";
121
+ break;
122
+ default: out += ch;
123
+ }
124
+ }
125
+ return quote + out + quote;
126
+ }
127
+ const QUOTES = /* @__PURE__ */ new Set([
128
+ "\"",
129
+ "'",
130
+ "`"
131
+ ]);
132
+ function earliest(text, needles, from) {
133
+ let best = -1;
134
+ for (const needle of needles) {
135
+ const at = text.indexOf(needle, from);
136
+ if (at !== -1 && (best === -1 || at < best)) best = at;
137
+ }
138
+ return best;
139
+ }
140
+ /**
141
+ * Finds `literal` (a JS string value) in an original file at or after offset
142
+ * `from`. Quoted spellings — JSON-escaped in double quotes, single quotes or
143
+ * backticks — win over a bare match, which may sit in a comment or inside a
144
+ * longer word; the bare value (raw, or JSON-escaped without quotes) is the
145
+ * fallback for files that never quote it. The position is the opening
146
+ * quote's, like the parser's literal offsets. Pass `previous.offset + 1` to
147
+ * find the next occurrence of the same literal.
148
+ */
149
+ function locateLiteral(fileText, literal, from = 0) {
150
+ const json = JSON.stringify(literal);
151
+ const start = from < 0 ? 0 : from;
152
+ const template = quoted(literal, "`");
153
+ const needles = [
154
+ json,
155
+ quoted(literal, "'"),
156
+ template
157
+ ];
158
+ if (/[\n\t]/.test(literal)) needles.push(quoted(literal, "`", true));
159
+ if (literal.includes("\n") && fileText.includes("\r\n")) needles.push(template.replaceAll("\n", "\r\n"));
160
+ if (/[\u2028\u2029]/.test(literal)) for (const needle of needles.slice()) needles.push(needle.replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029"));
161
+ if (/[\u0080-\uffff]/.test(literal)) for (const needle of needles.slice()) for (const upper of [false, true]) needles.push(needle.replace(/[\u0080-\uffff]/g, (ch) => {
162
+ const hex = ch.charCodeAt(0).toString(16).padStart(4, "0");
163
+ return "\\u" + (upper ? hex.toUpperCase() : hex);
164
+ }));
165
+ let offset = earliest(fileText, needles, start);
166
+ if (offset === -1 && literal !== "" && (start === 0 || earliest(fileText, needles, 0) === -1)) {
167
+ offset = earliest(fileText, [literal, json.slice(1, -1)], start);
168
+ if (offset > 0 && QUOTES.has(fileText[offset - 1])) offset--;
169
+ }
170
+ if (offset === -1) return null;
171
+ return {
172
+ ...lineColumn(fileText, offset),
173
+ offset
174
+ };
175
+ }
176
+ //#endregion
177
+ //#region src/core/extract/sinks.ts
178
+ const WRAPPERS = /* @__PURE__ */ new Set([
179
+ "TSAsExpression",
180
+ "TSSatisfiesExpression",
181
+ "TSNonNullExpression",
182
+ "TSTypeAssertion",
183
+ "TSInstantiationExpression",
184
+ "ParenthesizedExpression"
185
+ ]);
186
+ /** Strips parentheses and TypeScript-only wrappers, which change nothing at runtime. */
187
+ function unwrap(node) {
188
+ let current = node;
189
+ while (WRAPPERS.has(current.type)) current = current.expression;
190
+ return current;
191
+ }
192
+ /** The name of a non-computed member/property key, or of a computed one written as a string literal. */
193
+ function keyName(key, computed) {
194
+ if (!computed && (key.type === "Identifier" || key.type === "JSXIdentifier")) return key.name;
195
+ if (key.type === "Literal" && typeof key.value === "string") return key.value;
196
+ return null;
197
+ }
198
+ function propertyValue(objectNode, name) {
199
+ for (const property of objectNode.properties) if (property.type === "Property" && keyName(property.key, property.computed === true) === name) return unwrap(property.value);
200
+ return null;
201
+ }
202
+ /**
203
+ * Calls `mark` with every expression that `node` inserts as HTML: Svelte
204
+ * `$.html(…, () => X)`, Vue `innerHTML: X`, React
205
+ * `dangerouslySetInnerHTML: { __html: X }` (compiled or as a JSX attribute),
206
+ * Solid's `innerHTML={X}`, Astro `$$unescapeHTML(X)` and `x.innerHTML = X`.
207
+ * Only direct flows count: a value built from `X` is not `X`.
208
+ */
209
+ function markHtmlSinks(node, mark) {
210
+ switch (node.type) {
211
+ case "Property": {
212
+ if (node.kind !== "init" || node.method === true) return;
213
+ const name = keyName(node.key, node.computed === true);
214
+ if (name === "innerHTML") mark(unwrap(node.value));
215
+ else if (name === "dangerouslySetInnerHTML") {
216
+ const value = unwrap(node.value);
217
+ if (value.type === "ObjectExpression") {
218
+ const html = propertyValue(value, "__html");
219
+ if (html !== null) mark(html);
220
+ }
221
+ }
222
+ return;
223
+ }
224
+ case "JSXAttribute": {
225
+ const value = node.value;
226
+ if (value?.type !== "JSXExpressionContainer") return;
227
+ const name = keyName(node.name, false);
228
+ const expression = unwrap(value.expression);
229
+ if (name === "innerHTML") mark(expression);
230
+ else if (name === "dangerouslySetInnerHTML" && expression.type === "ObjectExpression") {
231
+ const html = propertyValue(expression, "__html");
232
+ if (html !== null) mark(html);
233
+ }
234
+ return;
235
+ }
236
+ case "AssignmentExpression": {
237
+ const left = node.left;
238
+ if ((node.operator === "=" || node.operator === "+=") && left.type === "MemberExpression" && keyName(left.property, left.computed === true) === "innerHTML") mark(unwrap(node.right));
239
+ return;
240
+ }
241
+ case "CallExpression": {
242
+ const callee = unwrap(node.callee);
243
+ const args = node.arguments;
244
+ if (callee.type === "Identifier" && callee.name === "$$unescapeHTML") {
245
+ if (args[0] !== void 0) mark(unwrap(args[0]));
246
+ return;
247
+ }
248
+ if (callee.type === "MemberExpression" && callee.computed !== true && callee.object.type === "Identifier" && callee.object.name === "$" && callee.property.name === "html") {
249
+ for (const arg of args) if (arg.type === "ArrowFunctionExpression" && arg.expression === true) mark(unwrap(arg.body));
250
+ }
251
+ return;
252
+ }
253
+ }
254
+ }
255
+ /** `extract-module-scope`; `severity` follows `strict.moduleScope`. */
256
+ function moduleScopeDiagnostic(callee, severity, where) {
257
+ return {
258
+ code: "extract-module-scope",
259
+ severity,
260
+ message: `${callee}() runs at module scope, so its text freezes at import and has no request on the server; call it inside a function or component, or declare the message with msg()`,
261
+ ...where
262
+ };
263
+ }
264
+ /** `extract-html-sink`: `t()` does not escape values, `richHtml()` does. */
265
+ function htmlSinkDiagnostic(callee, id, where) {
266
+ return {
267
+ code: "extract-html-sink",
268
+ severity: "warning",
269
+ message: `${callee}() returns plain text with unescaped values but is inserted as HTML here; use richHtml(), which escapes values`,
270
+ id,
271
+ ...where
272
+ };
273
+ }
274
+ /** `extract-wrapper-unused`: usually a wrapper export missing from `extraction.aliases`. */
275
+ function wrapperUnusedDiagnostic(specifier, where) {
276
+ return {
277
+ code: "extract-wrapper-unused",
278
+ severity: "warning",
279
+ message: `This module imports from the wrapper ${JSON.stringify(specifier)} but makes no vi18n call; add the wrapper's translation functions to extraction.aliases`,
280
+ ...where
281
+ };
282
+ }
283
+ //#endregion
284
+ //#region src/core/extract/comments.ts
285
+ const NONE = {
286
+ ignored: [],
287
+ contexts: []
288
+ };
289
+ /** Keys that are not children: positions, and the back links ESLint and oxlint add. */
290
+ const NOT_CHILDREN = /* @__PURE__ */ new Set([
291
+ "type",
292
+ "start",
293
+ "end",
294
+ "range",
295
+ "loc",
296
+ "parent"
297
+ ]);
298
+ const IGNORE = /^vi18n-ignore(?:\s|$)/;
299
+ const CONTEXT = "vi18n-context:";
300
+ function span(node) {
301
+ return node.range === void 0 ? {
302
+ start: node.start ?? 0,
303
+ end: node.end ?? 0
304
+ } : {
305
+ start: node.range[0],
306
+ end: node.range[1]
307
+ };
308
+ }
309
+ /** Whether `code` can hold a marker: the check before any comment is read. */
310
+ function hasMarkers(code) {
311
+ return code.includes("vi18n-");
312
+ }
313
+ /** The marker a comment holds, or null. Block comments may use JSDoc's leading `*`. */
314
+ function markerOf(comment) {
315
+ let text = comment.value;
316
+ if (comment.type === "Block") text = text.split("\n").map((line) => line.replace(/^\s*\*?/, "")).join("\n");
317
+ text = text.trimStart();
318
+ if (IGNORE.test(text)) return { note: null };
319
+ if (!text.startsWith(CONTEXT)) return null;
320
+ const note = text.slice(14).split("\n").map((line) => line.trim()).filter((line) => line !== "").join("\n");
321
+ return note === "" ? null : { note };
322
+ }
323
+ /** The child of `node` whose range holds `offset`, or null. */
324
+ function childAt(node, offset) {
325
+ for (const key in node) {
326
+ if (NOT_CHILDREN.has(key)) continue;
327
+ const value = node[key];
328
+ if (value === null || typeof value !== "object") continue;
329
+ for (const item of Array.isArray(value) ? value : [value]) {
330
+ if (item === null || typeof item !== "object" || typeof item.type !== "string") continue;
331
+ const { start, end } = span(item);
332
+ if (start <= offset && offset < end) return item;
333
+ }
334
+ }
335
+ return null;
336
+ }
337
+ /**
338
+ * The outermost node below `root` that starts at `offset` (null when the
339
+ * offset is punctuation, such as a closing brace), and the innermost node
340
+ * that holds it.
341
+ */
342
+ function nodeAt(root, offset) {
343
+ let holder = root;
344
+ for (;;) {
345
+ const child = childAt(holder, offset);
346
+ if (child === null) return {
347
+ at: null,
348
+ holder
349
+ };
350
+ if (span(child).start === offset) return {
351
+ at: child,
352
+ holder: child
353
+ };
354
+ holder = child;
355
+ }
356
+ }
357
+ /** The first offset at or after `from` that is not whitespace or inside a comment. */
358
+ function nextToken(code, from, comments) {
359
+ let at = from;
360
+ let next = 0;
361
+ for (;;) {
362
+ while (at < code.length && /\s/.test(code[at])) at++;
363
+ while (next < comments.length && comments[next].start < at) next++;
364
+ const comment = comments[next];
365
+ if (comment === void 0 || comment.start !== at) return at;
366
+ at = comment.end;
367
+ }
368
+ }
369
+ /** A comment after code on its line with nothing after it: it belongs to that line, not the next. */
370
+ function trails(code, start, end) {
371
+ const lineStart = code.lastIndexOf("\n", start - 1) + 1;
372
+ if (code.slice(lineStart, start).trim() === "") return false;
373
+ const lineEnd = code.indexOf("\n", end);
374
+ return code.slice(end, lineEnd === -1 ? code.length : lineEnd).trim() === "";
375
+ }
376
+ /**
377
+ * The ranges the marker comments among `comments` cover in `program` (an
378
+ * ESTree `Program` of `code`). A marker that trails code on its line, or
379
+ * that no node follows, covers nothing. `{/* vi18n-ignore *\/}` alone in a
380
+ * JSX child covers the child after it.
381
+ */
382
+ function findMarkers(code, program, comments) {
383
+ if (!hasMarkers(code)) return NONE;
384
+ const spans = comments.map(span).sort((a, b) => a.start - b.start);
385
+ const markers = {
386
+ ignored: [],
387
+ contexts: []
388
+ };
389
+ for (const comment of comments) {
390
+ const marker = markerOf(comment);
391
+ if (marker === null) continue;
392
+ const { start, end } = span(comment);
393
+ if (trails(code, start, end)) continue;
394
+ let found = nodeAt(program, nextToken(code, end, spans));
395
+ const container = found.holder;
396
+ if (found.at === null && container.type === "JSXExpressionContainer" && container.expression?.type === "JSXEmptyExpression") found = nodeAt(program, nextToken(code, span(container).end, spans));
397
+ if (found.at === null) continue;
398
+ const target = span(found.at);
399
+ if (marker.note === null) markers.ignored.push([target.start, target.end]);
400
+ else markers.contexts.push({
401
+ note: marker.note,
402
+ ...target
403
+ });
404
+ }
405
+ return markers;
406
+ }
407
+ /** Whether `offset` lies in code a `vi18n-ignore` covers. */
408
+ function isIgnored(markers, offset) {
409
+ for (const [start, end] of markers.ignored) if (start <= offset && offset < end) return true;
410
+ return false;
411
+ }
412
+ const cache = /* @__PURE__ */ new WeakMap();
413
+ /** {@link findMarkers} of a parsed module, once per module (the scan and the captured-value pass share it). */
414
+ function markersOf(parsed) {
415
+ if (parsed.comments === void 0 || !hasMarkers(parsed.code)) return NONE;
416
+ let markers = cache.get(parsed);
417
+ if (markers === void 0) {
418
+ markers = findMarkers(parsed.code, parsed.program, parsed.comments);
419
+ cache.set(parsed, markers);
420
+ }
421
+ return markers;
422
+ }
423
+ //#endregion
424
+ //#region src/core/extract/zod.ts
425
+ /**
426
+ * The messages of the zod bridge (`vi18nZod()`, INTERNALS §6 D206). The
427
+ * scanner treats a `vi18nZod()` call as a `msg()` of each, in this order, and
428
+ * the transform passes them to the call, so they are extracted, translated
429
+ * and split like any message written at the call. The runtime
430
+ * (`src/runtime/zod.ts`) picks one by index: the order is the contract, and
431
+ * `tests/runtime/zod.test.ts` renders every issue through both.
432
+ *
433
+ * Worded for the people filling a form, not for developers: zod's own texts
434
+ * ("Too small: expected string to have >=3 characters") stay its fallback.
435
+ */
436
+ const ZOD_MESSAGES = [
437
+ ["Required", "Validation error: a required field is empty"],
438
+ ["{expected, select, number {Must be a number} int {Must be a whole number} bigint {Must be a whole number} boolean {Must be true or false} date {Must be a date} string {Must be text} array {Must be a list} file {Must be a file} other {Invalid value}}", "Validation error: a value of the wrong type"],
439
+ ["{count, plural, one {Must be at least # character} other {Must be at least # characters}}", "Validation error: a text is too short"],
440
+ ["{count, plural, one {Must be at most # character} other {Must be at most # characters}}", "Validation error: a text is too long"],
441
+ ["{count, plural, one {Must be exactly # character} other {Must be exactly # characters}}", "Validation error: a text must have this exact length"],
442
+ ["{count, plural, one {Must have at least # item} other {Must have at least # items}}", "Validation error: a list has too few items"],
443
+ ["{count, plural, one {Must have at most # item} other {Must have at most # items}}", "Validation error: a list has too many items"],
444
+ ["{count, plural, one {Must have exactly # item} other {Must have exactly # items}}", "Validation error: a list must have this exact number of items"],
445
+ ["{count, plural, one {Must be at least # byte} other {Must be at least # bytes}}", "Validation error: a file is too small"],
446
+ ["{count, plural, one {Must be at most # byte} other {Must be at most # bytes}}", "Validation error: a file is too large"],
447
+ ["{count, plural, one {Must be exactly # byte} other {Must be exactly # bytes}}", "Validation error: a file must have this exact size"],
448
+ ["Must be at least {minimum, number}", "Validation error: a number is too small"],
449
+ ["Must be greater than {minimum, number}", "Validation error: a number is too small"],
450
+ ["Must be at most {maximum, number}", "Validation error: a number is too large"],
451
+ ["Must be less than {maximum, number}", "Validation error: a number is too large"],
452
+ ["Must be on or after {minimum, date, medium}", "Validation error: a date is too early"],
453
+ ["Must be after {minimum, date, medium}", "Validation error: a date is too early"],
454
+ ["Must be on or before {maximum, date, medium}", "Validation error: a date is too late"],
455
+ ["Must be before {maximum, date, medium}", "Validation error: a date is too late"],
456
+ ["Must be a multiple of {divisor, number}", "Validation error: a number is not a multiple"],
457
+ ["Must be a valid email address", "Validation error"],
458
+ ["Must be a valid URL", "Validation error"],
459
+ ["Must start with “{prefix}”", "Validation error: {prefix} is the required beginning"],
460
+ ["Must end with “{suffix}”", "Validation error: {suffix} is the required ending"],
461
+ ["Must contain “{includes}”", "Validation error: {includes} is required text"],
462
+ ["Invalid format", "Validation error: a text does not have the expected format"],
463
+ ["Invalid option", "Validation error: a value is not one of the allowed options"],
464
+ ["{count, plural, one {Unrecognized key: {keys}} other {Unrecognized keys: {keys}}}", "Validation error: an object has keys it must not have"],
465
+ ["Invalid value", "Validation error"]
466
+ ];
467
+ //#endregion
468
+ //#region src/core/extract/scan.ts
469
+ /**
470
+ * The extraction scanner (INTERNALS §2.4, §2.5): finds vi18n calls by import
471
+ * binding in one walk over a parsed module, turns literal messages into
472
+ * occurrences and rewrite instructions, and reports build errors and
473
+ * scan-time warnings. Bindings are resolved lexically, so a parameter or local
474
+ * that shadows an imported `t` is never mistaken for it.
475
+ */
476
+ /** Checks that need the source locale's plural rules (`ScanOptions.sourceLocale`). */
477
+ const LOCALE_CODES = /* @__PURE__ */ new Set([
478
+ "icu-plural-missing-category",
479
+ "icu-plural-unused-category",
480
+ "icu-plural-one-literal"
481
+ ]);
482
+ const EXPORTS = new Map([
483
+ "t",
484
+ "msg",
485
+ "rich",
486
+ "richHtml",
487
+ "richParts",
488
+ "preload",
489
+ "withLocale",
490
+ "createTranslator"
491
+ ].map((name) => [name, {
492
+ role: name,
493
+ api: name
494
+ }]).concat([["vi18nZod", {
495
+ role: "zod",
496
+ api: "vi18nZod"
497
+ }]]));
498
+ /** Methods of a `createTranslator()` result, as bindings. */
499
+ const TRANSLATOR_METHODS = new Map([
500
+ "t",
501
+ "rich",
502
+ "richHtml",
503
+ "richParts"
504
+ ].map((name) => [name, {
505
+ role: "translator",
506
+ api: name
507
+ }]));
508
+ /** `HELPER.useT` in `src/vite/types.ts`; core does not import the plugin layer. */
509
+ const USE_T = "__vi18nUseT";
510
+ const HOOK_SPECIFIERS = /* @__PURE__ */ new Set(["virtual:vi18n/glue/react", "virtual:vi18n/glue/preact"]);
511
+ /** Import sources that are vi18n itself or generated glue, never a user wrapper. */
512
+ function isWrapperSpecifier(specifier) {
513
+ return specifier !== "@menapo/vi18n" && specifier !== "@menapo/vi18n/server" && !specifier.startsWith("virtual:vi18n/");
514
+ }
515
+ const GLOBAL = {
516
+ role: "global",
517
+ api: "t"
518
+ };
519
+ const ALIAS = {
520
+ role: "alias",
521
+ api: "t"
522
+ };
523
+ const HOOK = {
524
+ role: "t",
525
+ api: "t"
526
+ };
527
+ const USE_T_BINDING = {
528
+ role: "useT",
529
+ api: USE_T
530
+ };
531
+ const TRANSLATOR_OBJECT = {
532
+ role: "translatorObject",
533
+ api: ""
534
+ };
535
+ /** The default export of a `.vue`/`.svelte` import source: a `<T>` rich-text component (§6 D214). */
536
+ const COMPONENT = {
537
+ role: "component",
538
+ api: "rich"
539
+ };
540
+ const COMPONENT_SOURCE = /\.(?:vue|svelte)(?:\?.*)?$/;
541
+ /** The props of a `<T>` that extraction reads; the others (slots, snippets, `values`) are code. */
542
+ const COMPONENT_PROPS = /* @__PURE__ */ new Set([
543
+ "message",
544
+ "id",
545
+ "context",
546
+ "maxLength"
547
+ ]);
548
+ /** Vue's compiled templates reach `<script setup>` bindings through these render parameters. */
549
+ const VUE_CONTEXTS = /* @__PURE__ */ new Set(["$setup", "_ctx"]);
550
+ /** §2.4: calls of these on a message argument build a string at runtime. */
551
+ const STRING_METHODS = /* @__PURE__ */ new Set([
552
+ "concat",
553
+ "trim",
554
+ "trimStart",
555
+ "trimEnd",
556
+ "trimLeft",
557
+ "trimRight",
558
+ "toLowerCase",
559
+ "toUpperCase",
560
+ "toLocaleLowerCase",
561
+ "toLocaleUpperCase",
562
+ "replace",
563
+ "replaceAll",
564
+ "slice",
565
+ "substring",
566
+ "substr",
567
+ "padStart",
568
+ "padEnd",
569
+ "repeat",
570
+ "normalize",
571
+ "at",
572
+ "charAt",
573
+ "join",
574
+ "toString"
575
+ ]);
576
+ /** Keys that hold only TypeScript types; nothing under them runs. */
577
+ const TYPE_KEYS = /* @__PURE__ */ new Set([
578
+ "typeAnnotation",
579
+ "typeParameters",
580
+ "typeArguments",
581
+ "superTypeArguments",
582
+ "returnType",
583
+ "implements"
584
+ ]);
585
+ /** Statements with nothing to scan: type-only declarations, and imports and star re-exports (read by `listImports`). */
586
+ const TYPE_ONLY = /* @__PURE__ */ new Set([
587
+ "TSTypeAliasDeclaration",
588
+ "TSInterfaceDeclaration",
589
+ "TSDeclareFunction",
590
+ "TSImportEqualsDeclaration",
591
+ "TSExportAssignment",
592
+ "TSNamespaceExportDeclaration",
593
+ "ImportDeclaration",
594
+ "ExportAllDeclaration"
595
+ ]);
596
+ /** Leaves: nothing to visit below them. */
597
+ const LEAVES = /* @__PURE__ */ new Set([
598
+ "Identifier",
599
+ "Literal",
600
+ "TemplateElement",
601
+ "ThisExpression",
602
+ "Super",
603
+ "PrivateIdentifier",
604
+ "MetaProperty",
605
+ "JSXText",
606
+ "JSXIdentifier",
607
+ "JSXEmptyExpression",
608
+ "EmptyStatement",
609
+ "DebuggerStatement",
610
+ "BreakStatement",
611
+ "ContinueStatement"
612
+ ]);
613
+ /**
614
+ * Child keys per node type in evaluation order. Types not listed are walked
615
+ * through all their keys except {@link TYPE_KEYS}.
616
+ */
617
+ const CHILDREN = /* @__PURE__ */ new Map([
618
+ ["Program", ["body"]],
619
+ ["ExpressionStatement", ["expression"]],
620
+ ["ReturnStatement", ["argument"]],
621
+ ["ThrowStatement", ["argument"]],
622
+ ["IfStatement", [
623
+ "test",
624
+ "consequent",
625
+ "alternate"
626
+ ]],
627
+ ["WhileStatement", ["test", "body"]],
628
+ ["DoWhileStatement", ["body", "test"]],
629
+ ["LabeledStatement", ["body"]],
630
+ ["WithStatement", ["object", "body"]],
631
+ ["TryStatement", [
632
+ "block",
633
+ "handler",
634
+ "finalizer"
635
+ ]],
636
+ ["SwitchCase", ["test", "consequent"]],
637
+ ["VariableDeclaration", ["declarations"]],
638
+ ["VariableDeclarator", ["id", "init"]],
639
+ ["ExportNamedDeclaration", ["declaration"]],
640
+ ["ExportDefaultDeclaration", ["declaration"]],
641
+ ["ClassDeclaration", [
642
+ "decorators",
643
+ "superClass",
644
+ "body"
645
+ ]],
646
+ ["ClassBody", ["body"]],
647
+ ["Decorator", ["expression"]],
648
+ ["ArrayExpression", ["elements"]],
649
+ ["ObjectExpression", ["properties"]],
650
+ ["SpreadElement", ["argument"]],
651
+ ["UnaryExpression", ["argument"]],
652
+ ["UpdateExpression", ["argument"]],
653
+ ["AwaitExpression", ["argument"]],
654
+ ["YieldExpression", ["argument"]],
655
+ ["BinaryExpression", ["left", "right"]],
656
+ ["LogicalExpression", ["left", "right"]],
657
+ ["AssignmentExpression", ["left", "right"]],
658
+ ["ConditionalExpression", [
659
+ "test",
660
+ "consequent",
661
+ "alternate"
662
+ ]],
663
+ ["NewExpression", ["callee", "arguments"]],
664
+ ["SequenceExpression", ["expressions"]],
665
+ ["TemplateLiteral", ["expressions"]],
666
+ ["TaggedTemplateExpression", ["tag", "quasi"]],
667
+ ["ChainExpression", ["expression"]],
668
+ ["ParenthesizedExpression", ["expression"]],
669
+ ["ImportExpression", ["source", "options"]],
670
+ ["ObjectPattern", ["properties"]],
671
+ ["ArrayPattern", ["elements"]],
672
+ ["RestElement", ["argument"]],
673
+ ["AssignmentPattern", ["left", "right"]],
674
+ ["JSXElement", ["openingElement", "children"]],
675
+ ["JSXOpeningElement", ["attributes"]],
676
+ ["JSXFragment", ["children"]],
677
+ ["JSXAttribute", ["value"]],
678
+ ["JSXSpreadAttribute", ["argument"]],
679
+ ["JSXExpressionContainer", ["expression"]],
680
+ ["JSXSpreadChild", ["expression"]],
681
+ ["TSAsExpression", ["expression"]],
682
+ ["TSSatisfiesExpression", ["expression"]],
683
+ ["TSNonNullExpression", ["expression"]],
684
+ ["TSTypeAssertion", ["expression"]],
685
+ ["TSInstantiationExpression", ["expression"]],
686
+ ["TSParameterProperty", ["decorators", "parameter"]]
687
+ ]);
688
+ /** {@link walkPlan}: nothing below the node runs (a leaf or type-only syntax). */
689
+ const SKIP = null;
690
+ /** {@link walkPlan}: walk every key except {@link TYPE_KEYS}. */
691
+ const ALL_KEYS = void 0;
692
+ /**
693
+ * What `visitChildren` does with a node type, decided once per type instead
694
+ * of three set lookups and two string tests per node: a scan visits every
695
+ * node of every module that mentions vi18n.
696
+ */
697
+ const PLANS = /* @__PURE__ */ new Map();
698
+ function walkPlan(type) {
699
+ if (PLANS.has(type)) return PLANS.get(type);
700
+ const plan = LEAVES.has(type) || TYPE_ONLY.has(type) || type.startsWith("TS") && type.endsWith("Type") ? SKIP : CHILDREN.get(type);
701
+ PLANS.set(type, plan);
702
+ return plan;
703
+ }
704
+ /** `new Set(names)` per configured list: every module's scan gets the same arrays. */
705
+ const NAME_SETS = /* @__PURE__ */ new WeakMap();
706
+ function nameSet(names) {
707
+ let set = NAME_SETS.get(names);
708
+ if (set === void 0) NAME_SETS.set(names, set = new Set(names));
709
+ return set;
710
+ }
711
+ const PASS = null;
712
+ const NO_DIAGNOSTICS = [];
713
+ /**
714
+ * Text `validateSource` certainly finds nothing in: ASCII without ICU syntax
715
+ * (so it parses to one literal, has no apostrophe and no argument) and so
716
+ * without the bidi controls and isolates its literal scan looks for.
717
+ */
718
+ function isQuietPlainText(text) {
719
+ for (let i = 0; i < text.length; i++) {
720
+ const c = text.charCodeAt(i);
721
+ if (c > 126 || c === 39 || c === 60 || c === 123 || c === 125) return false;
722
+ }
723
+ return true;
724
+ }
725
+ function snippet(code, node) {
726
+ const text = code.slice(node.start, node.end).replace(/\s+/g, " ");
727
+ return text.length > 60 ? `${text.slice(0, 57)}…` : text;
728
+ }
729
+ function stringValue(node) {
730
+ if (node.type === "Literal") return typeof node.value === "string" ? node.value : null;
731
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
732
+ const cooked = (node.quasis[0]?.value)?.cooked;
733
+ return typeof cooked === "string" ? cooked : null;
734
+ }
735
+ return null;
736
+ }
737
+ /** Whether an object literal has a plain property named `name`. */
738
+ function hasProperty(node, name) {
739
+ for (const property of node.properties) if (property.type === "Property" && keyName(property.key, property.computed === true) === name) return true;
740
+ return false;
741
+ }
742
+ /**
743
+ * Whether a `const` initialiser holds message text that `t()` could never
744
+ * look up: a literal that is not a manual id, a descriptor without an `id`,
745
+ * or a choice (`? :`, `||`, `??`, `&&`) with such a branch. A value that
746
+ * reaches `t()` through a variable is looked up as an id at runtime, so a
747
+ * manual id there may be an `extraction.keep` id, and text built at runtime
748
+ * a dynamic key: neither is reported.
749
+ */
750
+ function textInit(init) {
751
+ const stack = [init];
752
+ for (let next = stack.pop(); next !== void 0; next = stack.pop()) {
753
+ const node = unwrap(next);
754
+ switch (node.type) {
755
+ case "ConditionalExpression":
756
+ stack.push(node.alternate, node.consequent);
757
+ break;
758
+ case "LogicalExpression":
759
+ stack.push(node.right, node.left);
760
+ break;
761
+ case "Literal":
762
+ case "TemplateLiteral": {
763
+ const text = stringValue(node);
764
+ if (text !== null && !isValidManualId(text)) return true;
765
+ break;
766
+ }
767
+ case "ObjectExpression": if (hasProperty(node, "message") && !hasProperty(node, "id")) return true;
768
+ }
769
+ }
770
+ return false;
771
+ }
772
+ /** Offset after `from` and any whitespace, comments and `)` that follow it, never past `limit`. */
773
+ function afterParens(code, from, limit) {
774
+ let at = from;
775
+ let i = from;
776
+ while (i < limit) {
777
+ const ch = code.charCodeAt(i);
778
+ if (ch === 41) at = ++i;
779
+ else if (ch === 47 && code[i + 1] === "*") {
780
+ const close = code.indexOf("*/", i + 2);
781
+ if (close === -1) break;
782
+ i = close + 2;
783
+ } else if (ch === 47 && code[i + 1] === "/") {
784
+ const close = code.indexOf("\n", i + 2);
785
+ if (close === -1) break;
786
+ i = close + 1;
787
+ } else if (/\s/.test(code[i])) i++;
788
+ else break;
789
+ }
790
+ return at;
791
+ }
792
+ const FUNCTIONS = /* @__PURE__ */ new Set([
793
+ "FunctionExpression",
794
+ "ArrowFunctionExpression",
795
+ "FunctionDeclaration"
796
+ ]);
797
+ /** True when `nodes` await or yield outside nested functions. Iterative: arguments can nest deeply. */
798
+ function suspends(nodes) {
799
+ const stack = [...nodes];
800
+ for (let node = stack.pop(); node !== void 0; node = stack.pop()) {
801
+ if (node.type === "AwaitExpression" || node.type === "YieldExpression") return true;
802
+ if (FUNCTIONS.has(node.type)) continue;
803
+ for (const key in node) {
804
+ if (key === "type" || key === "start" || key === "end" || TYPE_KEYS.has(key)) continue;
805
+ const value = node[key];
806
+ if (value === null || typeof value !== "object") continue;
807
+ if (Array.isArray(value)) {
808
+ for (const item of value) if (item !== null && typeof item.type === "string") stack.push(item);
809
+ } else if (typeof value.type === "string") stack.push(value);
810
+ }
811
+ }
812
+ return false;
813
+ }
814
+ var Scanner = class {
815
+ occurrences = [];
816
+ calls = [];
817
+ diagnostics = [];
818
+ /** `validateSource` per source text: a module often repeats a message. */
819
+ validated = /* @__PURE__ */ new Map();
820
+ /** {@link mentions}: per word, the first occurrence (`at`, or -1) at or after `from`. */
821
+ cursors = /* @__PURE__ */ new Map();
822
+ /**
823
+ * Message calls that were rewritten or reported as build errors
824
+ * (`extract-wrapper-unused`). Pass-through calls and helpers such as
825
+ * `preload` do not count: they say nothing about a wrapper's translation
826
+ * functions being configured.
827
+ */
828
+ messageCalls = 0;
829
+ /** `vi18n-ignore` and `vi18n-context:` comments (§2.5.8), set before the walk. */
830
+ markers = {
831
+ ignored: [],
832
+ contexts: []
833
+ };
834
+ /** Offset of each occurrence's literal, for the context comments. */
835
+ literalStarts = [];
836
+ scope;
837
+ code;
838
+ options;
839
+ /** Names whose declarations matter: vi18n locals and globals. Others never get a scope entry. */
840
+ interesting = /* @__PURE__ */ new Set();
841
+ globals;
842
+ aliases;
843
+ /** Module-level bindings by local name, for Vue's `$setup.x` / `_ctx.x`. */
844
+ moduleVars;
845
+ /** Locals bound to Vue's `unref` (compiled templates call it `_unref`). */
846
+ unrefNames = /* @__PURE__ */ new Set(["_unref"]);
847
+ /** A `<T>` component is imported: calls are checked for it as their first argument. */
848
+ components = false;
849
+ fnDepth = 0;
850
+ withLocaleDepth = 0;
851
+ /** Start of the innermost expression statement or concise arrow body. */
852
+ leftEdge = -1;
853
+ /** The left edge starts a statement of a statement list ({@link ScanCallSite.asi}). */
854
+ leftEdgeAsi = false;
855
+ /** The statement a statement list is visiting, so an expression statement knows it sits in one. */
856
+ listStatement = null;
857
+ /** Variable declarators whose initialiser may create a translator or hook local, settled once their scope is complete. */
858
+ pendingInits = [];
859
+ /** Expressions inserted as HTML, marked before their calls are visited. */
860
+ sinks = null;
861
+ markSink = (node) => {
862
+ (this.sinks ??= /* @__PURE__ */ new WeakSet()).add(node);
863
+ };
864
+ constructor(code, options) {
865
+ this.code = code;
866
+ this.options = options;
867
+ this.globals = nameSet(options.globals);
868
+ this.aliases = nameSet(options.aliases);
869
+ for (const name of options.globals) this.interesting.add(name);
870
+ this.moduleVars = /* @__PURE__ */ new Map();
871
+ this.scope = {
872
+ vars: this.moduleVars,
873
+ parent: null
874
+ };
875
+ }
876
+ where(node) {
877
+ const where = { file: this.options.refFile };
878
+ if (node === null) return where;
879
+ if (this.options.exactPositions) Object.assign(where, lineColumn(this.code, node.start));
880
+ if (this.options.ranges === true) where.range = [node.start, node.end];
881
+ return where;
882
+ }
883
+ /**
884
+ * Under `vi18n-ignore`: warnings and the module-scope check, a policy the
885
+ * config sets. Errors stand, since each one is a message a production
886
+ * client would render as nothing.
887
+ */
888
+ silenced(code, severity, node) {
889
+ return (severity === "warning" || code === "extract-module-scope") && this.markers.ignored.length > 0 && isIgnored(this.markers, node.start);
890
+ }
891
+ report(code, severity, message, node) {
892
+ if (this.silenced(code, severity, node)) return;
893
+ this.diagnostics.push({
894
+ code,
895
+ severity,
896
+ message,
897
+ ...this.where(node)
898
+ });
899
+ }
900
+ /** Records the module's imports; returns the first wrapper import, if any. */
901
+ bindImports(parsed) {
902
+ const sources = this.options.sourceSpecifiers;
903
+ let wrapper = null;
904
+ for (const record of listImports(parsed)) {
905
+ const { specifier } = record;
906
+ if (sources.has(specifier)) {
907
+ if (wrapper === null && record.bindings.length > 0 && isWrapperSpecifier(specifier)) wrapper = {
908
+ specifier,
909
+ start: record.start
910
+ };
911
+ for (const { local, imported } of record.bindings) {
912
+ const binding = imported === "*" ? {
913
+ role: "namespace",
914
+ api: "",
915
+ specifier
916
+ } : this.exported(imported, specifier);
917
+ if (binding !== null) {
918
+ this.moduleVars.set(local, binding);
919
+ this.interesting.add(local);
920
+ } else if (this.interesting.has(local)) this.moduleVars.set(local, null);
921
+ }
922
+ } else for (const { local, imported } of record.bindings) {
923
+ if (specifier === "vue" && imported === "unref") this.unrefNames.add(local);
924
+ if (this.globals.has(local)) this.moduleVars.set(local, null);
925
+ }
926
+ }
927
+ return wrapper;
928
+ }
929
+ /** The binding an export of an import source gives, or null for other exports. */
930
+ exported(name, specifier) {
931
+ const binding = EXPORTS.get(name);
932
+ if (binding !== void 0) return binding;
933
+ if (name === "default" && COMPONENT_SOURCE.test(specifier)) {
934
+ this.components = true;
935
+ return COMPONENT;
936
+ }
937
+ if (this.aliases.has(name)) return ALIAS;
938
+ if (name === USE_T && HOOK_SPECIFIERS.has(specifier)) return USE_T_BINDING;
939
+ return null;
940
+ }
941
+ /** True when the module has anything a walk could find. */
942
+ get active() {
943
+ return this.interesting.size > 0;
944
+ }
945
+ resolveName(name) {
946
+ if (!this.interesting.has(name)) return null;
947
+ for (let scope = this.scope; scope !== null; scope = scope.parent) {
948
+ const found = scope.vars.get(name);
949
+ if (found !== void 0) return found;
950
+ }
951
+ return this.globals.has(name) ? GLOBAL : null;
952
+ }
953
+ isUnref(callee) {
954
+ const node = unwrap(callee);
955
+ return node.type === "Identifier" && this.unrefNames.has(node.name);
956
+ }
957
+ /** The vi18n binding an expression evaluates to, through every compiled shape of §2.4. */
958
+ resolveRef(input) {
959
+ const node = unwrap(input);
960
+ switch (node.type) {
961
+ case "Identifier": return this.resolveName(node.name);
962
+ case "SequenceExpression": {
963
+ const expressions = node.expressions;
964
+ const last = expressions[expressions.length - 1];
965
+ return last === void 0 ? null : this.resolveRef(last);
966
+ }
967
+ case "CallExpression": {
968
+ const args = node.arguments;
969
+ if (args.length !== 1 || !this.isUnref(node.callee)) return null;
970
+ const arg = args[0];
971
+ return arg.type === "SpreadElement" ? null : this.resolveRef(arg);
972
+ }
973
+ case "MemberExpression": {
974
+ const name = keyName(node.property, node.computed === true);
975
+ if (name === null) return null;
976
+ const object = unwrap(node.object);
977
+ if (object.type === "Identifier" && VUE_CONTEXTS.has(object.name)) {
978
+ const binding = this.moduleVars.get(name);
979
+ if (binding !== void 0) return binding;
980
+ return this.globals.has(name) ? GLOBAL : null;
981
+ }
982
+ if (object.type === "MemberExpression" && unwrap(object.object).type !== "Identifier") return null;
983
+ const owner = this.resolveRef(object);
984
+ if (owner === null) return null;
985
+ if (owner.role === "namespace") return this.exported(name, owner.specifier);
986
+ if (owner.role === "translatorObject") return TRANSLATOR_METHODS.get(name) ?? null;
987
+ return null;
988
+ }
989
+ default: return null;
990
+ }
991
+ }
992
+ /** The binding a call reaches and the index of its message argument. */
993
+ resolveCallee(callee) {
994
+ const node = unwrap(callee);
995
+ let binding;
996
+ let argIndex = 0;
997
+ if (node.type === "MemberExpression" && keyName(node.property, node.computed === true) === "call") {
998
+ binding = this.resolveRef(node.object);
999
+ if (binding !== null) argIndex = 1;
1000
+ else binding = this.resolveRef(node);
1001
+ } else binding = this.resolveRef(node);
1002
+ if (binding === null || binding.role === "namespace" || binding.role === "translatorObject" || binding.role === "text") return null;
1003
+ return {
1004
+ binding,
1005
+ argIndex
1006
+ };
1007
+ }
1008
+ /**
1009
+ * The initialiser of the `const` that `node` reads, when it holds message
1010
+ * text (`textInit`), resolved lexically like the vi18n bindings. Vue dev
1011
+ * templates read `<script setup>` bindings as `$setup.x`; Vue hoists their
1012
+ * literal consts to module scope.
1013
+ */
1014
+ textConst(node) {
1015
+ let binding = null;
1016
+ if (node.type === "Identifier") binding = this.resolveName(node.name);
1017
+ else if (node.type === "MemberExpression") {
1018
+ const object = unwrap(node.object);
1019
+ const name = keyName(node.property, node.computed === true);
1020
+ if (object.type === "Identifier" && object.name === "$setup" && name !== null) binding = this.moduleVars.get(name);
1021
+ }
1022
+ return binding?.role === "text" ? binding.init ?? null : null;
1023
+ }
1024
+ bind(vars, name, binding) {
1025
+ if (binding === null && !this.interesting.has(name)) return vars;
1026
+ const map = vars ?? /* @__PURE__ */ new Map();
1027
+ map.set(name, binding);
1028
+ if (binding !== null) this.interesting.add(name);
1029
+ return map;
1030
+ }
1031
+ declarePattern(vars, pattern) {
1032
+ if (pattern === null) return vars;
1033
+ switch (pattern.type) {
1034
+ case "Identifier": return this.bind(vars, pattern.name, null);
1035
+ case "ObjectPattern":
1036
+ for (const property of pattern.properties) vars = this.declarePattern(vars, property.type === "RestElement" ? property.argument : property.value);
1037
+ return vars;
1038
+ case "ArrayPattern":
1039
+ for (const element of pattern.elements) vars = this.declarePattern(vars, element);
1040
+ return vars;
1041
+ case "AssignmentPattern": return this.declarePattern(vars, pattern.left);
1042
+ case "RestElement": return this.declarePattern(vars, pattern.argument);
1043
+ case "TSParameterProperty": return this.declarePattern(vars, pattern.parameter);
1044
+ default: return vars;
1045
+ }
1046
+ }
1047
+ /** The role of `init` when it creates a translator or a hook local, else null. */
1048
+ initRole(init) {
1049
+ if (init === null) return null;
1050
+ let node = unwrap(init);
1051
+ if (node.type === "AwaitExpression") node = unwrap(node.argument);
1052
+ if (node.type !== "CallExpression") return null;
1053
+ const role = this.resolveCallee(node.callee)?.binding.role;
1054
+ return role === "createTranslator" || role === "useT" ? role : null;
1055
+ }
1056
+ /**
1057
+ * Declares the names of a declaration. Initialisers that call something are
1058
+ * queued for {@link settle}: their callee must be resolved in the scope being
1059
+ * built, where a parameter or local may shadow `createTranslator` or
1060
+ * `__vi18nUseT`. A `const` holding message text is bound as `text`, so the
1061
+ * names that shadow it in inner scopes are recorded too.
1062
+ */
1063
+ declareVariables(vars, declaration) {
1064
+ const constant = declaration.kind === "const" && declaration.declare !== true;
1065
+ for (const declarator of declaration.declarations) {
1066
+ const id = declarator.id;
1067
+ vars = this.declarePattern(vars, id);
1068
+ const init = declarator.init;
1069
+ if (init === null) continue;
1070
+ let node = unwrap(init);
1071
+ if (constant && id.type === "Identifier" && textInit(node)) vars = this.bind(vars, id.name, {
1072
+ role: "text",
1073
+ api: "",
1074
+ init: node
1075
+ });
1076
+ if (node.type === "AwaitExpression") node = unwrap(node.argument);
1077
+ if (node.type === "CallExpression") this.pendingInits.push(declarator);
1078
+ }
1079
+ return vars;
1080
+ }
1081
+ /** Binds the queued translator and hook locals of a scope whose declarations are all known. */
1082
+ settle(vars) {
1083
+ if (this.pendingInits.length === 0) return vars;
1084
+ const declarators = this.pendingInits;
1085
+ this.pendingInits = [];
1086
+ const saved = this.scope;
1087
+ for (const declarator of declarators) {
1088
+ this.scope = vars === null || vars === saved.vars ? saved : {
1089
+ vars,
1090
+ parent: saved
1091
+ };
1092
+ const role = this.initRole(declarator.init);
1093
+ this.scope = saved;
1094
+ if (role !== null) vars = this.bindInit(vars, declarator.id, role);
1095
+ }
1096
+ return vars;
1097
+ }
1098
+ /** Binds a declarator's pattern for an initialiser of `role`, over what {@link declarePattern} bound. */
1099
+ bindInit(vars, id, role) {
1100
+ if (id.type === "Identifier") return this.bind(vars, id.name, role === "useT" ? HOOK : TRANSLATOR_OBJECT);
1101
+ if (id.type !== "ObjectPattern" || role !== "createTranslator") return vars;
1102
+ for (const property of id.properties) {
1103
+ if (property.type !== "Property") continue;
1104
+ const name = keyName(property.key, property.computed === true);
1105
+ let target = property.value;
1106
+ if (target.type === "AssignmentPattern") target = target.left;
1107
+ const method = name === null ? void 0 : TRANSLATOR_METHODS.get(name);
1108
+ if (method !== void 0 && target.type === "Identifier") vars = this.bind(vars, target.name, method);
1109
+ }
1110
+ return vars;
1111
+ }
1112
+ /** `var` declarations anywhere in a function body (not in nested functions). */
1113
+ hoistVars(vars, statements) {
1114
+ for (const statement of statements) vars = this.hoistIn(vars, statement);
1115
+ return vars;
1116
+ }
1117
+ hoistIn(vars, statement) {
1118
+ if (statement === null) return vars;
1119
+ switch (statement.type) {
1120
+ case "VariableDeclaration": return statement.kind === "var" ? this.declareVariables(vars, statement) : vars;
1121
+ case "ExportNamedDeclaration": return this.hoistIn(vars, statement.declaration);
1122
+ case "BlockStatement":
1123
+ case "StaticBlock": return this.hoistVars(vars, statement.body);
1124
+ case "IfStatement": {
1125
+ let current = statement;
1126
+ while (current?.type === "IfStatement") {
1127
+ vars = this.hoistIn(vars, current.consequent);
1128
+ current = current.alternate;
1129
+ }
1130
+ return this.hoistIn(vars, current);
1131
+ }
1132
+ case "ForStatement":
1133
+ vars = this.hoistIn(vars, statement.init);
1134
+ return this.hoistIn(vars, statement.body);
1135
+ case "ForInStatement":
1136
+ case "ForOfStatement":
1137
+ vars = this.hoistIn(vars, statement.left);
1138
+ return this.hoistIn(vars, statement.body);
1139
+ case "WhileStatement":
1140
+ case "DoWhileStatement":
1141
+ case "LabeledStatement":
1142
+ case "WithStatement": return this.hoistIn(vars, statement.body);
1143
+ case "TryStatement": {
1144
+ vars = this.hoistIn(vars, statement.block);
1145
+ const handler = statement.handler;
1146
+ if (handler !== null) vars = this.hoistIn(vars, handler.body);
1147
+ return this.hoistIn(vars, statement.finalizer);
1148
+ }
1149
+ case "SwitchStatement":
1150
+ for (const switchCase of statement.cases) vars = this.hoistVars(vars, switchCase.consequent);
1151
+ return vars;
1152
+ default: return vars;
1153
+ }
1154
+ }
1155
+ /** Block-scoped declarations made directly in a statement list. */
1156
+ lexical(vars, statements) {
1157
+ for (let statement of statements) {
1158
+ if ((statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration") && statement.declaration !== null) statement = statement.declaration;
1159
+ switch (statement.type) {
1160
+ case "VariableDeclaration":
1161
+ if (statement.kind !== "var") vars = this.declareVariables(vars, statement);
1162
+ break;
1163
+ case "FunctionDeclaration":
1164
+ case "ClassDeclaration":
1165
+ case "TSEnumDeclaration":
1166
+ case "TSModuleDeclaration": {
1167
+ const id = statement.id;
1168
+ if (id?.type === "Identifier") vars = this.bind(vars, id.name, null);
1169
+ break;
1170
+ }
1171
+ }
1172
+ }
1173
+ return vars;
1174
+ }
1175
+ enter(vars) {
1176
+ const saved = this.scope;
1177
+ if (vars !== null) this.scope = {
1178
+ vars,
1179
+ parent: saved
1180
+ };
1181
+ return saved;
1182
+ }
1183
+ program(program) {
1184
+ const body = program.body;
1185
+ this.settle(this.lexical(this.hoistVars(this.moduleVars, body), body));
1186
+ this.visitStatements(body);
1187
+ }
1188
+ /** A statement list: its expression statements may need {@link ScanCallSite.asi}. */
1189
+ visitStatements(statements) {
1190
+ for (const statement of statements) {
1191
+ this.listStatement = statement;
1192
+ this.visit(statement);
1193
+ }
1194
+ }
1195
+ visitAll(nodes) {
1196
+ for (const node of nodes) if (node !== null) this.visit(node);
1197
+ }
1198
+ visit(node) {
1199
+ switch (node.type) {
1200
+ case "CallExpression":
1201
+ this.visitCall(node);
1202
+ return;
1203
+ case "FunctionDeclaration":
1204
+ case "FunctionExpression":
1205
+ case "ArrowFunctionExpression":
1206
+ this.visitFunction(node);
1207
+ return;
1208
+ case "BlockStatement":
1209
+ case "StaticBlock":
1210
+ this.visitBlock(node);
1211
+ return;
1212
+ case "ForStatement":
1213
+ case "ForInStatement":
1214
+ case "ForOfStatement":
1215
+ this.visitFor(node);
1216
+ return;
1217
+ case "CatchClause": {
1218
+ const param = node.param;
1219
+ const saved = this.enter(this.declarePattern(null, param));
1220
+ if (param !== null) this.visit(param);
1221
+ this.visitBlock(node.body);
1222
+ this.scope = saved;
1223
+ return;
1224
+ }
1225
+ case "SwitchStatement": {
1226
+ this.visit(node.discriminant);
1227
+ const cases = node.cases;
1228
+ let vars = null;
1229
+ for (const switchCase of cases) vars = this.lexical(vars, switchCase.consequent);
1230
+ const saved = this.enter(this.settle(vars));
1231
+ for (const switchCase of cases) {
1232
+ if (switchCase.test !== null) this.visit(switchCase.test);
1233
+ this.visitStatements(switchCase.consequent);
1234
+ }
1235
+ this.scope = saved;
1236
+ return;
1237
+ }
1238
+ case "ClassExpression": {
1239
+ const id = node.id;
1240
+ const saved = this.enter(id === null ? null : this.bind(null, id.name, null));
1241
+ this.visitAll(node.decorators ?? []);
1242
+ if (node.superClass !== null) this.visit(node.superClass);
1243
+ this.visit(node.body);
1244
+ this.scope = saved;
1245
+ return;
1246
+ }
1247
+ case "ExpressionStatement": {
1248
+ const saved = this.leftEdge;
1249
+ const savedAsi = this.leftEdgeAsi;
1250
+ this.leftEdge = node.start;
1251
+ this.leftEdgeAsi = node === this.listStatement;
1252
+ this.visit(node.expression);
1253
+ this.leftEdge = saved;
1254
+ this.leftEdgeAsi = savedAsi;
1255
+ return;
1256
+ }
1257
+ case "MemberExpression":
1258
+ this.visitChain(node);
1259
+ return;
1260
+ case "IfStatement": {
1261
+ let current = node;
1262
+ while (current?.type === "IfStatement") {
1263
+ this.visit(current.test);
1264
+ this.visit(current.consequent);
1265
+ current = current.alternate;
1266
+ }
1267
+ if (current !== null) this.visit(current);
1268
+ return;
1269
+ }
1270
+ case "ConditionalExpression": {
1271
+ let current = node;
1272
+ while (current.type === "ConditionalExpression") {
1273
+ this.visit(current.test);
1274
+ this.visit(current.consequent);
1275
+ current = current.alternate;
1276
+ }
1277
+ this.visit(current);
1278
+ return;
1279
+ }
1280
+ case "Property":
1281
+ case "MethodDefinition":
1282
+ case "PropertyDefinition":
1283
+ case "AccessorProperty":
1284
+ if (this.options.warnings && node.type === "Property") markHtmlSinks(node, this.markSink);
1285
+ if (node.decorators !== void 0) this.visitAll(node.decorators);
1286
+ if (node.computed === true) this.visit(node.key);
1287
+ if (node.value !== null && node.value !== void 0) this.visit(node.value);
1288
+ return;
1289
+ case "JSXAttribute":
1290
+ case "AssignmentExpression":
1291
+ if (this.options.warnings) markHtmlSinks(node, this.markSink);
1292
+ break;
1293
+ case "BinaryExpression":
1294
+ case "LogicalExpression": {
1295
+ const rights = [];
1296
+ let current = node;
1297
+ while (current.type === "BinaryExpression" || current.type === "LogicalExpression") {
1298
+ rights.push(current.right);
1299
+ current = current.left;
1300
+ }
1301
+ this.visit(current);
1302
+ for (let i = rights.length - 1; i >= 0; i--) this.visit(rights[i]);
1303
+ return;
1304
+ }
1305
+ }
1306
+ this.visitChildren(node);
1307
+ }
1308
+ visitChildren(node) {
1309
+ const keys = walkPlan(node.type);
1310
+ if (keys === SKIP) return;
1311
+ if (keys !== ALL_KEYS) {
1312
+ for (const key of keys) {
1313
+ const value = node[key];
1314
+ if (value === null || typeof value !== "object") continue;
1315
+ if (Array.isArray(value)) {
1316
+ for (const item of value) if (item !== null) this.visit(item);
1317
+ } else this.visit(value);
1318
+ }
1319
+ return;
1320
+ }
1321
+ for (const key in node) {
1322
+ if (key === "type" || key === "start" || key === "end" || TYPE_KEYS.has(key)) continue;
1323
+ this.visitValue(node[key]);
1324
+ }
1325
+ }
1326
+ visitValue(value) {
1327
+ if (value === null || typeof value !== "object") return;
1328
+ if (Array.isArray(value)) for (const item of value) this.visitValue(item);
1329
+ else if (typeof value.type === "string") this.visit(value);
1330
+ }
1331
+ visitBlock(block) {
1332
+ const body = block.body;
1333
+ let vars = this.lexical(null, body);
1334
+ if (block.type === "StaticBlock") vars = this.hoistVars(vars, body);
1335
+ const saved = this.enter(this.settle(vars));
1336
+ this.visitStatements(body);
1337
+ this.scope = saved;
1338
+ }
1339
+ visitFor(node) {
1340
+ const head = node.type === "ForStatement" ? node.init : node.left;
1341
+ const vars = head?.type === "VariableDeclaration" && head.kind !== "var" ? this.declareVariables(null, head) : null;
1342
+ const saved = this.enter(this.settle(vars));
1343
+ this.visitChildren(node);
1344
+ this.scope = saved;
1345
+ }
1346
+ visitFunction(node) {
1347
+ let vars = null;
1348
+ const id = node.id;
1349
+ if (node.type === "FunctionExpression" && id) vars = this.bind(vars, id.name, null);
1350
+ const params = node.params;
1351
+ for (const param of params) vars = this.declarePattern(vars, param);
1352
+ const body = node.body;
1353
+ const block = body !== null && body.type === "BlockStatement" ? body.body : null;
1354
+ if (block !== null) vars = this.lexical(this.hoistVars(vars, block), block);
1355
+ const saved = this.enter(this.settle(vars));
1356
+ const savedEdge = this.leftEdge;
1357
+ const savedAsi = this.leftEdgeAsi;
1358
+ this.fnDepth++;
1359
+ this.visitAll(params);
1360
+ if (block !== null) this.visitStatements(block);
1361
+ else if (body !== null) {
1362
+ this.leftEdge = body.start;
1363
+ this.leftEdgeAsi = false;
1364
+ this.visit(body);
1365
+ }
1366
+ this.fnDepth--;
1367
+ this.leftEdge = savedEdge;
1368
+ this.leftEdgeAsi = savedAsi;
1369
+ this.scope = saved;
1370
+ }
1371
+ /**
1372
+ * Member and unrecognised call chains (`x.a().b().c()`) nest to the left:
1373
+ * walk down the spine in a loop, then visit each link's own parts in
1374
+ * evaluation order, so long generated chains never exhaust the stack.
1375
+ */
1376
+ visitChain(node) {
1377
+ const links = [];
1378
+ let current = node;
1379
+ for (;;) if (current.type === "MemberExpression") {
1380
+ links.push(current);
1381
+ current = current.object;
1382
+ } else if (current.type === "CallExpression" && this.resolveCallee(current.callee) === null) {
1383
+ links.push(current);
1384
+ current = current.callee;
1385
+ } else break;
1386
+ this.visit(current);
1387
+ for (let i = links.length - 1; i >= 0; i--) {
1388
+ const link = links[i];
1389
+ if (link.type === "MemberExpression") {
1390
+ if (link.computed === true) this.visit(link.property);
1391
+ } else {
1392
+ if (this.options.warnings) markHtmlSinks(link, this.markSink);
1393
+ this.visitAll(link.arguments);
1394
+ }
1395
+ }
1396
+ }
1397
+ visitCall(node) {
1398
+ const resolved = this.resolveCallee(node.callee);
1399
+ const args = node.arguments;
1400
+ if (resolved === null) {
1401
+ const first = args[0];
1402
+ if (this.components && first !== void 0 && this.resolveRef(first)?.role === "component" && this.componentCall(node, args)) return;
1403
+ this.visitChain(node);
1404
+ return;
1405
+ }
1406
+ const { binding, argIndex } = resolved;
1407
+ switch (binding.role) {
1408
+ case "component":
1409
+ if (!this.componentCall(node, args)) {
1410
+ this.visit(node.callee);
1411
+ this.visitAll(args);
1412
+ }
1413
+ return;
1414
+ case "withLocale":
1415
+ this.visit(node.callee);
1416
+ this.withLocaleDepth++;
1417
+ this.visitAll(args);
1418
+ this.withLocaleDepth--;
1419
+ return;
1420
+ case "preload": {
1421
+ const load = args[argIndex];
1422
+ if (load !== void 0 && load.type !== "SpreadElement" && args.length === argIndex + 1) this.calls.push({
1423
+ kind: "preload",
1424
+ occurrence: -1,
1425
+ start: node.start,
1426
+ end: node.end,
1427
+ insertAt: afterParens(this.code, load.end, node.end - 1),
1428
+ callStart: node.start,
1429
+ callEnd: node.end
1430
+ });
1431
+ this.visit(node.callee);
1432
+ this.visitAll(args);
1433
+ return;
1434
+ }
1435
+ case "createTranslator":
1436
+ case "useT":
1437
+ this.visit(node.callee);
1438
+ this.visitAll(args);
1439
+ return;
1440
+ case "zod":
1441
+ this.zodCall(node);
1442
+ this.visit(node.callee);
1443
+ this.visitAll(args);
1444
+ return;
1445
+ }
1446
+ this.messageCall(node, binding, args, argIndex);
1447
+ }
1448
+ /**
1449
+ * A `<T>` component rendered with props `args[1]` (§2.4 "Rich-text
1450
+ * components"): a literal `message` prop is extracted like a `rich()`
1451
+ * message and rewritten in place, with `id`, `context` and `maxLength`
1452
+ * props read as a descriptor's. A computed message is a runtime id, as in
1453
+ * `rich(props.label)`; text built from substitutions (Svelte's
1454
+ * `message="Hi {name}"`) is `extract-dynamic-message`. False when the
1455
+ * props hold no `message`.
1456
+ */
1457
+ componentCall(node, args) {
1458
+ const props = args[1] === void 0 ? null : unwrap(args[1]);
1459
+ if (props?.type !== "ObjectExpression") return false;
1460
+ const read = [];
1461
+ let message = null;
1462
+ for (const property of props.properties) {
1463
+ if (property.type !== "Property" || property.computed === true) continue;
1464
+ const key = keyName(property.key, false);
1465
+ if (key === null || !COMPONENT_PROPS.has(key)) continue;
1466
+ read.push(property);
1467
+ if (key !== "message") continue;
1468
+ message = unwrap(property.value);
1469
+ if (property.kind === "get") {
1470
+ const body = message.body?.body;
1471
+ const returned = body?.length === 1 && body[0]?.type === "ReturnStatement" ? body[0] : null;
1472
+ message = returned?.argument ? unwrap(returned.argument) : message;
1473
+ }
1474
+ }
1475
+ if (message === null) return false;
1476
+ const analysis = stringValue(message) === null ? this.analyse(message, false, "T") : this.descriptor({
1477
+ ...props,
1478
+ properties: read
1479
+ });
1480
+ if (analysis !== PASS) this.messageCalls++;
1481
+ if (analysis !== PASS && !analysis.ok) this.report(analysis.code, "error", analysis.message, analysis.node);
1482
+ else if (analysis !== PASS) {
1483
+ const callee = node.callee;
1484
+ const occurrence = this.occurrence(analysis, "rich", callee);
1485
+ if (occurrence !== null) {
1486
+ this.calls.push({
1487
+ kind: "rich",
1488
+ occurrence: this.occurrences.length,
1489
+ start: analysis.literal.start,
1490
+ end: analysis.literal.end,
1491
+ callStart: node.start,
1492
+ callEnd: node.end
1493
+ });
1494
+ this.occurrences.push(occurrence);
1495
+ }
1496
+ }
1497
+ this.visit(node.callee);
1498
+ this.visitAll(args);
1499
+ return true;
1500
+ }
1501
+ /**
1502
+ * `vi18nZod()` (INTERNALS §6 D206): one `msg()` occurrence per bridge
1503
+ * message, at the call, and one site each that inserts its rewritten form
1504
+ * as a leading argument, in `ZOD_MESSAGES` order. They are vi18n's own
1505
+ * messages, validated by its tests, so the project's source-locale checks
1506
+ * do not run on them.
1507
+ */
1508
+ zodCall(node) {
1509
+ const callee = node.callee;
1510
+ const from = Math.max(callee.end, node.typeArguments?.end ?? 0);
1511
+ const insertAt = this.code.indexOf("(", afterParens(this.code, from, node.end - 1)) + 1;
1512
+ for (const [source, context] of ZOD_MESSAGES) {
1513
+ const occurrence = this.occurrence({
1514
+ ok: true,
1515
+ source,
1516
+ literal: node,
1517
+ context
1518
+ }, "msg", callee, false);
1519
+ if (occurrence === null) continue;
1520
+ this.calls.push({
1521
+ kind: "zod",
1522
+ occurrence: this.occurrences.length,
1523
+ start: insertAt,
1524
+ end: insertAt,
1525
+ insertAt,
1526
+ callStart: node.start,
1527
+ callEnd: node.end
1528
+ });
1529
+ this.occurrences.push(occurrence);
1530
+ }
1531
+ }
1532
+ messageCall(node, binding, args, argIndex) {
1533
+ const callee = node.callee;
1534
+ const kind = binding.role;
1535
+ if (this.fnDepth === 0 && this.withLocaleDepth === 0 && this.options.moduleScope !== "off" && kind !== "msg" && kind !== "translator" && !this.silenced("extract-module-scope", "warning", node)) this.diagnostics.push(moduleScopeDiagnostic(snippet(this.code, callee), this.options.moduleScope === "error" ? "error" : "warning", this.where(node)));
1536
+ const arg = args[argIndex];
1537
+ let analysis;
1538
+ if (kind === "msg" && args.length > argIndex + 1) analysis = this.extraArguments(node);
1539
+ else if (arg !== void 0 && arg.type !== "SpreadElement") analysis = this.analyse(arg, kind === "msg", binding.api);
1540
+ else if (kind === "msg") analysis = this.missingMessage(node);
1541
+ else analysis = PASS;
1542
+ if (analysis !== PASS) this.messageCalls++;
1543
+ if (analysis === PASS || !analysis.ok) {
1544
+ if (analysis !== PASS) this.report(analysis.code, "error", analysis.message, analysis.node);
1545
+ this.visit(callee);
1546
+ for (const value of args) if (analysis !== PASS && value === arg && analysis.inline !== void 0) this.visitAll(analysis.inline.arguments);
1547
+ else this.visit(value);
1548
+ return;
1549
+ }
1550
+ const occurrence = this.occurrence(analysis, kind, callee);
1551
+ if (occurrence !== null) {
1552
+ const top = kind === "msg";
1553
+ const site = {
1554
+ kind,
1555
+ occurrence: this.occurrences.length,
1556
+ start: top ? node.start : arg.start,
1557
+ end: top ? node.end : arg.end,
1558
+ callStart: node.start,
1559
+ callEnd: node.end
1560
+ };
1561
+ if (node.start === this.leftEdge) {
1562
+ if (top) site.parenthesize = true;
1563
+ if (this.leftEdgeAsi) site.asi = true;
1564
+ }
1565
+ if (!top && this.awaitsOrYields(node, args)) site.argsAt = afterParens(this.code, Math.max(callee.end, node.typeArguments?.end ?? 0), node.end - 1);
1566
+ this.occurrences.push(occurrence);
1567
+ this.literalStarts.push(analysis.literal.start);
1568
+ this.calls.push(site);
1569
+ if (binding.api === "t" && this.sinks?.has(node) && !this.silenced("extract-html-sink", "warning", node)) this.diagnostics.push(htmlSinkDiagnostic(snippet(this.code, callee), occurrence.id, this.where(node)));
1570
+ }
1571
+ this.visit(callee);
1572
+ for (const value of args) if (value !== arg) this.visit(value);
1573
+ }
1574
+ occurrence(analysis, kind, callee, validate = true) {
1575
+ const { codec } = this.options;
1576
+ let canonical;
1577
+ try {
1578
+ canonical = codec.canonicalize(analysis.source);
1579
+ } catch (error) {
1580
+ if (!isDiagnosticError(error)) throw error;
1581
+ const { code, severity, message } = error.diagnostic;
1582
+ this.report(code, severity, `${message} in ${snippet(this.code, callee)}(${snippet(this.code, analysis.literal)})`, analysis.literal);
1583
+ return null;
1584
+ }
1585
+ if (validate) this.validate(analysis, callee);
1586
+ const label = codec.label(canonical);
1587
+ const position = this.options.exactPositions ? lineColumn(this.code, analysis.literal.start) : {
1588
+ line: 0,
1589
+ column: 0
1590
+ };
1591
+ const occurrence = {
1592
+ id: analysis.id ?? label,
1593
+ manual: analysis.id !== void 0,
1594
+ source: analysis.source,
1595
+ canonical,
1596
+ sourceHash: codec.sourceHash(canonical),
1597
+ label,
1598
+ callee: kind,
1599
+ file: this.options.refFile,
1600
+ line: position.line,
1601
+ column: position.column
1602
+ };
1603
+ if (analysis.context !== void 0) occurrence.context = analysis.context;
1604
+ if (analysis.maxLength !== void 0) occurrence.maxLength = analysis.maxLength;
1605
+ return occurrence;
1606
+ }
1607
+ /**
1608
+ * The source checks of §2.1.3 that parsing does not make (bidi controls,
1609
+ * unbalanced isolates, argument kinds, plural categories, apostrophes), so
1610
+ * a source the catalogs would reject as a translation is not extracted
1611
+ * silently. Positions inside the message give way to the literal's.
1612
+ */
1613
+ validate(analysis, callee) {
1614
+ const { source } = analysis;
1615
+ let found = this.validated.get(source);
1616
+ if (found === void 0 && isQuietPlainText(source)) found = NO_DIAGNOSTICS;
1617
+ if (found === void 0) {
1618
+ const locale = this.options.sourceLocale;
1619
+ found = validateSource(this.options.codec.parse(source), {
1620
+ locale: locale ?? "und",
1621
+ text: source
1622
+ });
1623
+ if (locale === void 0) found = found.filter((d) => !LOCALE_CODES.has(d.code));
1624
+ this.validated.set(source, found);
1625
+ }
1626
+ for (const { code, severity, message } of found) this.report(code, severity, `${message} in ${snippet(this.code, callee)}(${snippet(this.code, analysis.literal)})`, analysis.literal);
1627
+ }
1628
+ /**
1629
+ * Classifies a message argument (§2.4); `PASS` leaves it to the runtime.
1630
+ * `api` names the called function in hints.
1631
+ */
1632
+ analyse(arg, isMsg, api) {
1633
+ let node = unwrap(arg);
1634
+ if (node.type === "ChainExpression") node = unwrap(node.expression);
1635
+ const dynamic = () => ({
1636
+ ok: false,
1637
+ code: "extract-dynamic-message",
1638
+ message: isMsg ? `msg() declares a message and takes only a string literal or a { message } object literal, not ${snippet(this.code, node)}` : `A message must be a literal, not a computed string (${snippet(this.code, node)}); pass values as ICU arguments such as "Hello {name}"`,
1639
+ node
1640
+ });
1641
+ switch (node.type) {
1642
+ case "Literal":
1643
+ if (typeof node.value === "string") return {
1644
+ ok: true,
1645
+ source: node.value,
1646
+ literal: node
1647
+ };
1648
+ break;
1649
+ case "TemplateLiteral": {
1650
+ const value = stringValue(node);
1651
+ return value === null ? dynamic() : {
1652
+ ok: true,
1653
+ source: value,
1654
+ literal: node
1655
+ };
1656
+ }
1657
+ case "BinaryExpression":
1658
+ if (node.operator === "+") return dynamic();
1659
+ break;
1660
+ case "ObjectExpression": return this.descriptor(node);
1661
+ case "CallExpression":
1662
+ if (!isMsg) {
1663
+ const inner = this.resolveCallee(node.callee);
1664
+ if (inner?.binding.role === "msg") {
1665
+ const innerArgs = node.arguments;
1666
+ const value = innerArgs[inner.argIndex];
1667
+ const result = innerArgs.length > inner.argIndex + 1 ? this.extraArguments(node) : value === void 0 || value.type === "SpreadElement" ? this.missingMessage(node) : this.analyse(value, true, "msg");
1668
+ return result.ok ? result : {
1669
+ ...result,
1670
+ inline: node
1671
+ };
1672
+ }
1673
+ }
1674
+ if (this.buildsString(node)) return dynamic();
1675
+ break;
1676
+ case "ConditionalExpression":
1677
+ case "LogicalExpression":
1678
+ if (this.choiceText(node) !== null) return this.choice(node, isMsg ? "msg" : api);
1679
+ break;
1680
+ case "Identifier":
1681
+ case "MemberExpression": {
1682
+ if (isMsg) break;
1683
+ const init = this.textConst(node);
1684
+ if (init !== null) return {
1685
+ ok: false,
1686
+ code: "extract-dynamic-message",
1687
+ message: `${snippet(this.code, node)} holds message text (${snippet(this.code, init)}) that extraction cannot see through a variable; pass the literal to ${api}() directly, or declare it with msg()`,
1688
+ node
1689
+ };
1690
+ break;
1691
+ }
1692
+ }
1693
+ return isMsg ? dynamic() : PASS;
1694
+ }
1695
+ /**
1696
+ * The first branch of a `? :`, `||`, `??` or `&&` argument that would be
1697
+ * message text, or a build error, passed on its own; null when every branch
1698
+ * is left to the runtime. `msg()` branches are references, extracted where
1699
+ * they stand.
1700
+ */
1701
+ choiceText(node) {
1702
+ const stack = [node];
1703
+ for (let next = stack.pop(); next !== void 0; next = stack.pop()) {
1704
+ let branch = unwrap(next);
1705
+ if (branch.type === "ChainExpression") branch = unwrap(branch.expression);
1706
+ switch (branch.type) {
1707
+ case "ConditionalExpression":
1708
+ stack.push(branch.alternate, branch.consequent);
1709
+ break;
1710
+ case "LogicalExpression":
1711
+ stack.push(branch.right, branch.left);
1712
+ break;
1713
+ case "Literal":
1714
+ if (typeof branch.value === "string") return branch;
1715
+ break;
1716
+ case "TemplateLiteral":
1717
+ case "ObjectExpression": return branch;
1718
+ case "BinaryExpression":
1719
+ if (branch.operator === "+") return branch;
1720
+ break;
1721
+ case "CallExpression":
1722
+ if (this.buildsString(branch)) return branch;
1723
+ break;
1724
+ case "Identifier":
1725
+ case "MemberExpression": if (this.textConst(branch) !== null) return branch;
1726
+ }
1727
+ }
1728
+ return null;
1729
+ }
1730
+ /** `extract-dynamic-message` for a choice between texts, with the rewrite that works. */
1731
+ choice(node, api) {
1732
+ const hint = node.type === "ConditionalExpression" ? `cond ? ${api}("A") : ${api}("B")` : `x ${node.operator} ${api}("B")`;
1733
+ return {
1734
+ ok: false,
1735
+ code: "extract-dynamic-message",
1736
+ message: `A message must be one literal, not a choice between texts (${snippet(this.code, node)}); write ${hint}`,
1737
+ node
1738
+ };
1739
+ }
1740
+ /** Cheap text test first: almost no call has `await` or `yield` in it. */
1741
+ awaitsOrYields(call, args) {
1742
+ return (this.mentions("await", call.start, call.end) || this.mentions("yield", call.start, call.end)) && suspends(args);
1743
+ }
1744
+ /**
1745
+ * Whether `word` occurs in `code[start, end)`, without copying the range.
1746
+ * An unbounded `indexOf` runs to the next occurrence anywhere after
1747
+ * `start`, often the end of the file, so each word keeps the occurrence it
1748
+ * last found: calls are visited in source order, and while that occurrence
1749
+ * is still at or after `start` it is the first one there. That keeps a
1750
+ * module's searches linear in its size, not in calls × size.
1751
+ */
1752
+ mentions(word, start, end) {
1753
+ let cursor = this.cursors.get(word);
1754
+ if (cursor === void 0 || start < cursor.from || cursor.at !== -1 && cursor.at < start) {
1755
+ cursor = {
1756
+ from: start,
1757
+ at: this.code.indexOf(word, start)
1758
+ };
1759
+ this.cursors.set(word, cursor);
1760
+ }
1761
+ return cursor.at !== -1 && cursor.at + word.length <= end;
1762
+ }
1763
+ /**
1764
+ * `msg()` is replaced as a whole, so arguments past the message would vanish
1765
+ * with their side effects, and calls inside them would be rewritten inside
1766
+ * text already replaced.
1767
+ */
1768
+ extraArguments(call) {
1769
+ return {
1770
+ ok: false,
1771
+ code: "extract-dynamic-message",
1772
+ message: "msg() takes exactly one argument, the message",
1773
+ node: call
1774
+ };
1775
+ }
1776
+ missingMessage(call) {
1777
+ return {
1778
+ ok: false,
1779
+ code: "extract-dynamic-message",
1780
+ message: "msg() needs a string literal or a { message } object literal",
1781
+ node: call
1782
+ };
1783
+ }
1784
+ /** `String(…)` or a string-method call: a string computed at runtime. */
1785
+ buildsString(call) {
1786
+ let callee = unwrap(call.callee);
1787
+ if (callee.type === "ChainExpression") callee = unwrap(callee.expression);
1788
+ if (callee.type === "Identifier") return callee.name === "String";
1789
+ if (callee.type !== "MemberExpression") return false;
1790
+ const name = keyName(callee.property, callee.computed === true);
1791
+ return name !== null && STRING_METHODS.has(name);
1792
+ }
1793
+ /** `{ id?, message, context?, maxLength? }` with literal values only. */
1794
+ descriptor(node) {
1795
+ const invalid = (what, at) => ({
1796
+ ok: false,
1797
+ code: "extract-non-literal-option",
1798
+ message: `A message descriptor takes only literal id, message, context and maxLength properties: ${what}`,
1799
+ node: at
1800
+ });
1801
+ let source;
1802
+ let literal;
1803
+ let id;
1804
+ let idNode;
1805
+ let context;
1806
+ let maxLength;
1807
+ for (const property of node.properties) {
1808
+ if (property.type !== "Property" || property.kind !== "init" || property.method === true) return invalid(`${snippet(this.code, property)} is not a plain property`, property);
1809
+ const key = keyName(property.key, property.computed === true);
1810
+ const value = unwrap(property.value);
1811
+ switch (key) {
1812
+ case "message": {
1813
+ const text = stringValue(value);
1814
+ if (text === null) return invalid(`message is ${snippet(this.code, value)}`, value);
1815
+ source = text;
1816
+ literal = value;
1817
+ break;
1818
+ }
1819
+ case "context": {
1820
+ const text = stringValue(value);
1821
+ if (text === null) return invalid(`context is ${snippet(this.code, value)}`, value);
1822
+ context = text;
1823
+ break;
1824
+ }
1825
+ case "id": {
1826
+ const text = stringValue(value);
1827
+ if (text === null) return invalid(`id is ${snippet(this.code, value)}`, value);
1828
+ if (!isValidManualId(text)) return {
1829
+ ok: false,
1830
+ code: "extract-invalid-manual-id",
1831
+ message: `Manual id ${JSON.stringify(text)} is invalid; use two or more dot-separated segments of letters, digits, "_", ":" and "-", such as "hero.cta"`,
1832
+ node: value
1833
+ };
1834
+ id = text;
1835
+ idNode = value;
1836
+ break;
1837
+ }
1838
+ case "maxLength":
1839
+ if (value.type !== "Literal" || typeof value.value !== "number" || !Number.isInteger(value.value) || value.value <= 0) return invalid(`maxLength is ${snippet(this.code, value)}, not a positive integer`, value);
1840
+ maxLength = value.value;
1841
+ break;
1842
+ default: return invalid(`unknown property ${snippet(this.code, property.key)}`, property);
1843
+ }
1844
+ }
1845
+ if (source === void 0 || literal === void 0) {
1846
+ const library = id === void 0 ? void 0 : this.options.libraryIds?.get(id);
1847
+ if (library === void 0 || idNode === void 0) return invalid(id === void 0 ? "the message property is missing" : `the message property is missing, and no package in extraction.dependencies declares ${JSON.stringify(id)} in its vi18n.messages.json`, node);
1848
+ source = library;
1849
+ literal = idNode;
1850
+ }
1851
+ const result = {
1852
+ ok: true,
1853
+ source,
1854
+ literal
1855
+ };
1856
+ if (id !== void 0) result.id = id;
1857
+ if (context !== void 0) result.context = context;
1858
+ if (maxLength !== void 0) result.maxLength = maxLength;
1859
+ return result;
1860
+ }
1861
+ };
1862
+ /**
1863
+ * Gives each `vi18n-context:` note to the first message (by position) in the
1864
+ * code it applies to, after any descriptor `context`, as a second note.
1865
+ */
1866
+ function attachContexts(markers, occurrences, literalStarts) {
1867
+ for (const { note, start, end } of markers.contexts) {
1868
+ let first = -1;
1869
+ for (let i = 0; i < occurrences.length; i++) {
1870
+ const at = literalStarts[i];
1871
+ if (at >= start && at < end && (first === -1 || at < literalStarts[first])) first = i;
1872
+ }
1873
+ if (first === -1) continue;
1874
+ const occurrence = occurrences[first];
1875
+ occurrence.context = occurrence.context === void 0 ? note : `${occurrence.context}\n${note}`;
1876
+ }
1877
+ }
1878
+ /**
1879
+ * Scans one module (INTERNALS §2.5.5): every vi18n call with a literal
1880
+ * message becomes a {@link MessageOccurrence} and a call site for
1881
+ * `rewriteModule`; computed messages and bad descriptors are build errors;
1882
+ * other arguments pass through to the runtime untouched. `calls` holds
1883
+ * {@link ScanCallSite}s.
1884
+ */
1885
+ function scanModule(parsed, options) {
1886
+ const scanner = new Scanner(parsed.code, options);
1887
+ const wrapper = scanner.bindImports(parsed);
1888
+ if (scanner.active) {
1889
+ scanner.markers = markersOf(parsed);
1890
+ try {
1891
+ scanner.program(parsed.program);
1892
+ } catch (error) {
1893
+ if (!(error instanceof RangeError) || !/call stack/i.test(error.message)) throw error;
1894
+ throw diagnosticError({
1895
+ code: "extract-parse-error",
1896
+ severity: "error",
1897
+ message: `Cannot scan ${options.refFile}: its code is nested too deeply`,
1898
+ file: options.refFile
1899
+ });
1900
+ }
1901
+ }
1902
+ if (options.warnings && wrapper !== null && scanner.messageCalls === 0 && !isIgnored(markersOf(parsed), wrapper.start)) {
1903
+ const where = { file: options.refFile };
1904
+ if (options.exactPositions) Object.assign(where, lineColumn(parsed.code, wrapper.start));
1905
+ scanner.diagnostics.push(wrapperUnusedDiagnostic(wrapper.specifier, where));
1906
+ }
1907
+ attachContexts(scanner.markers, scanner.occurrences, scanner.literalStarts);
1908
+ return {
1909
+ occurrences: scanner.occurrences,
1910
+ calls: scanner.calls,
1911
+ diagnostics: scanner.diagnostics,
1912
+ importsWrapper: wrapper !== null
1913
+ };
1914
+ }
1915
+ //#endregion
1916
+ //#region src/core/extract/sources.ts
1917
+ /**
1918
+ * Which import specifiers of a module are vi18n sources for the CLI (INTERNALS
1919
+ * §2.5.2). The plugin asks Vite's resolver; the CLI has none, so a specifier
1920
+ * counts when it equals an `extraction.importSources` entry, when it is
1921
+ * relative and resolves to a wrapper's file, or when it is alias-shaped
1922
+ * (`$lib/i18n`, `@/lib/i18n`) and ends in a wrapper's basename (§6 D13).
1923
+ */
1924
+ /** Probed after an extensionless relative specifier, in this order (§2.5.2). */
1925
+ const PROBE_EXTENSIONS = [
1926
+ ".ts",
1927
+ ".tsx",
1928
+ ".js",
1929
+ ".jsx",
1930
+ ".mjs",
1931
+ ".mts"
1932
+ ];
1933
+ /** Script extensions a specifier's last segment may carry (`./i18n.js` names `i18n.ts` in TS ESM). */
1934
+ const SCRIPT_EXTENSION = /\.(?:[cm]?[jt]s|[jt]sx)$/;
1935
+ /** TypeScript's ESM convention: an import written `.js` names the `.ts` file. */
1936
+ const TS_TWINS = {
1937
+ ".js": [".ts", ".tsx"],
1938
+ ".jsx": [".tsx"],
1939
+ ".mjs": [".mts"],
1940
+ ".cjs": [".cts"]
1941
+ };
1942
+ const infos = /* @__PURE__ */ new WeakMap();
1943
+ function isRelative(specifier) {
1944
+ return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
1945
+ }
1946
+ /** The last path segment without a script extension and without a `?query`. */
1947
+ function lastName(specifier) {
1948
+ const query = specifier.indexOf("?");
1949
+ const path = query === -1 ? specifier : specifier.slice(0, query);
1950
+ return path.slice(path.lastIndexOf("/") + 1).replace(SCRIPT_EXTENSION, "");
1951
+ }
1952
+ function isFile(path) {
1953
+ return statSync(path, { throwIfNoEntry: false })?.isFile() === true;
1954
+ }
1955
+ function real(path) {
1956
+ try {
1957
+ return realpathSync(path);
1958
+ } catch {
1959
+ return path;
1960
+ }
1961
+ }
1962
+ /**
1963
+ * The module file a path names, with the extension probing of §2.5.2: as
1964
+ * written, with each probed extension, with the `.js` → `.ts` twin, then as a
1965
+ * directory's `index.*`. Returns its realpath, as the file walk lists files.
1966
+ */
1967
+ function probeModule(path) {
1968
+ const query = path.indexOf("?");
1969
+ if (query !== -1) path = path.slice(0, query);
1970
+ if (isFile(path)) return real(path);
1971
+ for (const ext of PROBE_EXTENSIONS) if (isFile(path + ext)) return real(path + ext);
1972
+ const ext = /\.[cm]?jsx?$/.exec(path)?.[0];
1973
+ if (ext !== void 0) {
1974
+ const stem = path.slice(0, -ext.length);
1975
+ for (const twin of TS_TWINS[ext] ?? []) if (isFile(stem + twin)) return real(stem + twin);
1976
+ }
1977
+ for (const ext of PROBE_EXTENSIONS) {
1978
+ const index = join(path, "index" + ext);
1979
+ if (isFile(index)) return real(index);
1980
+ }
1981
+ return null;
1982
+ }
1983
+ /**
1984
+ * JSONC as tsconfig files are written: comments and trailing commas removed
1985
+ * outside strings.
1986
+ */
1987
+ function parseJsonc(text) {
1988
+ let out = "";
1989
+ for (let i = 0; i < text.length; i++) {
1990
+ const ch = text[i];
1991
+ if (ch === "\"") {
1992
+ let j = i + 1;
1993
+ while (j < text.length && text[j] !== "\"") j += text[j] === "\\" ? 2 : 1;
1994
+ out += text.slice(i, j + 1);
1995
+ i = j;
1996
+ } else if (ch === "/" && text[i + 1] === "/") {
1997
+ while (i < text.length && text[i] !== "\n") i++;
1998
+ out += "\n";
1999
+ } else if (ch === "/" && text[i + 1] === "*") {
2000
+ const end = text.indexOf("*/", i + 2);
2001
+ i = end === -1 ? text.length : end + 1;
2002
+ } else out += ch;
2003
+ }
2004
+ return JSON.parse(out.replace(/,(\s*[}\]])/g, "$1"));
2005
+ }
2006
+ function readJson(file, jsonc) {
2007
+ try {
2008
+ const text = readFileSync(file, "utf8");
2009
+ const value = jsonc ? parseJsonc(text) : JSON.parse(text);
2010
+ return typeof value === "object" && value !== null ? value : null;
2011
+ } catch {
2012
+ return null;
2013
+ }
2014
+ }
2015
+ /** The first target a `package.json` `imports` value names, by condition order. */
2016
+ function conditionTarget(value) {
2017
+ if (typeof value === "string") return value;
2018
+ if (Array.isArray(value)) {
2019
+ for (const item of value) {
2020
+ const target = conditionTarget(item);
2021
+ if (target !== null) return target;
2022
+ }
2023
+ return null;
2024
+ }
2025
+ if (typeof value !== "object" || value === null) return null;
2026
+ for (const [condition, item] of Object.entries(value)) {
2027
+ if (condition === "types") continue;
2028
+ if (condition === "import" || condition === "module" || condition === "node" || condition === "default" || condition === "development" || condition === "production") {
2029
+ const target = conditionTarget(item);
2030
+ if (target !== null) return target;
2031
+ }
2032
+ }
2033
+ return null;
2034
+ }
2035
+ /** Applies a `*` pattern map (`imports`, tsconfig `paths`) to `specifier`. */
2036
+ function patternMatch(keys, specifier) {
2037
+ if (keys.includes(specifier)) return {
2038
+ key: specifier,
2039
+ star: ""
2040
+ };
2041
+ let best = null;
2042
+ for (const key of keys) {
2043
+ const at = key.indexOf("*");
2044
+ if (at === -1) continue;
2045
+ const prefix = key.slice(0, at);
2046
+ const suffix = key.slice(at + 1);
2047
+ if (specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix) && (best === null || prefix.length > best.key.indexOf("*"))) best = {
2048
+ key,
2049
+ star: specifier.slice(prefix.length, specifier.length - suffix.length)
2050
+ };
2051
+ }
2052
+ return best;
2053
+ }
2054
+ /** `#…` through the nearest `package.json` `imports` above the config root. */
2055
+ function resolvePackageImport(root, specifier) {
2056
+ for (let dir = root;; dir = dirname(dir)) {
2057
+ const pkg = readJson(join(dir, "package.json"), false);
2058
+ if (pkg !== null) {
2059
+ const imports = pkg.imports;
2060
+ if (typeof imports !== "object" || imports === null) return null;
2061
+ const map = imports;
2062
+ const hit = patternMatch(Object.keys(map), specifier);
2063
+ if (hit === null) return null;
2064
+ const target = conditionTarget(map[hit.key]);
2065
+ if (target === null || !target.startsWith("./")) return null;
2066
+ return probeModule(resolve(dir, target.replaceAll("*", hit.star)));
2067
+ }
2068
+ if (dirname(dir) === dir) return null;
2069
+ }
2070
+ }
2071
+ /** `compilerOptions.paths` in effect for `file`, following relative and package `extends`. */
2072
+ function tsconfigPaths(file, depth = 0) {
2073
+ const json = readJson(file, true);
2074
+ if (json === null || depth > 8) return null;
2075
+ const options = json.compilerOptions;
2076
+ const dir = dirname(file);
2077
+ const baseUrl = typeof options?.baseUrl === "string" ? resolve(dir, options.baseUrl) : null;
2078
+ if (typeof options?.paths === "object" && options.paths !== null) return {
2079
+ base: baseUrl ?? dir,
2080
+ paths: options.paths
2081
+ };
2082
+ const parents = typeof json.extends === "string" ? [json.extends] : json.extends;
2083
+ if (!Array.isArray(parents)) return null;
2084
+ for (let i = parents.length - 1; i >= 0; i--) {
2085
+ const parent = parents[i];
2086
+ if (typeof parent !== "string") continue;
2087
+ let parentFile = null;
2088
+ if (isRelative(parent) || isAbsolute(parent)) {
2089
+ const candidate = resolve(dir, parent);
2090
+ parentFile = isFile(candidate) ? candidate : isFile(candidate + ".json") ? candidate + ".json" : null;
2091
+ } else try {
2092
+ parentFile = createRequire(file).resolve(parent);
2093
+ } catch {
2094
+ parentFile = null;
2095
+ }
2096
+ if (parentFile === null) continue;
2097
+ const inherited = tsconfigPaths(parentFile, depth + 1);
2098
+ if (inherited !== null) return baseUrl === null ? inherited : {
2099
+ base: baseUrl,
2100
+ paths: inherited.paths
2101
+ };
2102
+ }
2103
+ return null;
2104
+ }
2105
+ function resolveTsPath(root, specifier) {
2106
+ for (const name of ["tsconfig.json", "jsconfig.json"]) {
2107
+ const file = join(root, name);
2108
+ if (!isFile(file)) continue;
2109
+ const config = tsconfigPaths(file);
2110
+ if (config === null) continue;
2111
+ const hit = patternMatch(Object.keys(config.paths), specifier);
2112
+ if (hit === null) continue;
2113
+ const targets = config.paths[hit.key];
2114
+ if (!Array.isArray(targets)) continue;
2115
+ for (const target of targets) {
2116
+ if (typeof target !== "string") continue;
2117
+ const found = probeModule(resolve(config.base, target.replaceAll("*", hit.star)));
2118
+ if (found !== null) return found;
2119
+ }
2120
+ }
2121
+ return null;
2122
+ }
2123
+ /**
2124
+ * The wrapper's file as Vite would most likely resolve it: a path relative
2125
+ * to the config root, a `#` subpath import, a tsconfig `paths` alias, or a
2126
+ * (workspace) package. Vite aliases live in the Vite config, which the CLI
2127
+ * does not load, so some alias wrappers stay unresolved.
2128
+ */
2129
+ function resolveWrapper(root, specifier) {
2130
+ const path = (file) => file === null ? null : {
2131
+ file,
2132
+ package: false
2133
+ };
2134
+ if (isRelative(specifier)) return path(probeModule(resolve(root, specifier)));
2135
+ if (isAbsolute(specifier)) return path(probeModule(specifier) ?? probeModule(join(root, specifier)));
2136
+ if (specifier.startsWith("#")) return path(resolvePackageImport(root, specifier));
2137
+ const aliased = resolveTsPath(root, specifier);
2138
+ if (aliased !== null) return path(aliased);
2139
+ try {
2140
+ return {
2141
+ file: real(createRequire(join(root, "package.json")).resolve(specifier)),
2142
+ package: true
2143
+ };
2144
+ } catch {
2145
+ return null;
2146
+ }
2147
+ }
2148
+ /**
2149
+ * The name an alias-form import of a wrapper written `specifier` ends in:
2150
+ * its last segment, or for `…/i18n/index` the directory's (an `index` name
2151
+ * would make every `…/index` import a source).
2152
+ */
2153
+ function writtenName(specifier) {
2154
+ const name = lastName(specifier);
2155
+ if (name !== "index") return name;
2156
+ const query = specifier.indexOf("?");
2157
+ const path = query === -1 ? specifier : specifier.slice(0, query);
2158
+ return lastName(path.slice(0, Math.max(0, path.lastIndexOf("/"))));
2159
+ }
2160
+ function sourceInfo(config) {
2161
+ const known = infos.get(config);
2162
+ if (known !== void 0 && known.importSources === config.extraction.importSources && known.root === config.root) return known;
2163
+ const entries = new Set(config.extraction.importSources);
2164
+ const names = /* @__PURE__ */ new Set();
2165
+ const unresolved = /* @__PURE__ */ new Set();
2166
+ const probe = /* @__PURE__ */ new Set();
2167
+ const files = /* @__PURE__ */ new Set();
2168
+ const usable = (name) => name !== "" && name !== "." && name !== ".." && name !== "index";
2169
+ for (const specifier of entries) {
2170
+ if (DEFAULT_IMPORT_SOURCES.includes(specifier)) continue;
2171
+ const wrapper = resolveWrapper(config.root, specifier);
2172
+ const written = writtenName(specifier);
2173
+ if (usable(written)) names.add(written);
2174
+ if (wrapper === null) {
2175
+ if (usable(written)) unresolved.add(written);
2176
+ continue;
2177
+ }
2178
+ files.add(wrapper.file);
2179
+ const fileName = lastName(basename(wrapper.file));
2180
+ probe.add(fileName);
2181
+ if (wrapper.package) continue;
2182
+ const alias = fileName === "index" ? basename(dirname(wrapper.file)) : fileName;
2183
+ if (usable(alias)) names.add(alias);
2184
+ }
2185
+ for (const name of names) probe.add(name);
2186
+ const needles = /* @__PURE__ */ new Set([
2187
+ ...entries,
2188
+ ...names,
2189
+ ...config.extraction.globals
2190
+ ]);
2191
+ for (const name of probe) if (!names.has(name)) needles.add("/" + name);
2192
+ const info = {
2193
+ importSources: config.extraction.importSources,
2194
+ root: config.root,
2195
+ entries,
2196
+ names,
2197
+ unresolved,
2198
+ probe,
2199
+ files,
2200
+ needles: [...needles]
2201
+ };
2202
+ infos.set(config, info);
2203
+ return info;
2204
+ }
2205
+ /**
2206
+ * Strings one of which occurs in every module that can hold an extracted call
2207
+ * (import sources, wrapper names, globals). A module containing none of them
2208
+ * cannot bind vi18n, so the scan skips parsing it, as the plugin's code
2209
+ * filter does.
2210
+ */
2211
+ function sourceNeedles(config) {
2212
+ return sourceInfo(config).needles;
2213
+ }
2214
+ /**
2215
+ * The specifiers among `imports` (of the module at absolute path `file`)
2216
+ * that are vi18n sources. Relative imports are resolved only when their last
2217
+ * segment names a wrapper, so a module costs file-system probes only for
2218
+ * likely wrapper imports. A wrapper the CLI cannot resolve is matched by name
2219
+ * for relative imports too, like alias-form imports (§6 D13).
2220
+ */
2221
+ function sourceSpecifiers(imports, file, config) {
2222
+ const info = sourceInfo(config);
2223
+ const out = /* @__PURE__ */ new Set();
2224
+ for (const { specifier } of imports) {
2225
+ if (out.has(specifier)) continue;
2226
+ if (info.entries.has(specifier)) {
2227
+ out.add(specifier);
2228
+ continue;
2229
+ }
2230
+ const name = lastName(specifier);
2231
+ if (!isRelative(specifier)) {
2232
+ if (info.names.has(name)) out.add(specifier);
2233
+ } else if (info.unresolved.has(name)) out.add(specifier);
2234
+ else if (info.probe.has(name)) {
2235
+ const target = probeModule(resolve(dirname(file), specifier));
2236
+ if (target !== null && info.files.has(target)) out.add(specifier);
2237
+ }
2238
+ }
2239
+ return out;
2240
+ }
2241
+ //#endregion
2242
+ export { isIgnored as a, unwrap as c, listImports as d, findMarkers as i, lineColumn as l, sourceSpecifiers as n, markersOf as o, scanModule as r, keyName as s, sourceNeedles as t, locateLiteral as u };