@zintljs/extractor 0.1.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/index.d.mts +345 -0
- package/dist/index.mjs +2433 -0
- package/package.json +60 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2433 @@
|
|
|
1
|
+
import { parseSync } from "oxc-parser";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
//#region src/logger.ts
|
|
5
|
+
const COLORS = {
|
|
6
|
+
reset: "\x1B[0m",
|
|
7
|
+
bold: "\x1B[1m",
|
|
8
|
+
dim: "\x1B[2m",
|
|
9
|
+
red: "\x1B[31m",
|
|
10
|
+
green: "\x1B[32m",
|
|
11
|
+
yellow: "\x1B[33m",
|
|
12
|
+
blue: "\x1B[34m",
|
|
13
|
+
magenta: "\x1B[35m",
|
|
14
|
+
cyan: "\x1B[36m",
|
|
15
|
+
gray: "\x1B[90m"
|
|
16
|
+
};
|
|
17
|
+
const LEVEL_WEIGHTS = {
|
|
18
|
+
silent: 0,
|
|
19
|
+
error: 1,
|
|
20
|
+
warn: 2,
|
|
21
|
+
info: 3
|
|
22
|
+
};
|
|
23
|
+
var ZintlLogger = class ZintlLogger {
|
|
24
|
+
level;
|
|
25
|
+
prefix;
|
|
26
|
+
debugEnabled = false;
|
|
27
|
+
debugScope = null;
|
|
28
|
+
lastDebugTime = Date.now();
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
const envLevel = typeof process !== "undefined" ? process.env?.ZINTL_LOG_LEVEL : void 0;
|
|
31
|
+
const isTest = typeof process !== "undefined" && (process.env?.NODE_ENV === "test" || process.env?.VITEST === "true");
|
|
32
|
+
this.prefix = options.prefix || "Zintl";
|
|
33
|
+
this.setupDebug(options.debug);
|
|
34
|
+
const resolvedLevel = envLevel || options.level || (isTest && !this.debugEnabled ? "silent" : "info");
|
|
35
|
+
this.level = LEVEL_WEIGHTS[resolvedLevel] ?? LEVEL_WEIGHTS.info;
|
|
36
|
+
}
|
|
37
|
+
setupDebug(debugOption) {
|
|
38
|
+
const envDebug = (typeof process !== "undefined" ? process.env?.DEBUG : "") || "";
|
|
39
|
+
const isZintlAll = envDebug.includes("zintl:*");
|
|
40
|
+
if (debugOption === true || isZintlAll || envDebug === "*") {
|
|
41
|
+
this.debugEnabled = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (typeof debugOption === "string") {
|
|
45
|
+
this.debugScope = debugOption;
|
|
46
|
+
this.debugEnabled = true;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const normalizedScope = this.prefix.toLowerCase().split("/").pop();
|
|
50
|
+
if (normalizedScope && envDebug.includes(`zintl:${normalizedScope}`)) this.debugEnabled = true;
|
|
51
|
+
}
|
|
52
|
+
formatMessage(levelStr, color, message) {
|
|
53
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour12: false });
|
|
54
|
+
return `${`${COLORS.gray}[${timestamp}]${COLORS.reset} ${color}${COLORS.bold}[${this.prefix}/${levelStr}]${COLORS.reset}`} ${message}`;
|
|
55
|
+
}
|
|
56
|
+
formatDebugMessage(message) {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const diff = now - this.lastDebugTime;
|
|
59
|
+
this.lastDebugTime = now;
|
|
60
|
+
const namespace = `zintl:${this.prefix.toLowerCase().replace(/^zintl\//i, "")}`;
|
|
61
|
+
const colors = [
|
|
62
|
+
COLORS.magenta,
|
|
63
|
+
COLORS.cyan,
|
|
64
|
+
COLORS.blue,
|
|
65
|
+
COLORS.green,
|
|
66
|
+
COLORS.yellow
|
|
67
|
+
];
|
|
68
|
+
return ` ${colors[namespace.length % colors.length]}${namespace}${COLORS.reset} ${message} ${COLORS.gray}+${diff}ms${COLORS.reset}`;
|
|
69
|
+
}
|
|
70
|
+
error(message, ...args) {
|
|
71
|
+
if (this.level >= LEVEL_WEIGHTS.error) console.error(this.formatMessage("ERROR", COLORS.red, message), ...args);
|
|
72
|
+
}
|
|
73
|
+
warn(message, ...args) {
|
|
74
|
+
if (this.level >= LEVEL_WEIGHTS.warn) console.warn(this.formatMessage("WARN", COLORS.yellow, message), ...args);
|
|
75
|
+
}
|
|
76
|
+
info(message, ...args) {
|
|
77
|
+
if (this.level >= LEVEL_WEIGHTS.info) console.info(this.formatMessage("INFO", COLORS.cyan, message), ...args);
|
|
78
|
+
}
|
|
79
|
+
debug(message, ...args) {
|
|
80
|
+
if (this.debugEnabled) {
|
|
81
|
+
if (this.debugScope && !this.prefix.toLowerCase().includes(this.debugScope.toLowerCase())) return;
|
|
82
|
+
console.debug(this.formatDebugMessage(message), ...args);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create a sub-logger with a combined prefix.
|
|
87
|
+
*/
|
|
88
|
+
withPrefix(subPrefix) {
|
|
89
|
+
const newPrefix = `${this.prefix}/${subPrefix}`;
|
|
90
|
+
const subLogger = new ZintlLogger({
|
|
91
|
+
level: Object.keys(LEVEL_WEIGHTS).find((key) => LEVEL_WEIGHTS[key] === this.level),
|
|
92
|
+
prefix: newPrefix,
|
|
93
|
+
debug: this.debugEnabled || this.debugScope || void 0
|
|
94
|
+
});
|
|
95
|
+
subLogger.lastDebugTime = this.lastDebugTime;
|
|
96
|
+
return subLogger;
|
|
97
|
+
}
|
|
98
|
+
setLevel(level) {
|
|
99
|
+
this.level = LEVEL_WEIGHTS[level];
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/** Global default logger */
|
|
103
|
+
const logger = new ZintlLogger();
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/constants.ts
|
|
106
|
+
/**
|
|
107
|
+
* The extractor's only constants.
|
|
108
|
+
*
|
|
109
|
+
* The former `DEFAULT_UI_ATTRIBUTES` / `DEFAULT_UI_OBJECT_FIELDS` /
|
|
110
|
+
* `DEFAULT_UI_SINK_PROPERTIES` sets and `TEMPLATE_ATTR_REGEX` lived here and
|
|
111
|
+
* encoded opinions about which DOM and JSX attributes are translatable. That is
|
|
112
|
+
* facet knowledge — it now lives in `@zintljs/compiler/facets` and reaches the
|
|
113
|
+
* extractor as descriptors. All four were already unreferenced (one survived
|
|
114
|
+
* only inside a commented-out line) and have been removed.
|
|
115
|
+
*/
|
|
116
|
+
const ZINTL_MACRO = "zintl";
|
|
117
|
+
const RUNTIME_PACKAGE = "zintl";
|
|
118
|
+
/**
|
|
119
|
+
* Module specifiers that carry the Zintl runtime surface.
|
|
120
|
+
*
|
|
121
|
+
* This list was previously inlined at four call sites (`parser.ts`, two in
|
|
122
|
+
* `visitors/program.ts`, one in `visitors/bindings.ts`) and the four had drifted:
|
|
123
|
+
* the `bindings.ts` copy omitted the bare `"zintl"` literal, so a project with a
|
|
124
|
+
* custom `runtimePackage` would have had its bare `"zintl"` imports recognised
|
|
125
|
+
* by three of the four checks and missed by the fourth.
|
|
126
|
+
*/
|
|
127
|
+
const RUNTIME_SPECIFIERS = [
|
|
128
|
+
"zintl",
|
|
129
|
+
"zintl/internal",
|
|
130
|
+
"zintl/macro",
|
|
131
|
+
"virtual:zintl/runtime/internal"
|
|
132
|
+
];
|
|
133
|
+
/** Whether an import specifier resolves to the Zintl runtime. */
|
|
134
|
+
function isRuntimeSpecifier(source, runtimePackage = RUNTIME_PACKAGE) {
|
|
135
|
+
return source === runtimePackage || RUNTIME_SPECIFIERS.includes(source);
|
|
136
|
+
}
|
|
137
|
+
const HTML_TAG_SPLIT_REGEX = /(<[^>]+>)/g;
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/targets.ts
|
|
140
|
+
let pluginIdCounter = 0;
|
|
141
|
+
const pluginIds = /* @__PURE__ */ new WeakMap();
|
|
142
|
+
function getTargetKey(t) {
|
|
143
|
+
if (typeof t === "string") return t;
|
|
144
|
+
if (t && typeof t === "object") {
|
|
145
|
+
let id = pluginIds.get(t);
|
|
146
|
+
if (!id) {
|
|
147
|
+
id = `plugin:${pluginIdCounter++}`;
|
|
148
|
+
pluginIds.set(t, id);
|
|
149
|
+
}
|
|
150
|
+
return id;
|
|
151
|
+
}
|
|
152
|
+
return String(t);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Memoized per unique descriptor set. Callers must treat the result as
|
|
156
|
+
* read-only — it is shared. The compiler's `compileExtractionState` copies it
|
|
157
|
+
* before attaching rules, which is what keeps two compilers with identical
|
|
158
|
+
* descriptors from clobbering each other.
|
|
159
|
+
*/
|
|
160
|
+
const targetsCache = /* @__PURE__ */ new Map();
|
|
161
|
+
function resolveTargets(targets) {
|
|
162
|
+
const cacheKey = targets.map(getTargetKey).join("|");
|
|
163
|
+
const cached = targetsCache.get(cacheKey);
|
|
164
|
+
if (cached) return cached;
|
|
165
|
+
const jsxAttributes = /* @__PURE__ */ new Set();
|
|
166
|
+
const jsxElementAttributes = /* @__PURE__ */ new Map();
|
|
167
|
+
const domProperties = /* @__PURE__ */ new Set();
|
|
168
|
+
const objectFields = /* @__PURE__ */ new Set();
|
|
169
|
+
const htmlAttributes = /* @__PURE__ */ new Set();
|
|
170
|
+
const plugins = [];
|
|
171
|
+
const fastPathHints = [];
|
|
172
|
+
for (const item of new Set(targets)) if (typeof item === "string") {
|
|
173
|
+
if (item.startsWith("jsx:")) {
|
|
174
|
+
const parts = item.split(":");
|
|
175
|
+
if (parts.length === 3) {
|
|
176
|
+
const [, element, attr] = parts;
|
|
177
|
+
if (element === "*") jsxAttributes.add(attr);
|
|
178
|
+
else {
|
|
179
|
+
let attrs = jsxElementAttributes.get(element);
|
|
180
|
+
if (!attrs) {
|
|
181
|
+
attrs = /* @__PURE__ */ new Set();
|
|
182
|
+
jsxElementAttributes.set(element, attrs);
|
|
183
|
+
}
|
|
184
|
+
attrs.add(attr);
|
|
185
|
+
}
|
|
186
|
+
fastPathHints.push(attr);
|
|
187
|
+
}
|
|
188
|
+
} else if (item.startsWith("dom:prop:")) {
|
|
189
|
+
const prop = item.substring(9);
|
|
190
|
+
domProperties.add(prop);
|
|
191
|
+
fastPathHints.push(prop);
|
|
192
|
+
} else if (item.startsWith("dom:attr:")) {
|
|
193
|
+
const attr = item.substring(9);
|
|
194
|
+
fastPathHints.push(attr);
|
|
195
|
+
} else if (item.startsWith("obj:field:")) {
|
|
196
|
+
const field = item.substring(10);
|
|
197
|
+
objectFields.add(field);
|
|
198
|
+
fastPathHints.push(field);
|
|
199
|
+
} else if (item.startsWith("html:attr:")) {
|
|
200
|
+
const attr = item.substring(10);
|
|
201
|
+
htmlAttributes.add(attr);
|
|
202
|
+
fastPathHints.push(attr);
|
|
203
|
+
}
|
|
204
|
+
} else if (item && typeof item === "object") {
|
|
205
|
+
plugins.push(item);
|
|
206
|
+
if (item.fastPathHint) if (Array.isArray(item.fastPathHint)) fastPathHints.push(...item.fastPathHint);
|
|
207
|
+
else fastPathHints.push(item.fastPathHint);
|
|
208
|
+
}
|
|
209
|
+
const uniqueHints = Array.from(new Set(fastPathHints));
|
|
210
|
+
const hasDomSinks = domProperties.size > 0;
|
|
211
|
+
const hasJsxSinks = jsxAttributes.size > 0 || jsxElementAttributes.size > 0;
|
|
212
|
+
const hasHtmlSinks = htmlAttributes.size > 0;
|
|
213
|
+
const patternParts = [
|
|
214
|
+
"zintl",
|
|
215
|
+
"loadI18nInstance",
|
|
216
|
+
"t\\(",
|
|
217
|
+
...uniqueHints.map((h) => h.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
218
|
+
];
|
|
219
|
+
if (hasJsxSinks || hasHtmlSinks) patternParts.push("<");
|
|
220
|
+
const resolved = {
|
|
221
|
+
jsxAttributes,
|
|
222
|
+
jsxElementAttributes,
|
|
223
|
+
domProperties,
|
|
224
|
+
objectFields,
|
|
225
|
+
htmlAttributes,
|
|
226
|
+
plugins,
|
|
227
|
+
fastPathHints,
|
|
228
|
+
uniqueHints,
|
|
229
|
+
fastPathRegex: new RegExp(patternParts.join("|")),
|
|
230
|
+
hasDomSinks,
|
|
231
|
+
hasJsxSinks,
|
|
232
|
+
sfcRules: [],
|
|
233
|
+
suppressionRules: [],
|
|
234
|
+
mustacheRegex: null
|
|
235
|
+
};
|
|
236
|
+
targetsCache.set(cacheKey, resolved);
|
|
237
|
+
return resolved;
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/comments.ts
|
|
241
|
+
function parseZintlComments(nodeStart, trivias, code) {
|
|
242
|
+
const result = {
|
|
243
|
+
contextVars: {},
|
|
244
|
+
ignore: false
|
|
245
|
+
};
|
|
246
|
+
if (!trivias) return result;
|
|
247
|
+
for (const trivia of trivias) {
|
|
248
|
+
if (trivia.start > nodeStart) continue;
|
|
249
|
+
const gap = code.slice(trivia.end, nodeStart);
|
|
250
|
+
if ((gap.match(/\n/g) || []).length > 1) continue;
|
|
251
|
+
if (/[^ \t\r\n{}/ *]/.test(gap)) continue;
|
|
252
|
+
const text = trivia.value.trim();
|
|
253
|
+
if (!text.includes("@zintl-")) continue;
|
|
254
|
+
const parts = text.split(/(@zintl-[a-z]+)/);
|
|
255
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
256
|
+
const directive = parts[i];
|
|
257
|
+
const content = (parts[i + 1] || "").trim();
|
|
258
|
+
if (directive === "@zintl-ignore") result.ignore = true;
|
|
259
|
+
else if (directive === "@zintl-note") {
|
|
260
|
+
if (!result.note) result.note = content.startsWith(":") ? content.slice(1).trim() : content;
|
|
261
|
+
} else if (directive === "@zintl-pass") {
|
|
262
|
+
const varRegex = /([a-zA-Z0-9_]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}|([^\s]+))/g;
|
|
263
|
+
let match;
|
|
264
|
+
while ((match = varRegex.exec(content)) !== null) {
|
|
265
|
+
const name = match[1];
|
|
266
|
+
const doubleQuoted = match[2];
|
|
267
|
+
const singleQuoted = match[3];
|
|
268
|
+
const expression = match[4];
|
|
269
|
+
const unquoted = match[5];
|
|
270
|
+
if (expression !== void 0) result.contextVars[name] = expression.trim();
|
|
271
|
+
else if (doubleQuoted !== void 0) result.contextVars[name] = `"${doubleQuoted}"`;
|
|
272
|
+
else if (singleQuoted !== void 0) result.contextVars[name] = `'${singleQuoted}'`;
|
|
273
|
+
else if (unquoted !== void 0) if (!isNaN(Number(unquoted)) || unquoted === "true" || unquoted === "false") result.contextVars[name] = unquoted;
|
|
274
|
+
else result.contextVars[name] = `"${unquoted}"`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Heuristic to find comments attached to a node or its logical parents.
|
|
283
|
+
*/
|
|
284
|
+
function getAttachedComments(node, parents, trivias, code) {
|
|
285
|
+
if (!trivias) return {
|
|
286
|
+
contextVars: {},
|
|
287
|
+
ignore: false
|
|
288
|
+
};
|
|
289
|
+
return parseZintlComments(node.start, trivias, code);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Extracts directives from a raw string (e.g. an HTML comment tag)
|
|
293
|
+
*/
|
|
294
|
+
function parseHTMLDirectives(text) {
|
|
295
|
+
const result = {
|
|
296
|
+
contextVars: {},
|
|
297
|
+
ignore: false
|
|
298
|
+
};
|
|
299
|
+
const cleanText = text.replace(/^<!--/, "").replace(/-->$/, "").trim();
|
|
300
|
+
if (!cleanText.includes("@zintl-")) return result;
|
|
301
|
+
const parts = cleanText.split(/(@zintl-[a-z]+)/);
|
|
302
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
303
|
+
const directive = parts[i];
|
|
304
|
+
let content = (parts[i + 1] || "").trim();
|
|
305
|
+
if (content.startsWith(":")) content = content.slice(1).trim();
|
|
306
|
+
if (directive === "@zintl-ignore") result.ignore = true;
|
|
307
|
+
else if (directive === "@zintl-note") {
|
|
308
|
+
if (!result.note) result.note = content;
|
|
309
|
+
} else if (directive === "@zintl-pass") {
|
|
310
|
+
const varRegex = /([a-zA-Z0-9_]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}|([^\s]+))/g;
|
|
311
|
+
let match;
|
|
312
|
+
while ((match = varRegex.exec(content)) !== null) {
|
|
313
|
+
const name = match[1];
|
|
314
|
+
const doubleQuoted = match[2];
|
|
315
|
+
const singleQuoted = match[3];
|
|
316
|
+
const expression = match[4];
|
|
317
|
+
const unquoted = match[5];
|
|
318
|
+
if (expression !== void 0) result.contextVars[name] = expression.trim();
|
|
319
|
+
else if (doubleQuoted !== void 0) result.contextVars[name] = `"${doubleQuoted}"`;
|
|
320
|
+
else if (singleQuoted !== void 0) result.contextVars[name] = `'${singleQuoted}'`;
|
|
321
|
+
else if (unquoted !== void 0) if (!isNaN(Number(unquoted)) || unquoted === "true" || unquoted === "false") result.contextVars[name] = unquoted;
|
|
322
|
+
else result.contextVars[name] = `"${unquoted}"`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/context.ts
|
|
330
|
+
const VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
331
|
+
"area",
|
|
332
|
+
"base",
|
|
333
|
+
"br",
|
|
334
|
+
"col",
|
|
335
|
+
"embed",
|
|
336
|
+
"hr",
|
|
337
|
+
"img",
|
|
338
|
+
"input",
|
|
339
|
+
"link",
|
|
340
|
+
"meta",
|
|
341
|
+
"param",
|
|
342
|
+
"source",
|
|
343
|
+
"track",
|
|
344
|
+
"wbr"
|
|
345
|
+
]);
|
|
346
|
+
function getTagName(token) {
|
|
347
|
+
const match = token.match(/^<\/?([a-zA-Z0-9:-]+)/);
|
|
348
|
+
return match ? match[1].toLowerCase() : "";
|
|
349
|
+
}
|
|
350
|
+
function isSingleWrappingPhrasingTag(html) {
|
|
351
|
+
const trimmed = html.trim();
|
|
352
|
+
if (!trimmed.startsWith("<") || !trimmed.endsWith(">")) return false;
|
|
353
|
+
if (trimmed.endsWith("/>")) return false;
|
|
354
|
+
const tokens = trimmed.split(/(<[^>]+>)/g).filter((t) => t.length > 0);
|
|
355
|
+
if (tokens.length < 3) return false;
|
|
356
|
+
const first = tokens[0];
|
|
357
|
+
if (!first.startsWith("<") || first.startsWith("</") || first.startsWith("<!--")) return false;
|
|
358
|
+
const firstTagName = getTagName(first);
|
|
359
|
+
if (!INLINE_PHRASING_TAGS.has(firstTagName.replace(/\d+$/, ""))) return false;
|
|
360
|
+
const last = tokens[tokens.length - 1];
|
|
361
|
+
if (last !== `</${firstTagName}>` && !last.startsWith(`</${firstTagName}`)) return false;
|
|
362
|
+
const stack = [];
|
|
363
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
364
|
+
const token = tokens[i];
|
|
365
|
+
if (token.startsWith("<") && token.endsWith(">")) {
|
|
366
|
+
if (token.startsWith("<!--")) continue;
|
|
367
|
+
const isClosing = token.startsWith("</");
|
|
368
|
+
if (token.endsWith("/>")) continue;
|
|
369
|
+
const tagName = getTagName(token);
|
|
370
|
+
if (isClosing) {
|
|
371
|
+
if (stack.length === 0) return false;
|
|
372
|
+
if (stack.pop() !== tagName) return false;
|
|
373
|
+
if (stack.length === 0 && i < tokens.length - 1) return false;
|
|
374
|
+
} else stack.push(tagName);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return stack.length === 0;
|
|
378
|
+
}
|
|
379
|
+
function hasTranslatableText(text) {
|
|
380
|
+
let stripped = text.replace(/<[^>]+>/g, "");
|
|
381
|
+
stripped = stripped.replace(/\{[^}]+\}/g, "");
|
|
382
|
+
return stripped.trim().length > 0;
|
|
383
|
+
}
|
|
384
|
+
function hasNonWhitespaceOutsidePhrasing(html) {
|
|
385
|
+
const tokens = html.split(/(<[^>]+>)/g);
|
|
386
|
+
let textOutside = "";
|
|
387
|
+
const stack = [];
|
|
388
|
+
for (const token of tokens) if (token.startsWith("<") && token.endsWith(">")) {
|
|
389
|
+
if (token.startsWith("<!--")) continue;
|
|
390
|
+
const isClosing = token.startsWith("</");
|
|
391
|
+
if (token.endsWith("/>")) continue;
|
|
392
|
+
const tagName = getTagName(token);
|
|
393
|
+
if (isClosing) {
|
|
394
|
+
if (stack.length > 0) stack.pop();
|
|
395
|
+
} else stack.push(tagName);
|
|
396
|
+
} else if (stack.length === 0) textOutside += token;
|
|
397
|
+
return textOutside.trim().length > 0;
|
|
398
|
+
}
|
|
399
|
+
const INLINE_PHRASING_TAGS = /* @__PURE__ */ new Set([
|
|
400
|
+
"span",
|
|
401
|
+
"code",
|
|
402
|
+
"strong",
|
|
403
|
+
"em",
|
|
404
|
+
"a",
|
|
405
|
+
"b",
|
|
406
|
+
"i",
|
|
407
|
+
"u",
|
|
408
|
+
"mark",
|
|
409
|
+
"small",
|
|
410
|
+
"s",
|
|
411
|
+
"del",
|
|
412
|
+
"ins",
|
|
413
|
+
"sub",
|
|
414
|
+
"sup",
|
|
415
|
+
"abbr",
|
|
416
|
+
"time",
|
|
417
|
+
"q",
|
|
418
|
+
"img",
|
|
419
|
+
"br",
|
|
420
|
+
"picture",
|
|
421
|
+
"svg",
|
|
422
|
+
"use",
|
|
423
|
+
"path",
|
|
424
|
+
"circle",
|
|
425
|
+
"rect",
|
|
426
|
+
"g",
|
|
427
|
+
"defs",
|
|
428
|
+
"symbol",
|
|
429
|
+
"clippath",
|
|
430
|
+
"mask",
|
|
431
|
+
"pattern",
|
|
432
|
+
"polygon",
|
|
433
|
+
"polyline",
|
|
434
|
+
"line",
|
|
435
|
+
"ellipse",
|
|
436
|
+
"text",
|
|
437
|
+
"tspan",
|
|
438
|
+
"stop",
|
|
439
|
+
"lineargradient",
|
|
440
|
+
"radialgradient",
|
|
441
|
+
"image"
|
|
442
|
+
]);
|
|
443
|
+
function normalizeTags(html) {
|
|
444
|
+
const tokens = html.split(/(<[^>]+>)/g);
|
|
445
|
+
const tagMap = [];
|
|
446
|
+
const distinctOpenTags = {};
|
|
447
|
+
for (const token of tokens) if (token.startsWith("<") && token.endsWith(">")) {
|
|
448
|
+
const isClosing = token.startsWith("</");
|
|
449
|
+
if (!token.startsWith("<!--") && !isClosing) {
|
|
450
|
+
const tagName = getTagName(token);
|
|
451
|
+
if (INLINE_PHRASING_TAGS.has(tagName)) {
|
|
452
|
+
if (!distinctOpenTags[tagName]) distinctOpenTags[tagName] = [];
|
|
453
|
+
if (!distinctOpenTags[tagName].includes(token)) distinctOpenTags[tagName].push(token);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const activeStacks = {};
|
|
458
|
+
let normalized = "";
|
|
459
|
+
const offsetMap = [];
|
|
460
|
+
let origIdx = 0;
|
|
461
|
+
for (const token of tokens) {
|
|
462
|
+
if (token.startsWith("<") && token.endsWith(">")) {
|
|
463
|
+
const isClosing = token.startsWith("</");
|
|
464
|
+
if (token.startsWith("<!--")) {
|
|
465
|
+
for (let i = 0; i < token.length; i++) offsetMap.push(origIdx + i);
|
|
466
|
+
normalized += token;
|
|
467
|
+
} else {
|
|
468
|
+
const tagName = getTagName(token);
|
|
469
|
+
if (INLINE_PHRASING_TAGS.has(tagName)) {
|
|
470
|
+
const list = distinctOpenTags[tagName] || [];
|
|
471
|
+
const totalConfigs = list.length;
|
|
472
|
+
let normToken = "";
|
|
473
|
+
if (isClosing) if (totalConfigs > 1) normToken = `</${tagName}${(activeStacks[tagName] || []).pop() || 1}>`;
|
|
474
|
+
else normToken = `</${tagName}>`;
|
|
475
|
+
else {
|
|
476
|
+
let alias = tagName;
|
|
477
|
+
const isVoid = VOID_ELEMENTS.has(tagName) || token.endsWith("/>");
|
|
478
|
+
if (totalConfigs > 1) {
|
|
479
|
+
const idx = list.indexOf(token) + 1;
|
|
480
|
+
if (!isVoid) {
|
|
481
|
+
if (!activeStacks[tagName]) activeStacks[tagName] = [];
|
|
482
|
+
activeStacks[tagName].push(idx);
|
|
483
|
+
}
|
|
484
|
+
alias = `${tagName}${idx}`;
|
|
485
|
+
}
|
|
486
|
+
normToken = isVoid ? `<${alias}/>` : `<${alias}>`;
|
|
487
|
+
if (!tagMap.some((entry) => entry.alias === alias)) tagMap.push({
|
|
488
|
+
alias,
|
|
489
|
+
originalOpen: token,
|
|
490
|
+
tagName
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
for (let i = 0; i < normToken.length; i++) {
|
|
494
|
+
const origOffset = origIdx + Math.min(token.length - 1, Math.floor(i / normToken.length * token.length));
|
|
495
|
+
offsetMap.push(origOffset);
|
|
496
|
+
}
|
|
497
|
+
normalized += normToken;
|
|
498
|
+
} else {
|
|
499
|
+
for (let i = 0; i < token.length; i++) offsetMap.push(origIdx + i);
|
|
500
|
+
normalized += token;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
for (let i = 0; i < token.length; i++) offsetMap.push(origIdx + i);
|
|
505
|
+
normalized += token;
|
|
506
|
+
}
|
|
507
|
+
origIdx += token.length;
|
|
508
|
+
}
|
|
509
|
+
offsetMap.push(origIdx);
|
|
510
|
+
return {
|
|
511
|
+
normalized,
|
|
512
|
+
tagMap,
|
|
513
|
+
offsetMap
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
var ExtractionContext = class {
|
|
517
|
+
code;
|
|
518
|
+
filePath;
|
|
519
|
+
fileBoundaryId;
|
|
520
|
+
options;
|
|
521
|
+
_localBoundaries;
|
|
522
|
+
_internalDeps;
|
|
523
|
+
_exportedBoundaries;
|
|
524
|
+
boundaryHashes = {};
|
|
525
|
+
trivias = [];
|
|
526
|
+
hasZintlMacro = false;
|
|
527
|
+
hasZintlMarker = false;
|
|
528
|
+
hasTopLevelAnchor = false;
|
|
529
|
+
isZeroConfig = true;
|
|
530
|
+
mode = "boundary";
|
|
531
|
+
zintlImportGroup;
|
|
532
|
+
componentFunctions = /* @__PURE__ */ new Set();
|
|
533
|
+
registerComponentFunction(parents) {
|
|
534
|
+
const funcNode = parents.find((p) => [
|
|
535
|
+
"FunctionDeclaration",
|
|
536
|
+
"FunctionExpression",
|
|
537
|
+
"ArrowFunctionExpression"
|
|
538
|
+
].includes(p.type));
|
|
539
|
+
if (funcNode && funcNode.body && funcNode.body.type === "BlockStatement") this.componentFunctions.add(funcNode.body.start + 1);
|
|
540
|
+
}
|
|
541
|
+
_rawSinks;
|
|
542
|
+
_seenSinks;
|
|
543
|
+
handledNodes = /* @__PURE__ */ new Set();
|
|
544
|
+
_rawManualTranslations;
|
|
545
|
+
_messages;
|
|
546
|
+
_transforms;
|
|
547
|
+
_runtimeImports;
|
|
548
|
+
_usedKeys;
|
|
549
|
+
_anchorSites;
|
|
550
|
+
_dependencyPaths;
|
|
551
|
+
_scopeBoundaries;
|
|
552
|
+
_logicTaintedIdentifiers;
|
|
553
|
+
_dependencyBindings;
|
|
554
|
+
get messages() {
|
|
555
|
+
return this._messages ??= /* @__PURE__ */ new Map();
|
|
556
|
+
}
|
|
557
|
+
get transforms() {
|
|
558
|
+
return this._transforms ??= [];
|
|
559
|
+
}
|
|
560
|
+
get runtimeImports() {
|
|
561
|
+
return this._runtimeImports ??= [];
|
|
562
|
+
}
|
|
563
|
+
get usedKeys() {
|
|
564
|
+
return this._usedKeys ??= /* @__PURE__ */ new Set();
|
|
565
|
+
}
|
|
566
|
+
get anchorSites() {
|
|
567
|
+
return this._anchorSites ??= [];
|
|
568
|
+
}
|
|
569
|
+
get dependencyPaths() {
|
|
570
|
+
return this._dependencyPaths ??= /* @__PURE__ */ new Map();
|
|
571
|
+
}
|
|
572
|
+
get scopeBoundaries() {
|
|
573
|
+
return this._scopeBoundaries ??= /* @__PURE__ */ new Map();
|
|
574
|
+
}
|
|
575
|
+
get logicTaintedIdentifiers() {
|
|
576
|
+
return this._logicTaintedIdentifiers ??= /* @__PURE__ */ new Set();
|
|
577
|
+
}
|
|
578
|
+
get dependencyBindings() {
|
|
579
|
+
return this._dependencyBindings ??= /* @__PURE__ */ new Map();
|
|
580
|
+
}
|
|
581
|
+
get localBoundaries() {
|
|
582
|
+
return this._localBoundaries ??= /* @__PURE__ */ new Map();
|
|
583
|
+
}
|
|
584
|
+
get internalDeps() {
|
|
585
|
+
return this._internalDeps ??= /* @__PURE__ */ new Map();
|
|
586
|
+
}
|
|
587
|
+
get exportedBoundaries() {
|
|
588
|
+
return this._exportedBoundaries ??= /* @__PURE__ */ new Map();
|
|
589
|
+
}
|
|
590
|
+
get rawSinks() {
|
|
591
|
+
return this._rawSinks ??= [];
|
|
592
|
+
}
|
|
593
|
+
get rawManualTranslations() {
|
|
594
|
+
return this._rawManualTranslations ??= [];
|
|
595
|
+
}
|
|
596
|
+
get seenSinks() {
|
|
597
|
+
return this._seenSinks ??= /* @__PURE__ */ new Set();
|
|
598
|
+
}
|
|
599
|
+
runtimePackage;
|
|
600
|
+
uiAttributes;
|
|
601
|
+
uiObjectFields;
|
|
602
|
+
uiSinkProperties;
|
|
603
|
+
jsxElementAttributes;
|
|
604
|
+
htmlAttributes;
|
|
605
|
+
targetPlugins;
|
|
606
|
+
boundaryStack;
|
|
607
|
+
logger;
|
|
608
|
+
isIgnoredFile = false;
|
|
609
|
+
suppressionLevel = 0;
|
|
610
|
+
/** Pre-built fast-path regex derived from the active target configuration. */
|
|
611
|
+
fastPathRegex;
|
|
612
|
+
/** True when at least one dom:prop target is configured (e.g. innerHTML). */
|
|
613
|
+
hasDomSinks;
|
|
614
|
+
/** True when at least one jsx: target is configured. */
|
|
615
|
+
hasJsxSinks;
|
|
616
|
+
sfcRules;
|
|
617
|
+
suppressionRules;
|
|
618
|
+
mustacheRegex;
|
|
619
|
+
constructor(code, filePath, fileBoundaryId, options = {}) {
|
|
620
|
+
this.code = code;
|
|
621
|
+
this.filePath = filePath;
|
|
622
|
+
this.fileBoundaryId = fileBoundaryId;
|
|
623
|
+
this.options = options;
|
|
624
|
+
this.logger = options.logger || logger;
|
|
625
|
+
this.runtimePackage = options.runtimePackage || "zintl";
|
|
626
|
+
this.uiAttributes = options.uiAttributes ? new Set(options.uiAttributes) : /* @__PURE__ */ new Set();
|
|
627
|
+
this.uiObjectFields = options.uiObjectFields ? new Set(options.uiObjectFields) : /* @__PURE__ */ new Set();
|
|
628
|
+
this.uiSinkProperties = options.uiSinkProperties ? [...options.uiSinkProperties] : [];
|
|
629
|
+
this.jsxElementAttributes = /* @__PURE__ */ new Map();
|
|
630
|
+
this.htmlAttributes = /* @__PURE__ */ new Set();
|
|
631
|
+
this.targetPlugins = [];
|
|
632
|
+
const compiledState = options.compiledState ?? resolveTargets(options.targets || []);
|
|
633
|
+
options.compiledState = compiledState;
|
|
634
|
+
for (const attr of compiledState.jsxAttributes) this.uiAttributes.add(attr);
|
|
635
|
+
for (const field of compiledState.objectFields) this.uiObjectFields.add(field);
|
|
636
|
+
for (const prop of compiledState.domProperties) if (!this.uiSinkProperties.includes(prop)) this.uiSinkProperties.push(prop);
|
|
637
|
+
this.jsxElementAttributes = compiledState.jsxElementAttributes;
|
|
638
|
+
this.htmlAttributes = compiledState.htmlAttributes;
|
|
639
|
+
this.targetPlugins = compiledState.plugins;
|
|
640
|
+
this.hasDomSinks = compiledState.hasDomSinks;
|
|
641
|
+
this.hasJsxSinks = compiledState.hasJsxSinks;
|
|
642
|
+
this.sfcRules = [...compiledState.sfcRules, ...options.sfcRules || []];
|
|
643
|
+
this.suppressionRules = [...compiledState.suppressionRules, ...options.suppressionRules || []];
|
|
644
|
+
let mustacheRegex = compiledState.mustacheRegex ?? null;
|
|
645
|
+
if (!mustacheRegex && compiledState.mustacheRules) {
|
|
646
|
+
const rule = compiledState.mustacheRules.find((r) => r.extensions.some((ext) => filePath.endsWith(ext) || filePath.includes(ext + ".")));
|
|
647
|
+
if (rule) mustacheRegex = rule.pattern;
|
|
648
|
+
}
|
|
649
|
+
this.mustacheRegex = mustacheRegex;
|
|
650
|
+
const extraHints = [
|
|
651
|
+
...options.uiObjectFields || [],
|
|
652
|
+
...options.uiSinkProperties || [],
|
|
653
|
+
...options.uiAttributes || []
|
|
654
|
+
];
|
|
655
|
+
if (extraHints.length > 0) {
|
|
656
|
+
const parts = [...compiledState.fastPathRegex.source.split("|"), ...extraHints.map((h) => h.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))];
|
|
657
|
+
this.fastPathRegex = new RegExp(parts.join("|"));
|
|
658
|
+
} else this.fastPathRegex = compiledState.fastPathRegex;
|
|
659
|
+
this.isZeroConfig = options.isZeroConfig ?? true;
|
|
660
|
+
this.boundaryStack = [{
|
|
661
|
+
id: fileBoundaryId,
|
|
662
|
+
active: true
|
|
663
|
+
}];
|
|
664
|
+
}
|
|
665
|
+
getActiveBoundary() {
|
|
666
|
+
return this.boundaryStack[this.boundaryStack.length - 1];
|
|
667
|
+
}
|
|
668
|
+
pushSuppression(comments) {
|
|
669
|
+
if (comments.ignore) this.suppressionLevel++;
|
|
670
|
+
}
|
|
671
|
+
popSuppression(comments) {
|
|
672
|
+
if (comments.ignore) this.suppressionLevel--;
|
|
673
|
+
}
|
|
674
|
+
addMessage(id, text, context, boundaryId, location, variables = [], note, sinkType, passVars) {
|
|
675
|
+
const fullId = `${boundaryId}:${id}`;
|
|
676
|
+
const existing = this.messages.get(fullId);
|
|
677
|
+
if (existing) {
|
|
678
|
+
if (context && !existing.contexts.includes(context)) existing.contexts.push(context);
|
|
679
|
+
if (sinkType && !existing.sinkTypes.includes(sinkType)) existing.sinkTypes.push(sinkType);
|
|
680
|
+
} else this.messages.set(fullId, {
|
|
681
|
+
id,
|
|
682
|
+
text,
|
|
683
|
+
contexts: context ? [context] : [],
|
|
684
|
+
boundaryId,
|
|
685
|
+
location,
|
|
686
|
+
variables,
|
|
687
|
+
note,
|
|
688
|
+
sinkTypes: sinkType ? [sinkType] : [],
|
|
689
|
+
passVars
|
|
690
|
+
});
|
|
691
|
+
this.usedKeys.add(fullId);
|
|
692
|
+
}
|
|
693
|
+
addTransform(start, end, replacement, msgId, boundaryId, argNode, originalText) {
|
|
694
|
+
this.transforms.push({
|
|
695
|
+
start,
|
|
696
|
+
end,
|
|
697
|
+
replacement,
|
|
698
|
+
msgId,
|
|
699
|
+
boundaryId: boundaryId || this.getActiveBoundary().id,
|
|
700
|
+
argNode,
|
|
701
|
+
originalText
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
addDependency(id, dynamic, bindings = []) {
|
|
705
|
+
const depId = id.startsWith(".") ? join(dirname(this.fileBoundaryId), id).replace(/\\/g, "/") : id;
|
|
706
|
+
const cleanId = /\.(tsx?|jsx?|mts|mjs|cts|cjs)$/i.test(depId) ? depId.replace(/\.[^/.]+$/, "") : depId;
|
|
707
|
+
if (!this.dependencyPaths.has(cleanId)) this.dependencyPaths.set(cleanId, dynamic);
|
|
708
|
+
else if (!dynamic) this.dependencyPaths.set(cleanId, false);
|
|
709
|
+
if (bindings.length > 0) {
|
|
710
|
+
if (!this.dependencyBindings.has(cleanId)) this.dependencyBindings.set(cleanId, /* @__PURE__ */ new Set());
|
|
711
|
+
for (const b of bindings) this.dependencyBindings.get(cleanId).add(b);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
addInternalDependency(targetBoundaryId) {
|
|
715
|
+
const current = this.getActiveBoundary().id;
|
|
716
|
+
if (current === targetBoundaryId) return;
|
|
717
|
+
if (!this.internalDeps.has(current)) this.internalDeps.set(current, /* @__PURE__ */ new Set());
|
|
718
|
+
this.internalDeps.get(current).add(targetBoundaryId);
|
|
719
|
+
}
|
|
720
|
+
addRawSink(sink) {
|
|
721
|
+
const key = `${sink.boundaryId}:${sink.start}:${sink.end}:${sink.sinkType}`;
|
|
722
|
+
if (this.seenSinks.has(key)) return;
|
|
723
|
+
this.seenSinks.add(key);
|
|
724
|
+
this.rawSinks.push(sink);
|
|
725
|
+
}
|
|
726
|
+
pushNormalizedSource(source, sources) {
|
|
727
|
+
if (source.variables?.length) {
|
|
728
|
+
let counter = 1;
|
|
729
|
+
const mapping = {}, newVariables = [];
|
|
730
|
+
let newText = source.text;
|
|
731
|
+
const existingNames = new Set(source.variables.filter((v) => !/^var\d+$/.test(v)));
|
|
732
|
+
for (const vName of source.variables) if (/^var\d+$/.test(vName)) {
|
|
733
|
+
let newName = counter === 1 ? "input" : `input${counter}`;
|
|
734
|
+
while (existingNames.has(newName)) newName = `input${++counter}`;
|
|
735
|
+
counter++;
|
|
736
|
+
mapping[vName] = newName;
|
|
737
|
+
newVariables.push(newName);
|
|
738
|
+
newText = newText.replace(new RegExp(`\\{${vName}\\}`, "g"), `{${newName}}`);
|
|
739
|
+
} else newVariables.push(vName);
|
|
740
|
+
source.text = newText;
|
|
741
|
+
source.variables = newVariables;
|
|
742
|
+
if (Object.keys(mapping).length) source.normalizedVariables = mapping;
|
|
743
|
+
}
|
|
744
|
+
sources.push(source);
|
|
745
|
+
}
|
|
746
|
+
stitchHTML(text, onFragment, initialNote, initialPassVars = {}, getOffsets) {
|
|
747
|
+
const { normalized, tagMap, offsetMap } = normalizeTags(text);
|
|
748
|
+
const tokens = normalized.split(/(<[^>]+>)/g);
|
|
749
|
+
const isPartition = (t) => {
|
|
750
|
+
if (t.startsWith("<") && t.endsWith(">")) {
|
|
751
|
+
if (t.startsWith("<!--")) return false;
|
|
752
|
+
const baseTagName = getTagName(t).replace(/\d+$/, "");
|
|
753
|
+
return !INLINE_PHRASING_TAGS.has(baseTagName);
|
|
754
|
+
}
|
|
755
|
+
return false;
|
|
756
|
+
};
|
|
757
|
+
const skipStitchTokenIndices = /* @__PURE__ */ new Set();
|
|
758
|
+
let currentSegment = [];
|
|
759
|
+
const processSegment = (indices) => {
|
|
760
|
+
if (indices.length === 0) return;
|
|
761
|
+
const segmentStr = indices.map((idx) => tokens[idx]).join("");
|
|
762
|
+
if (!hasNonWhitespaceOutsidePhrasing(segmentStr)) for (const idx of indices) {
|
|
763
|
+
const t = tokens[idx];
|
|
764
|
+
if (t.startsWith("<") && t.endsWith(">") && !t.startsWith("<!--")) skipStitchTokenIndices.add(idx);
|
|
765
|
+
}
|
|
766
|
+
else if (isSingleWrappingPhrasingTag(segmentStr)) {
|
|
767
|
+
let openIdx = -1;
|
|
768
|
+
let closeIdx = -1;
|
|
769
|
+
for (const idx of indices) {
|
|
770
|
+
const t = tokens[idx];
|
|
771
|
+
if (t.startsWith("<") && t.endsWith(">") && !t.startsWith("<!--") && !t.startsWith("</")) {
|
|
772
|
+
openIdx = idx;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
for (let i = indices.length - 1; i >= 0; i--) {
|
|
777
|
+
const idx = indices[i];
|
|
778
|
+
const t = tokens[idx];
|
|
779
|
+
if (t.startsWith("</") && t.endsWith(">")) {
|
|
780
|
+
closeIdx = idx;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (openIdx !== -1 && closeIdx !== -1) {
|
|
785
|
+
skipStitchTokenIndices.add(openIdx);
|
|
786
|
+
skipStitchTokenIndices.add(closeIdx);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
for (let i = 0; i < tokens.length; i++) if (isPartition(tokens[i])) {
|
|
791
|
+
processSegment(currentSegment);
|
|
792
|
+
currentSegment = [];
|
|
793
|
+
} else currentSegment.push(i);
|
|
794
|
+
processSegment(currentSegment);
|
|
795
|
+
let currentIdx = 0, pendingIgnore = false;
|
|
796
|
+
const ignoreStack = [];
|
|
797
|
+
let localNote = initialNote;
|
|
798
|
+
const localPassVars = { ...initialPassVars };
|
|
799
|
+
let buffer = "";
|
|
800
|
+
let bufferStartIdx = 0;
|
|
801
|
+
const flushBuffer = () => {
|
|
802
|
+
if (buffer.trim() && hasTranslatableText(buffer)) {
|
|
803
|
+
const trimmed = buffer.trim();
|
|
804
|
+
const leadingWhitespaceLen = buffer.length - buffer.trimStart().length;
|
|
805
|
+
const trailingWhitespaceLen = buffer.length - buffer.trimEnd().length;
|
|
806
|
+
const sInNorm = bufferStartIdx + leadingWhitespaceLen;
|
|
807
|
+
const eInNorm = currentIdx - trailingWhitespaceLen;
|
|
808
|
+
const sInOrig = offsetMap[sInNorm];
|
|
809
|
+
const eInOrig = offsetMap[eInNorm];
|
|
810
|
+
const offsets = getOffsets?.(sInOrig, eInOrig);
|
|
811
|
+
onFragment(trimmed, localNote, localPassVars, offsets?.start, offsets?.end, tagMap.length ? tagMap : void 0);
|
|
812
|
+
localNote = initialNote;
|
|
813
|
+
for (const k in localPassVars) if (!(k in initialPassVars)) delete localPassVars[k];
|
|
814
|
+
else localPassVars[k] = initialPassVars[k];
|
|
815
|
+
}
|
|
816
|
+
buffer = "";
|
|
817
|
+
bufferStartIdx = 0;
|
|
818
|
+
};
|
|
819
|
+
let tokenIdx = 0;
|
|
820
|
+
for (const token of tokens) {
|
|
821
|
+
if (token.startsWith("<") && token.endsWith(">")) {
|
|
822
|
+
const isClosing = token.startsWith("</"), isComment = token.startsWith("<!--"), tagName = isComment ? "" : getTagName(token);
|
|
823
|
+
const directives = parseHTMLDirectives(token);
|
|
824
|
+
if (isComment) {
|
|
825
|
+
if (directives.ignore) pendingIgnore = true;
|
|
826
|
+
if (directives.note) localNote = directives.note;
|
|
827
|
+
Object.assign(localPassVars, directives.contextVars);
|
|
828
|
+
currentIdx += token.length;
|
|
829
|
+
tokenIdx++;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
const hasIgnore = directives.ignore || pendingIgnore || ignoreStack.length > 0;
|
|
833
|
+
const baseTagName = tagName.replace(/\d+$/, "");
|
|
834
|
+
let isPhrasing = INLINE_PHRASING_TAGS.has(baseTagName);
|
|
835
|
+
if (skipStitchTokenIndices.has(tokenIdx) || hasIgnore) isPhrasing = false;
|
|
836
|
+
if (isPhrasing && !isComment) {
|
|
837
|
+
if (ignoreStack.length === 0 && !pendingIgnore) {
|
|
838
|
+
if (buffer === "") bufferStartIdx = currentIdx;
|
|
839
|
+
buffer += token;
|
|
840
|
+
}
|
|
841
|
+
} else {
|
|
842
|
+
flushBuffer();
|
|
843
|
+
if (directives.ignore) {
|
|
844
|
+
if (isComment) pendingIgnore = true;
|
|
845
|
+
else if (!isClosing && !token.endsWith("/>") && !VOID_ELEMENTS.has(tagName)) ignoreStack.push(tagName);
|
|
846
|
+
} else if (pendingIgnore && !isComment) {
|
|
847
|
+
if (!isClosing && !token.endsWith("/>") && !VOID_ELEMENTS.has(tagName)) ignoreStack.push(tagName);
|
|
848
|
+
pendingIgnore = false;
|
|
849
|
+
}
|
|
850
|
+
if (isClosing && ignoreStack.length && ignoreStack[ignoreStack.length - 1] === tagName) ignoreStack.pop();
|
|
851
|
+
if (directives.note) localNote = directives.note;
|
|
852
|
+
Object.assign(localPassVars, directives.contextVars);
|
|
853
|
+
}
|
|
854
|
+
} else if (ignoreStack.length === 0 && !pendingIgnore) {
|
|
855
|
+
if (buffer === "") bufferStartIdx = currentIdx;
|
|
856
|
+
buffer += token;
|
|
857
|
+
}
|
|
858
|
+
currentIdx += token.length;
|
|
859
|
+
tokenIdx++;
|
|
860
|
+
}
|
|
861
|
+
flushBuffer();
|
|
862
|
+
}
|
|
863
|
+
findLiteralsInExpression(node, inheritedDirectives, defaultContext) {
|
|
864
|
+
if (!node) return [];
|
|
865
|
+
const comments = parseZintlComments(node.start, this.trivias, this.code);
|
|
866
|
+
if (inheritedDirectives) {
|
|
867
|
+
if (inheritedDirectives.note) comments.note = inheritedDirectives.note;
|
|
868
|
+
if (inheritedDirectives.ignore) comments.ignore = true;
|
|
869
|
+
Object.assign(comments.contextVars, inheritedDirectives.contextVars);
|
|
870
|
+
}
|
|
871
|
+
if (this.isIgnoredFile || this.suppressionLevel > 0 || comments.ignore) return [];
|
|
872
|
+
const sources = [];
|
|
873
|
+
const isLit = (n) => n.type === "StringLiteral" || n.type === "Literal" && typeof n.value === "string";
|
|
874
|
+
if (isLit(node)) {
|
|
875
|
+
const text = node.value;
|
|
876
|
+
if (/<[^>]+>/.test(text)) this.stitchHTML(text, (trimmed, note, passVars, start, end, tagMap) => {
|
|
877
|
+
this.pushNormalizedSource({
|
|
878
|
+
node,
|
|
879
|
+
text: trimmed,
|
|
880
|
+
context: defaultContext || "Literal",
|
|
881
|
+
location: {
|
|
882
|
+
line: 0,
|
|
883
|
+
column: 0
|
|
884
|
+
},
|
|
885
|
+
variables: [],
|
|
886
|
+
note,
|
|
887
|
+
transformStart: start,
|
|
888
|
+
transformEnd: end,
|
|
889
|
+
inlineReplacement: true,
|
|
890
|
+
passVars: Object.keys(passVars || {}).length ? { ...passVars } : void 0,
|
|
891
|
+
tagMap
|
|
892
|
+
}, sources);
|
|
893
|
+
}, comments.note, comments.contextVars, (s, e) => ({
|
|
894
|
+
start: node.start + 1 + s,
|
|
895
|
+
end: node.start + 1 + e
|
|
896
|
+
}));
|
|
897
|
+
else if (node.value) this.pushNormalizedSource({
|
|
898
|
+
node,
|
|
899
|
+
text: node.value,
|
|
900
|
+
context: defaultContext || "Literal",
|
|
901
|
+
location: {
|
|
902
|
+
line: 0,
|
|
903
|
+
column: 0
|
|
904
|
+
},
|
|
905
|
+
passVars: Object.keys(comments.contextVars).length ? { ...comments.contextVars } : void 0
|
|
906
|
+
}, sources);
|
|
907
|
+
} else if (node.type === "TemplateLiteral") {
|
|
908
|
+
let text = "";
|
|
909
|
+
const variables = [], chunks = [];
|
|
910
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
911
|
+
const raw = node.quasis[i].value.raw;
|
|
912
|
+
text += raw;
|
|
913
|
+
chunks.push({
|
|
914
|
+
isVar: false,
|
|
915
|
+
text: raw,
|
|
916
|
+
sourceStart: node.quasis[i].start + 1,
|
|
917
|
+
sourceEnd: i === node.quasis.length - 1 ? node.quasis[i].end - 1 : node.quasis[i].end - 2
|
|
918
|
+
});
|
|
919
|
+
if (i < node.expressions.length) {
|
|
920
|
+
const expr = node.expressions[i];
|
|
921
|
+
let vName = "var" + i;
|
|
922
|
+
if (expr.type === "Identifier") vName = expr.name;
|
|
923
|
+
else if (expr.type === "MemberExpression") {
|
|
924
|
+
const parts = [];
|
|
925
|
+
let curr = expr;
|
|
926
|
+
while (curr && curr.type === "MemberExpression" && curr.property.type === "Identifier") {
|
|
927
|
+
parts.unshift(curr.property.name);
|
|
928
|
+
curr = curr.object;
|
|
929
|
+
}
|
|
930
|
+
if (curr && curr.type === "Identifier") {
|
|
931
|
+
parts.unshift(curr.name);
|
|
932
|
+
vName = parts.join("_");
|
|
933
|
+
} else if (parts.length > 0) vName = parts[parts.length - 1];
|
|
934
|
+
}
|
|
935
|
+
const varFragment = `{${vName}}`;
|
|
936
|
+
text += varFragment;
|
|
937
|
+
variables.push(vName);
|
|
938
|
+
chunks.push({
|
|
939
|
+
isVar: true,
|
|
940
|
+
text: varFragment,
|
|
941
|
+
sourceStart: expr.start - 2,
|
|
942
|
+
sourceEnd: expr.end + 1
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const getSourceIndex = (tIdx) => {
|
|
947
|
+
let curr = 0;
|
|
948
|
+
for (const chunk of chunks) {
|
|
949
|
+
if (tIdx === curr) return chunk.sourceStart;
|
|
950
|
+
if (tIdx > curr && tIdx < curr + chunk.text.length) {
|
|
951
|
+
if (chunk.isVar) throw new Error("Split inside variable");
|
|
952
|
+
return chunk.sourceStart + (tIdx - curr);
|
|
953
|
+
}
|
|
954
|
+
curr += chunk.text.length;
|
|
955
|
+
}
|
|
956
|
+
return chunks[chunks.length - 1].sourceEnd;
|
|
957
|
+
};
|
|
958
|
+
if (/<[^>]+>/.test(text)) this.stitchHTML(text, (trimmed, note, passVars, start, end, tagMap) => {
|
|
959
|
+
if (trimmed.replace(/{[a-zA-Z0-9_]+}/g, "").trim() || !!comments.note || Object.keys(comments.contextVars).length) this.pushNormalizedSource({
|
|
960
|
+
node,
|
|
961
|
+
text: trimmed,
|
|
962
|
+
context: defaultContext || "Template",
|
|
963
|
+
location: {
|
|
964
|
+
line: 0,
|
|
965
|
+
column: 0
|
|
966
|
+
},
|
|
967
|
+
variables: (trimmed.match(/{[a-zA-Z0-9_]+}/g) || []).map((v) => v.slice(1, -1)),
|
|
968
|
+
note,
|
|
969
|
+
transformStart: start,
|
|
970
|
+
transformEnd: end,
|
|
971
|
+
inlineReplacement: true,
|
|
972
|
+
passVars: Object.keys(passVars || {}).length ? { ...passVars } : void 0,
|
|
973
|
+
tagMap
|
|
974
|
+
}, sources);
|
|
975
|
+
}, comments.note, comments.contextVars, (s, e) => ({
|
|
976
|
+
start: getSourceIndex(s),
|
|
977
|
+
end: getSourceIndex(e)
|
|
978
|
+
}));
|
|
979
|
+
else {
|
|
980
|
+
const trimmed = text.trim();
|
|
981
|
+
if (trimmed && (trimmed.replace(/{[a-zA-Z0-9_]+}/g, "").trim() || !!comments.note || Object.keys(comments.contextVars).length)) this.pushNormalizedSource({
|
|
982
|
+
node,
|
|
983
|
+
text: trimmed,
|
|
984
|
+
context: defaultContext || "Template",
|
|
985
|
+
location: {
|
|
986
|
+
line: 0,
|
|
987
|
+
column: 0
|
|
988
|
+
},
|
|
989
|
+
variables,
|
|
990
|
+
note: comments.note,
|
|
991
|
+
passVars: Object.keys(comments.contextVars).length ? { ...comments.contextVars } : void 0
|
|
992
|
+
}, sources);
|
|
993
|
+
}
|
|
994
|
+
} else if (node.type === "ConditionalExpression") sources.push(...this.findLiteralsInExpression(node.consequent, inheritedDirectives, defaultContext), ...this.findLiteralsInExpression(node.alternate, inheritedDirectives, defaultContext));
|
|
995
|
+
else if (node.type === "BinaryExpression" && node.operator === "+") sources.push(...this.findLiteralsInExpression(node.left, inheritedDirectives, defaultContext), ...this.findLiteralsInExpression(node.right, inheritedDirectives, defaultContext));
|
|
996
|
+
else if (node.type === "LogicalExpression") sources.push(...this.findLiteralsInExpression(node.left, inheritedDirectives, defaultContext), ...this.findLiteralsInExpression(node.right, inheritedDirectives, defaultContext));
|
|
997
|
+
else if (node.type === "ObjectExpression") node.properties.forEach((prop) => {
|
|
998
|
+
if (prop.type === "Property") {
|
|
999
|
+
const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : "";
|
|
1000
|
+
if (key) this.findLiteralsInExpression(prop.value, inheritedDirectives).forEach((s) => {
|
|
1001
|
+
s.context = s.context === "Literal" ? key : `${key}.${s.context}`;
|
|
1002
|
+
sources.push(s);
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
return sources;
|
|
1007
|
+
}
|
|
1008
|
+
sha1(content) {
|
|
1009
|
+
return createHash("sha1").update(content).digest("hex");
|
|
1010
|
+
}
|
|
1011
|
+
computeBoundaryHashes() {
|
|
1012
|
+
const hashes = {}, allBIds = /* @__PURE__ */ new Set();
|
|
1013
|
+
if (this._messages) for (const msg of this._messages.values()) allBIds.add(msg.boundaryId);
|
|
1014
|
+
for (const b of this.boundaryStack) allBIds.add(b.id);
|
|
1015
|
+
if (this._anchorSites) for (const site of this._anchorSites) allBIds.add(site.boundaryId);
|
|
1016
|
+
for (const bId of allBIds) if (this.boundaryHashes[bId]) hashes[bId] = this.boundaryHashes[bId];
|
|
1017
|
+
else hashes[bId] = this.boundaryHashes[bId] = "b_" + this.sha1(bId).substring(0, 12);
|
|
1018
|
+
return hashes;
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
//#endregion
|
|
1022
|
+
//#region src/hashing.ts
|
|
1023
|
+
/**
|
|
1024
|
+
* Generates a stable ID for a message based on its text, context, and note.
|
|
1025
|
+
* Including the note ensures that identical strings with different translator
|
|
1026
|
+
* context are treated as unique entries.
|
|
1027
|
+
*/
|
|
1028
|
+
const idCache = /* @__PURE__ */ new Map();
|
|
1029
|
+
function generateMessageId(messageText, _context = "", _note = "") {
|
|
1030
|
+
const cached = idCache.get(messageText);
|
|
1031
|
+
if (cached) return cached;
|
|
1032
|
+
const id = createHash("sha1").update(messageText).digest("hex").slice(0, 8);
|
|
1033
|
+
idCache.set(messageText, id);
|
|
1034
|
+
return id;
|
|
1035
|
+
}
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/visitors/jsx.ts
|
|
1038
|
+
function getJsxTagName(element) {
|
|
1039
|
+
if (element.openingElement?.name?.type === "JSXIdentifier") return element.openingElement.name.name.toLowerCase();
|
|
1040
|
+
return "";
|
|
1041
|
+
}
|
|
1042
|
+
function isPhrasingOnly(node) {
|
|
1043
|
+
if (node.type === "JSXText") return true;
|
|
1044
|
+
if (node.type === "JSXExpressionContainer") return true;
|
|
1045
|
+
if (node.type === "JSXElement") {
|
|
1046
|
+
const tagName = getJsxTagName(node);
|
|
1047
|
+
if (!INLINE_PHRASING_TAGS.has(tagName)) return false;
|
|
1048
|
+
return (node.children || []).every(isPhrasingOnly);
|
|
1049
|
+
}
|
|
1050
|
+
if (node.type === "JSXFragment") return (node.children || []).every(isPhrasingOnly);
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
function processJsxChildren(node, ctx) {
|
|
1054
|
+
if (ctx.suppressionLevel > 0) return;
|
|
1055
|
+
const { id: boundaryId, active } = ctx.getActiveBoundary();
|
|
1056
|
+
if (!active) return;
|
|
1057
|
+
const children = node.children;
|
|
1058
|
+
if (!children?.length) return;
|
|
1059
|
+
const comments = parseZintlComments(node.start, ctx.trivias, ctx.code);
|
|
1060
|
+
for (let i = 0; i < children.length; i++) {
|
|
1061
|
+
const child = children[i];
|
|
1062
|
+
if (child.type === "JSXExpressionContainer" && child.expression.type === "JSXEmptyExpression") {
|
|
1063
|
+
const childComments = parseZintlComments(child.expression.start, ctx.trivias, ctx.code);
|
|
1064
|
+
if (childComments.ignore) for (let j = i + 1; j < children.length; j++) {
|
|
1065
|
+
const nextChild = children[j];
|
|
1066
|
+
if (nextChild.type === "JSXText" && !nextChild.value.trim()) continue;
|
|
1067
|
+
ctx.handledNodes.add(nextChild.start);
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
if (childComments.note) comments.note = childComments.note;
|
|
1071
|
+
Object.assign(comments.contextVars, childComments.contextVars);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (comments.ignore) return;
|
|
1075
|
+
const hasDirectives = !!comments.note || Object.keys(comments.contextVars).length > 0;
|
|
1076
|
+
if (!children.some((c) => c.type === "JSXText" && c.value.trim()) && !hasDirectives) return;
|
|
1077
|
+
if (children.some((c) => c.type === "JSXElement" || c.type === "JSXFragment")) {
|
|
1078
|
+
if (!children.every(isPhrasingOnly)) return;
|
|
1079
|
+
function shouldStitchChildren(childrenList) {
|
|
1080
|
+
let elementCount = 0;
|
|
1081
|
+
let hasNonWhitespaceText = false;
|
|
1082
|
+
for (const child of childrenList) {
|
|
1083
|
+
if (ctx.handledNodes.has(child) || ctx.handledNodes.has(child.start)) continue;
|
|
1084
|
+
if (child.type === "JSXElement" || child.type === "JSXFragment") elementCount++;
|
|
1085
|
+
else if (child.type === "JSXText") {
|
|
1086
|
+
if (child.value.trim().length > 0) hasNonWhitespaceText = true;
|
|
1087
|
+
} else if (child.type === "JSXExpressionContainer") {
|
|
1088
|
+
if (child.expression.type !== "JSXEmptyExpression") hasNonWhitespaceText = true;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (elementCount === 0) return false;
|
|
1092
|
+
return hasNonWhitespaceText;
|
|
1093
|
+
}
|
|
1094
|
+
if (!shouldStitchChildren(children)) return;
|
|
1095
|
+
}
|
|
1096
|
+
let text = "", variables = [], pairs = [];
|
|
1097
|
+
const exprNodes = [];
|
|
1098
|
+
const tagMap = [];
|
|
1099
|
+
const distinctOpenTags = {};
|
|
1100
|
+
function countTags(childNode) {
|
|
1101
|
+
if (ctx.handledNodes.has(childNode) || ctx.handledNodes.has(childNode.start)) return;
|
|
1102
|
+
if (childNode.type === "JSXElement") {
|
|
1103
|
+
const tagName = getJsxTagName(childNode);
|
|
1104
|
+
if (INLINE_PHRASING_TAGS.has(tagName)) {
|
|
1105
|
+
const openStart = childNode.openingElement.start;
|
|
1106
|
+
const openEnd = childNode.openingElement.end;
|
|
1107
|
+
const originalOpen = ctx.code.slice(openStart, openEnd);
|
|
1108
|
+
if (!distinctOpenTags[tagName]) distinctOpenTags[tagName] = [];
|
|
1109
|
+
if (!distinctOpenTags[tagName].includes(originalOpen)) distinctOpenTags[tagName].push(originalOpen);
|
|
1110
|
+
}
|
|
1111
|
+
(childNode.children || []).forEach(countTags);
|
|
1112
|
+
} else if (childNode.type === "JSXFragment") (childNode.children || []).forEach(countTags);
|
|
1113
|
+
}
|
|
1114
|
+
children.forEach(countTags);
|
|
1115
|
+
const activeStacks = {};
|
|
1116
|
+
function serializeNode(childNode) {
|
|
1117
|
+
if (ctx.handledNodes.has(childNode) || ctx.handledNodes.has(childNode.start)) return;
|
|
1118
|
+
if (childNode.type === "JSXText") {
|
|
1119
|
+
text += childNode.value;
|
|
1120
|
+
ctx.handledNodes.add(childNode);
|
|
1121
|
+
} else if (childNode.type === "JSXExpressionContainer") {
|
|
1122
|
+
const expr = childNode.expression;
|
|
1123
|
+
if (expr.type === "JSXEmptyExpression") return;
|
|
1124
|
+
if (expr.type === "StringLiteral" || expr.type === "Literal" && typeof expr.value === "string") {
|
|
1125
|
+
text += expr.value;
|
|
1126
|
+
ctx.handledNodes.add(childNode);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
let vName = `var${variables.length}`;
|
|
1130
|
+
if (expr.type === "Identifier") vName = expr.name;
|
|
1131
|
+
else if (expr.type === "MemberExpression") {
|
|
1132
|
+
const parts = [];
|
|
1133
|
+
let curr = expr;
|
|
1134
|
+
while (curr && curr.type === "MemberExpression" && curr.property.type === "Identifier") {
|
|
1135
|
+
parts.unshift(curr.property.name);
|
|
1136
|
+
curr = curr.object;
|
|
1137
|
+
}
|
|
1138
|
+
if (curr && curr.type === "Identifier") {
|
|
1139
|
+
parts.unshift(curr.name);
|
|
1140
|
+
vName = parts.join("_");
|
|
1141
|
+
} else if (parts.length > 0) vName = parts[parts.length - 1];
|
|
1142
|
+
}
|
|
1143
|
+
if (/^var\d+$/.test(vName)) vName = variables.length === 0 ? "input" : `input${variables.length + 1}`;
|
|
1144
|
+
text += `{${vName}}`;
|
|
1145
|
+
variables.push(vName);
|
|
1146
|
+
pairs.push(`${vName}: ${ctx.code.slice(expr.start, expr.end)}`);
|
|
1147
|
+
exprNodes.push(expr);
|
|
1148
|
+
ctx.handledNodes.add(childNode);
|
|
1149
|
+
} else if (childNode.type === "JSXElement") {
|
|
1150
|
+
const tagName = getJsxTagName(childNode);
|
|
1151
|
+
const list = distinctOpenTags[tagName] || [];
|
|
1152
|
+
const totalConfigs = list.length;
|
|
1153
|
+
const isSelfClosing = childNode.openingElement.selfClosing;
|
|
1154
|
+
let alias = tagName;
|
|
1155
|
+
const openStart = childNode.openingElement.start;
|
|
1156
|
+
const openEnd = childNode.openingElement.end;
|
|
1157
|
+
const originalOpen = ctx.code.slice(openStart, openEnd);
|
|
1158
|
+
if (totalConfigs > 1) {
|
|
1159
|
+
const idx = list.indexOf(originalOpen) + 1;
|
|
1160
|
+
if (!isSelfClosing) {
|
|
1161
|
+
if (!activeStacks[tagName]) activeStacks[tagName] = [];
|
|
1162
|
+
activeStacks[tagName].push(idx);
|
|
1163
|
+
}
|
|
1164
|
+
alias = `${tagName}${idx}`;
|
|
1165
|
+
}
|
|
1166
|
+
tagMap.push({
|
|
1167
|
+
alias,
|
|
1168
|
+
originalOpen,
|
|
1169
|
+
tagName
|
|
1170
|
+
});
|
|
1171
|
+
text += isSelfClosing ? `<${alias}/>` : `<${alias}>`;
|
|
1172
|
+
ctx.handledNodes.add(childNode);
|
|
1173
|
+
ctx.handledNodes.add(childNode.openingElement);
|
|
1174
|
+
if (childNode.closingElement) ctx.handledNodes.add(childNode.closingElement);
|
|
1175
|
+
if (!isSelfClosing) {
|
|
1176
|
+
(childNode.children || []).forEach(serializeNode);
|
|
1177
|
+
if (totalConfigs > 1) {
|
|
1178
|
+
const idx = (activeStacks[tagName] || []).pop() || 1;
|
|
1179
|
+
text += `</${tagName}${idx}>`;
|
|
1180
|
+
} else text += `</${tagName}>`;
|
|
1181
|
+
}
|
|
1182
|
+
} else if (childNode.type === "JSXFragment") {
|
|
1183
|
+
ctx.handledNodes.add(childNode);
|
|
1184
|
+
ctx.handledNodes.add(childNode.openingFragment);
|
|
1185
|
+
if (childNode.closingFragment) ctx.handledNodes.add(childNode.closingFragment);
|
|
1186
|
+
(childNode.children || []).forEach(serializeNode);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
children.forEach(serializeNode);
|
|
1190
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
1191
|
+
if (!text) return;
|
|
1192
|
+
const context = node.openingElement?.name?.type === "JSXIdentifier" ? node.openingElement.name.name : "";
|
|
1193
|
+
const id = generateMessageId(text, context, comments.note);
|
|
1194
|
+
ctx.addMessage(id, text, context, boundaryId, {
|
|
1195
|
+
line: 0,
|
|
1196
|
+
column: 0
|
|
1197
|
+
}, variables, comments.note, void 0, Object.keys(comments.contextVars).length ? comments.contextVars : void 0);
|
|
1198
|
+
const start = children[0].start, end = children[children.length - 1].end;
|
|
1199
|
+
ctx.addRawSink({
|
|
1200
|
+
text,
|
|
1201
|
+
sinkType: context,
|
|
1202
|
+
start,
|
|
1203
|
+
end,
|
|
1204
|
+
line: 0,
|
|
1205
|
+
column: 0,
|
|
1206
|
+
boundaryId,
|
|
1207
|
+
isFragment: false,
|
|
1208
|
+
requiresJsxBraces: true,
|
|
1209
|
+
note: comments.note,
|
|
1210
|
+
variables: pairs.map((pair, i) => ({
|
|
1211
|
+
name: pair.split(":")[0].trim(),
|
|
1212
|
+
originalName: pair.split(":")[0].trim(),
|
|
1213
|
+
expression: pair.split(":")[1].trim(),
|
|
1214
|
+
start: exprNodes[i]?.start ?? 0,
|
|
1215
|
+
end: exprNodes[i]?.end ?? 0
|
|
1216
|
+
})),
|
|
1217
|
+
passVars: Object.keys(comments.contextVars).length ? comments.contextVars : void 0,
|
|
1218
|
+
tagMap: tagMap.length ? tagMap : void 0
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
function createJsxVisitor(_ctx) {
|
|
1222
|
+
return {
|
|
1223
|
+
JSXElement: {
|
|
1224
|
+
enter(node, ctx, parents) {
|
|
1225
|
+
if (ctx.handledNodes.has(node) || ctx.handledNodes.has(node.start)) return;
|
|
1226
|
+
ctx.registerComponentFunction(parents);
|
|
1227
|
+
const comments = parseZintlComments(node.start, ctx.trivias, ctx.code);
|
|
1228
|
+
if (comments.ignore) {
|
|
1229
|
+
ctx.pushSuppression(comments);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
processJsxChildren(node, ctx);
|
|
1233
|
+
},
|
|
1234
|
+
exit(node, ctx) {
|
|
1235
|
+
ctx.popSuppression(parseZintlComments(node.start, ctx.trivias, ctx.code));
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
JSXFragment: {
|
|
1239
|
+
enter(node, ctx, parents) {
|
|
1240
|
+
if (ctx.handledNodes.has(node) || ctx.handledNodes.has(node.start)) return;
|
|
1241
|
+
ctx.registerComponentFunction(parents);
|
|
1242
|
+
const comments = parseZintlComments(node.start, ctx.trivias, ctx.code);
|
|
1243
|
+
if (comments.ignore) {
|
|
1244
|
+
ctx.pushSuppression(comments);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
processJsxChildren(node, ctx);
|
|
1248
|
+
},
|
|
1249
|
+
exit(node, ctx) {
|
|
1250
|
+
ctx.popSuppression(parseZintlComments(node.start, ctx.trivias, ctx.code));
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
JSXText(node, ctx, parents) {
|
|
1254
|
+
if (ctx.suppressionLevel > 0 || ctx.handledNodes.has(node)) return;
|
|
1255
|
+
ctx.registerComponentFunction(parents);
|
|
1256
|
+
const { id: boundaryId, active } = ctx.getActiveBoundary();
|
|
1257
|
+
if (!active) return;
|
|
1258
|
+
const comments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1259
|
+
if (comments.ignore) return;
|
|
1260
|
+
const text = node.value.trim();
|
|
1261
|
+
if (!text) return;
|
|
1262
|
+
const context = parents[0]?.openingElement?.name?.name || "";
|
|
1263
|
+
ctx.addMessage(generateMessageId(text, context, comments.note), text, context, boundaryId, {
|
|
1264
|
+
line: 0,
|
|
1265
|
+
column: 0
|
|
1266
|
+
}, [], comments.note, void 0, Object.keys(comments.contextVars).length ? comments.contextVars : void 0);
|
|
1267
|
+
ctx.addRawSink({
|
|
1268
|
+
text,
|
|
1269
|
+
sinkType: context,
|
|
1270
|
+
start: node.start,
|
|
1271
|
+
end: node.end,
|
|
1272
|
+
line: 0,
|
|
1273
|
+
column: 0,
|
|
1274
|
+
boundaryId,
|
|
1275
|
+
variables: [],
|
|
1276
|
+
note: comments.note,
|
|
1277
|
+
passVars: Object.keys(comments.contextVars).length ? comments.contextVars : void 0,
|
|
1278
|
+
isFragment: false,
|
|
1279
|
+
requiresJsxBraces: true
|
|
1280
|
+
});
|
|
1281
|
+
},
|
|
1282
|
+
JSXAttribute: {
|
|
1283
|
+
enter(node, ctx, parents) {
|
|
1284
|
+
if (ctx.suppressionLevel > 0 || ctx.handledNodes.has(node.start)) return;
|
|
1285
|
+
ctx.registerComponentFunction(parents);
|
|
1286
|
+
const { id: boundaryId, active } = ctx.getActiveBoundary();
|
|
1287
|
+
if (!active) return;
|
|
1288
|
+
const comments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1289
|
+
if (comments.ignore) {
|
|
1290
|
+
ctx.pushSuppression(comments);
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
const attrName = node.name.type === "JSXIdentifier" ? node.name.name : "";
|
|
1294
|
+
let isTranslatable = ctx.uiAttributes.has(attrName);
|
|
1295
|
+
if (!isTranslatable) {
|
|
1296
|
+
const parentElement = parents.slice().reverse().find((p) => p.type === "JSXOpeningElement");
|
|
1297
|
+
if (parentElement && parentElement.name && parentElement.name.type === "JSXIdentifier") {
|
|
1298
|
+
const elementName = parentElement.name.name;
|
|
1299
|
+
const scopedAttrs = ctx.jsxElementAttributes?.get(elementName);
|
|
1300
|
+
if (scopedAttrs && scopedAttrs.has(attrName)) isTranslatable = true;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (isTranslatable && (node.value?.type === "StringLiteral" || node.value?.type === "Literal")) {
|
|
1304
|
+
const text = node.value.value;
|
|
1305
|
+
if (!text || !text.trim()) return;
|
|
1306
|
+
ctx.addMessage(generateMessageId(text, attrName), text, attrName, boundaryId, {
|
|
1307
|
+
line: 0,
|
|
1308
|
+
column: 0
|
|
1309
|
+
});
|
|
1310
|
+
ctx.addRawSink({
|
|
1311
|
+
text,
|
|
1312
|
+
sinkType: attrName,
|
|
1313
|
+
start: node.value.start,
|
|
1314
|
+
end: node.value.end,
|
|
1315
|
+
line: 0,
|
|
1316
|
+
column: 0,
|
|
1317
|
+
boundaryId,
|
|
1318
|
+
variables: [],
|
|
1319
|
+
isFragment: false,
|
|
1320
|
+
requiresJsxBraces: true
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
exit(node, ctx, parents) {
|
|
1325
|
+
ctx.popSuppression(getAttachedComments(node, parents, ctx.trivias, ctx.code));
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
JSXExpressionContainer(node, ctx, parents) {
|
|
1329
|
+
if (ctx.suppressionLevel > 0 || ctx.handledNodes.has(node)) return;
|
|
1330
|
+
ctx.registerComponentFunction(parents);
|
|
1331
|
+
const parentAttr = parents[0] && parents[0].type === "JSXAttribute" ? parents[0] : null;
|
|
1332
|
+
if (parentAttr) {
|
|
1333
|
+
const attrName = parentAttr.name.type === "JSXIdentifier" ? parentAttr.name.name : "";
|
|
1334
|
+
let isTranslatable = ctx.uiAttributes.has(attrName);
|
|
1335
|
+
if (!isTranslatable) {
|
|
1336
|
+
const parentElement = parents.slice().reverse().find((p) => p.type === "JSXOpeningElement");
|
|
1337
|
+
if (parentElement && parentElement.name && parentElement.name.type === "JSXIdentifier") {
|
|
1338
|
+
const elementName = parentElement.name.name;
|
|
1339
|
+
const scopedAttrs = ctx.jsxElementAttributes?.get(elementName);
|
|
1340
|
+
if (scopedAttrs && scopedAttrs.has(attrName)) isTranslatable = true;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (!isTranslatable) return;
|
|
1344
|
+
}
|
|
1345
|
+
const { id: boundaryId, active } = ctx.getActiveBoundary();
|
|
1346
|
+
if (!active) return;
|
|
1347
|
+
const expr = node.expression;
|
|
1348
|
+
if (expr.type !== "Identifier" && !(expr.type === "MemberExpression" && expr.object.type === "Identifier" && expr.property.type === "Identifier")) {
|
|
1349
|
+
const comments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1350
|
+
if (comments.ignore) return;
|
|
1351
|
+
ctx.findLiteralsInExpression(expr, comments).forEach((source) => {
|
|
1352
|
+
if (!source.text || !source.text.trim()) return;
|
|
1353
|
+
const id = generateMessageId(source.text, source.context, source.note);
|
|
1354
|
+
ctx.addMessage(id, source.text, source.context, boundaryId, source.location, source.variables, source.note, void 0, source.passVars);
|
|
1355
|
+
const rawVars = [];
|
|
1356
|
+
if (source.variables?.length && source.node.type === "TemplateLiteral") source.node.expressions.forEach((e, i) => {
|
|
1357
|
+
const vName = e.type === "Identifier" ? e.name : "var" + i;
|
|
1358
|
+
const finalName = source.normalizedVariables && source.normalizedVariables[vName] || vName;
|
|
1359
|
+
if (source.variables.includes(finalName)) rawVars.push({
|
|
1360
|
+
name: finalName,
|
|
1361
|
+
originalName: vName,
|
|
1362
|
+
expression: ctx.code.slice(e.start, e.end),
|
|
1363
|
+
start: e.start,
|
|
1364
|
+
end: e.end
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
1367
|
+
ctx.addRawSink({
|
|
1368
|
+
text: source.text,
|
|
1369
|
+
sinkType: source.context,
|
|
1370
|
+
start: source.node.start,
|
|
1371
|
+
end: source.node.end,
|
|
1372
|
+
line: source.location.line,
|
|
1373
|
+
column: source.location.column,
|
|
1374
|
+
boundaryId,
|
|
1375
|
+
variables: rawVars,
|
|
1376
|
+
isFragment: false,
|
|
1377
|
+
note: source.note,
|
|
1378
|
+
passVars: source.passVars
|
|
1379
|
+
});
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
//#endregion
|
|
1386
|
+
//#region src/visitors/bindings.ts
|
|
1387
|
+
function resolveBoundaryId(ctx, sourcePath) {
|
|
1388
|
+
const isRelative = sourcePath.startsWith("./") || sourcePath.startsWith("../");
|
|
1389
|
+
if (!isRelative && !sourcePath.match(/\.(ts|tsx|js|jsx)$/) && sourcePath.includes(".")) return null;
|
|
1390
|
+
if (isRelative && sourcePath.match(/\.(css|svg|png|jpg|jpeg|gif|webp|woff2?|ttf)$/i)) return null;
|
|
1391
|
+
const currentDir = ctx.fileBoundaryId.includes("/") ? ctx.fileBoundaryId.substring(0, ctx.fileBoundaryId.lastIndexOf("/")) : "";
|
|
1392
|
+
let resolved = sourcePath;
|
|
1393
|
+
if (sourcePath.startsWith("./")) {
|
|
1394
|
+
const name = sourcePath.substring(2);
|
|
1395
|
+
resolved = currentDir ? `${currentDir}/${name}` : name;
|
|
1396
|
+
} else if (sourcePath.startsWith("../")) {
|
|
1397
|
+
const depth = (sourcePath.match(/\.\.\//g) || []).length;
|
|
1398
|
+
const parts = currentDir.split("/").filter(Boolean);
|
|
1399
|
+
const up = Math.max(0, parts.length - depth);
|
|
1400
|
+
const name = sourcePath.replace(/\.\.\//g, "");
|
|
1401
|
+
const base = parts.slice(0, up).join("/");
|
|
1402
|
+
resolved = base ? `${base}/${name}` : name;
|
|
1403
|
+
}
|
|
1404
|
+
return resolved.replace(/\.(tsx?|jsx?)$/, "");
|
|
1405
|
+
}
|
|
1406
|
+
function extractRawVariables(source, ctx) {
|
|
1407
|
+
if (!source.variables?.length || source.node.type !== "TemplateLiteral") return [];
|
|
1408
|
+
const node = source.node;
|
|
1409
|
+
const variables = [];
|
|
1410
|
+
node.expressions.forEach((expr, i) => {
|
|
1411
|
+
const vName = resolveExpressionName(expr, i);
|
|
1412
|
+
const finalName = source.normalizedVariables?.[vName] ?? vName;
|
|
1413
|
+
const withinRange = !source.transformStart || expr.start >= source.transformStart && source.transformEnd !== void 0 && expr.end <= source.transformEnd;
|
|
1414
|
+
if (source.variables.includes(finalName) && withinRange) variables.push({
|
|
1415
|
+
name: finalName,
|
|
1416
|
+
originalName: vName,
|
|
1417
|
+
expression: ctx.code.slice(expr.start, expr.end),
|
|
1418
|
+
start: expr.start,
|
|
1419
|
+
end: expr.end
|
|
1420
|
+
});
|
|
1421
|
+
});
|
|
1422
|
+
return variables;
|
|
1423
|
+
}
|
|
1424
|
+
/** Infer a readable variable name from a template expression node. */
|
|
1425
|
+
function resolveExpressionName(expr, index) {
|
|
1426
|
+
if (expr.type === "Identifier") return expr.name;
|
|
1427
|
+
if (expr.type === "MemberExpression") {
|
|
1428
|
+
const parts = [];
|
|
1429
|
+
let curr = expr;
|
|
1430
|
+
while (curr && curr.type === "MemberExpression" && curr.property.type === "Identifier") {
|
|
1431
|
+
parts.unshift(curr.property.name);
|
|
1432
|
+
curr = curr.object;
|
|
1433
|
+
}
|
|
1434
|
+
if (curr && curr.type === "Identifier") {
|
|
1435
|
+
parts.unshift(curr.name);
|
|
1436
|
+
return parts.join("_");
|
|
1437
|
+
} else if (parts.length > 0) return parts[parts.length - 1];
|
|
1438
|
+
}
|
|
1439
|
+
return `var${index}`;
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Process a single literal source found on a UI sink property.
|
|
1443
|
+
* Registers the message, records the raw sink, and queues the code transform.
|
|
1444
|
+
*/
|
|
1445
|
+
function processSinkSource(source, sinkType, boundaryId, parentStart, ctx) {
|
|
1446
|
+
const msgId = generateMessageId(source.text, source.context, source.note);
|
|
1447
|
+
ctx.addMessage(msgId, source.text, source.context, boundaryId, source.location, source.variables, source.note, sinkType, source.passVars);
|
|
1448
|
+
const rawVars = extractRawVariables(source, ctx);
|
|
1449
|
+
const isFragment = !!source.inlineReplacement;
|
|
1450
|
+
const requiresQuoteConversion = isFragment && source.node.type === "StringLiteral";
|
|
1451
|
+
ctx.addRawSink({
|
|
1452
|
+
text: source.text,
|
|
1453
|
+
sinkType,
|
|
1454
|
+
start: isFragment ? source.transformStart : source.node.start,
|
|
1455
|
+
end: isFragment ? source.transformEnd : source.node.end,
|
|
1456
|
+
line: source.location.line,
|
|
1457
|
+
column: source.location.column,
|
|
1458
|
+
boundaryId,
|
|
1459
|
+
variables: rawVars,
|
|
1460
|
+
note: source.note,
|
|
1461
|
+
passVars: source.passVars,
|
|
1462
|
+
isFragment,
|
|
1463
|
+
fragmentStart: isFragment ? source.transformStart : void 0,
|
|
1464
|
+
fragmentEnd: isFragment ? source.transformEnd : void 0,
|
|
1465
|
+
hostStart: isFragment ? source.node.start : void 0,
|
|
1466
|
+
hostEnd: isFragment ? source.node.end : void 0,
|
|
1467
|
+
requiresQuoteConversion,
|
|
1468
|
+
tagMap: source.tagMap
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
function createBindingVisitor(ctx) {
|
|
1472
|
+
const visitor = {
|
|
1473
|
+
ImportDeclaration(node, ctx) {
|
|
1474
|
+
if (node.source.type !== "StringLiteral" && node.source.type !== "Literal") return;
|
|
1475
|
+
const sourceVal = node.source.value;
|
|
1476
|
+
if (isRuntimeSpecifier(sourceVal, ctx.runtimePackage)) node.specifiers?.forEach((spec) => {
|
|
1477
|
+
if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") ctx.runtimeImports.push(spec.imported.name);
|
|
1478
|
+
});
|
|
1479
|
+
else if (sourceVal.startsWith(".")) {
|
|
1480
|
+
if (resolveBoundaryId(ctx, sourceVal) !== null) {
|
|
1481
|
+
const bindings = [];
|
|
1482
|
+
node.specifiers?.forEach((spec) => {
|
|
1483
|
+
if (spec.type === "ImportSpecifier") {
|
|
1484
|
+
const name = spec.imported.name || spec.imported.value;
|
|
1485
|
+
if (name) bindings.push(name);
|
|
1486
|
+
} else if (spec.type === "ImportDefaultSpecifier") bindings.push("default");
|
|
1487
|
+
});
|
|
1488
|
+
ctx.addDependency(sourceVal, false, bindings);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
ImportExpression(node, ctx) {
|
|
1493
|
+
const src = node.source;
|
|
1494
|
+
if ((src.type === "StringLiteral" || src.type === "Literal") && src.value.startsWith(".")) {
|
|
1495
|
+
const resolved = resolveBoundaryId(ctx, src.value);
|
|
1496
|
+
if (resolved !== null && !ctx.dependencyPaths.has(resolved)) ctx.dependencyPaths.set(resolved, true);
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
VariableDeclaration: {
|
|
1500
|
+
enter(node, ctx, parents) {
|
|
1501
|
+
if (getAttachedComments(node, parents, ctx.trivias, ctx.code).ignore) ctx.suppressionLevel++;
|
|
1502
|
+
},
|
|
1503
|
+
exit(node, ctx, parents) {
|
|
1504
|
+
if (getAttachedComments(node, parents, ctx.trivias, ctx.code).ignore) ctx.suppressionLevel--;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
if (ctx.hasDomSinks) visitor.AssignmentExpression = function(node, ctx, parents) {
|
|
1509
|
+
if (ctx.suppressionLevel > 0) return;
|
|
1510
|
+
const { id: boundaryId, active } = ctx.getActiveBoundary();
|
|
1511
|
+
if (!active || node.left.type !== "MemberExpression") return;
|
|
1512
|
+
const prop = node.left.property.type === "Identifier" ? node.left.property.name : "";
|
|
1513
|
+
if (!ctx.uiSinkProperties.includes(prop)) return;
|
|
1514
|
+
const stmtComments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1515
|
+
ctx.findLiteralsInExpression(node.right, stmtComments, prop).forEach((source) => processSinkSource(source, prop, boundaryId, parents[0]?.start ?? node.start, ctx));
|
|
1516
|
+
};
|
|
1517
|
+
if (ctx.uiObjectFields.size > 0) visitor.Property = function(node, ctx, parents) {
|
|
1518
|
+
if (ctx.suppressionLevel > 0) return;
|
|
1519
|
+
if (ctx.handledNodes.has(node.start)) return;
|
|
1520
|
+
const { id: boundaryId, active } = ctx.getActiveBoundary();
|
|
1521
|
+
if (!active) return;
|
|
1522
|
+
if (getAttachedComments(node, parents, ctx.trivias, ctx.code).ignore) return;
|
|
1523
|
+
const keyName = node.key.type === "Identifier" ? node.key.name : node.key.type === "StringLiteral" || node.key.type === "Literal" ? node.key.value : "";
|
|
1524
|
+
if (!ctx.uiObjectFields.has(keyName)) return;
|
|
1525
|
+
const stmtComments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1526
|
+
ctx.findLiteralsInExpression(node.value, stmtComments, keyName).forEach((source) => processSinkSource(source, keyName, boundaryId, parents[0]?.start ?? node.start, ctx));
|
|
1527
|
+
};
|
|
1528
|
+
return visitor;
|
|
1529
|
+
}
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region src/walker.ts
|
|
1532
|
+
function walk(node, visitors, ctx, parents = []) {
|
|
1533
|
+
if (!node || typeof node !== "object") return;
|
|
1534
|
+
if (ctx.isIgnoredFile) return;
|
|
1535
|
+
const type = node.type;
|
|
1536
|
+
if (ctx.handledNodes.has(node.start)) return;
|
|
1537
|
+
const visitor = visitors[type];
|
|
1538
|
+
if (visitor) try {
|
|
1539
|
+
if (typeof visitor === "function") visitor(node, ctx, parents);
|
|
1540
|
+
else if (visitor.enter) visitor.enter(node, ctx, parents);
|
|
1541
|
+
} catch (e) {
|
|
1542
|
+
ctx.logger.error(`ERROR in visitor [${type}] at offset ${node.start}:`, e);
|
|
1543
|
+
throw e;
|
|
1544
|
+
}
|
|
1545
|
+
const nextParents = [node, ...parents];
|
|
1546
|
+
const type_ = node.type;
|
|
1547
|
+
if (type_ === "Program") for (const stmt of node.body) walk(stmt, visitors, ctx, nextParents);
|
|
1548
|
+
else if (type_ === "JSXElement") {
|
|
1549
|
+
walk(node.openingElement, visitors, ctx, nextParents);
|
|
1550
|
+
for (const child of node.children) walk(child, visitors, ctx, nextParents);
|
|
1551
|
+
if (node.closingElement) walk(node.closingElement, visitors, ctx, nextParents);
|
|
1552
|
+
} else if (type_ === "JSXOpeningElement") {
|
|
1553
|
+
walk(node.name, visitors, ctx, nextParents);
|
|
1554
|
+
for (const attr of node.attributes) walk(attr, visitors, ctx, nextParents);
|
|
1555
|
+
} else if (type_ === "JSXAttribute") {
|
|
1556
|
+
if (node.value) walk(node.value, visitors, ctx, nextParents);
|
|
1557
|
+
} else if (type_ === "JSXExpressionContainer") walk(node.expression, visitors, ctx, nextParents);
|
|
1558
|
+
else if (type_ === "FunctionDeclaration" || type_ === "FunctionExpression" || type_ === "ArrowFunctionExpression") {
|
|
1559
|
+
if (node.id) walk(node.id, visitors, ctx, nextParents);
|
|
1560
|
+
const params = node.params?.items || node.params || [];
|
|
1561
|
+
for (const param of params) walk(param, visitors, ctx, nextParents);
|
|
1562
|
+
walk(node.body, visitors, ctx, nextParents);
|
|
1563
|
+
} else if (type_ === "BlockStatement") for (const stmt of node.body) walk(stmt, visitors, ctx, nextParents);
|
|
1564
|
+
else if (type_ === "CallExpression") {
|
|
1565
|
+
walk(node.callee, visitors, ctx, nextParents);
|
|
1566
|
+
for (const arg of node.arguments) walk(arg, visitors, ctx, nextParents);
|
|
1567
|
+
} else if (type_ === "ExpressionStatement") walk(node.expression, visitors, ctx, nextParents);
|
|
1568
|
+
else if (type_ === "VariableDeclaration") for (const decl of node.declarations) walk(decl, visitors, ctx, nextParents);
|
|
1569
|
+
else if (type_ === "VariableDeclarator") {
|
|
1570
|
+
walk(node.id, visitors, ctx, nextParents);
|
|
1571
|
+
if (node.init) walk(node.init, visitors, ctx, nextParents);
|
|
1572
|
+
} else if (type_ === "ReturnStatement") {
|
|
1573
|
+
if (node.argument) walk(node.argument, visitors, ctx, nextParents);
|
|
1574
|
+
} else for (const key in node) {
|
|
1575
|
+
if (key === "parent") continue;
|
|
1576
|
+
const child = node[key];
|
|
1577
|
+
if (Array.isArray(child)) {
|
|
1578
|
+
for (const item of child) if (item && typeof item === "object" && item.type) walk(item, visitors, ctx, nextParents);
|
|
1579
|
+
} else if (child && typeof child === "object" && child.type) walk(child, visitors, ctx, nextParents);
|
|
1580
|
+
}
|
|
1581
|
+
if (visitor && typeof visitor !== "function" && visitor.exit) visitor.exit(node, ctx, parents);
|
|
1582
|
+
}
|
|
1583
|
+
//#endregion
|
|
1584
|
+
//#region src/visitors/program.ts
|
|
1585
|
+
const GLOBALS = /* @__PURE__ */ new Set([
|
|
1586
|
+
"window",
|
|
1587
|
+
"document",
|
|
1588
|
+
"location",
|
|
1589
|
+
"URLSearchParams",
|
|
1590
|
+
"localStorage",
|
|
1591
|
+
"navigator",
|
|
1592
|
+
"history",
|
|
1593
|
+
"Intl",
|
|
1594
|
+
"console"
|
|
1595
|
+
]);
|
|
1596
|
+
function checkSuppression(node, ctx, parents, isInitStart) {
|
|
1597
|
+
if (ctx.suppressionRules.length === 0) return false;
|
|
1598
|
+
if (!node.id || node.id.type !== "Identifier") return false;
|
|
1599
|
+
const name = node.id.name;
|
|
1600
|
+
for (const rule of ctx.suppressionRules) {
|
|
1601
|
+
if (!rule.match.types.includes(node.type)) continue;
|
|
1602
|
+
if (!rule.match.names.includes(name)) continue;
|
|
1603
|
+
if (rule.match.isTopLevel && !isTopLevelDecl(parents)) continue;
|
|
1604
|
+
if (rule.bypassIf === "hasAnchor") {
|
|
1605
|
+
const initStart = isInitStart !== void 0 ? isInitStart : node.start;
|
|
1606
|
+
if (ctx.hasTopLevelAnchor || (ctx.hasZintlMacro || ctx.hasZintlMarker) && (ctx.scopeBoundaries.has(node.start) || ctx.scopeBoundaries.has(initStart))) continue;
|
|
1607
|
+
}
|
|
1608
|
+
return true;
|
|
1609
|
+
}
|
|
1610
|
+
return false;
|
|
1611
|
+
}
|
|
1612
|
+
function isTopLevelDecl(parents) {
|
|
1613
|
+
for (const p of parents) if (p.type === "FunctionDeclaration" || p.type === "FunctionExpression" || p.type === "ArrowFunctionExpression" || p.type === "MethodDefinition" || p.type === "ClassBody") return false;
|
|
1614
|
+
return true;
|
|
1615
|
+
}
|
|
1616
|
+
const SCOPE_TYPES = /* @__PURE__ */ new Set([
|
|
1617
|
+
"Program",
|
|
1618
|
+
"FunctionDeclaration",
|
|
1619
|
+
"FunctionExpression",
|
|
1620
|
+
"ArrowFunctionExpression",
|
|
1621
|
+
"BlockStatement"
|
|
1622
|
+
]);
|
|
1623
|
+
function createProgramVisitor(_ctx) {
|
|
1624
|
+
function getAnchorInfo(node, parents) {
|
|
1625
|
+
let isAnchor = false, isTopLevel = false, current = node, idx = 0;
|
|
1626
|
+
while (idx < parents.length) {
|
|
1627
|
+
const parent = parents[idx];
|
|
1628
|
+
if (parent.type === "AwaitExpression" || parent.type === "UnaryExpression" && parent.operator === "void" || parent.type === "MemberExpression" && !parent.computed && parent.property.type === "Identifier" && parent.property.name === "then" || parent.type === "CallExpression" && parent.callee === current) {
|
|
1629
|
+
current = parent;
|
|
1630
|
+
idx++;
|
|
1631
|
+
} else break;
|
|
1632
|
+
}
|
|
1633
|
+
const immediateParent = parents[idx];
|
|
1634
|
+
let statementRange;
|
|
1635
|
+
if (immediateParent) {
|
|
1636
|
+
if ([
|
|
1637
|
+
"ExpressionStatement",
|
|
1638
|
+
"ArrowFunctionExpression",
|
|
1639
|
+
"ReturnStatement",
|
|
1640
|
+
"BlockStatement",
|
|
1641
|
+
"VariableDeclarator"
|
|
1642
|
+
].includes(immediateParent.type)) {
|
|
1643
|
+
isAnchor = true;
|
|
1644
|
+
if (immediateParent.type === "ExpressionStatement") statementRange = {
|
|
1645
|
+
start: immediateParent.start,
|
|
1646
|
+
end: immediateParent.end
|
|
1647
|
+
};
|
|
1648
|
+
else statementRange = {
|
|
1649
|
+
start: current.start,
|
|
1650
|
+
end: current.end
|
|
1651
|
+
};
|
|
1652
|
+
let stmtIdx = idx;
|
|
1653
|
+
while (stmtIdx < parents.length) {
|
|
1654
|
+
const p = parents[stmtIdx];
|
|
1655
|
+
if (p.type === "Program") {
|
|
1656
|
+
isTopLevel = true;
|
|
1657
|
+
break;
|
|
1658
|
+
}
|
|
1659
|
+
if (p.type === "BlockStatement") break;
|
|
1660
|
+
stmtIdx++;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return {
|
|
1665
|
+
isAnchor,
|
|
1666
|
+
isTopLevel,
|
|
1667
|
+
statementRange
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
const scopeCache = /* @__PURE__ */ new Map();
|
|
1671
|
+
const getScopeDecls = (scope) => {
|
|
1672
|
+
if (scopeCache.has(scope)) return scopeCache.get(scope);
|
|
1673
|
+
const decls = /* @__PURE__ */ new Map();
|
|
1674
|
+
const body = scope.body?.type === "BlockStatement" ? scope.body.body : scope.body;
|
|
1675
|
+
if (Array.isArray(body)) {
|
|
1676
|
+
for (const stmt of body) if (stmt.type === "VariableDeclaration") {
|
|
1677
|
+
for (const decl of stmt.declarations) if (decl.id.type === "Identifier" && decl.init) decls.set(decl.id.name, {
|
|
1678
|
+
stmt,
|
|
1679
|
+
decl
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
scopeCache.set(scope, decls);
|
|
1684
|
+
return decls;
|
|
1685
|
+
};
|
|
1686
|
+
function traceIdentifier(name, parents, seen = /* @__PURE__ */ new Set()) {
|
|
1687
|
+
if (seen.has(name) || GLOBALS.has(name)) return void 0;
|
|
1688
|
+
seen.add(name);
|
|
1689
|
+
for (let i = 0; i < parents.length; i++) {
|
|
1690
|
+
const p = parents[i];
|
|
1691
|
+
if (!SCOPE_TYPES.has(p.type)) continue;
|
|
1692
|
+
const entry = getScopeDecls(p).get(name);
|
|
1693
|
+
if (entry) {
|
|
1694
|
+
const { stmt, decl } = entry;
|
|
1695
|
+
let code = _ctx.code.slice(stmt.start, stmt.end);
|
|
1696
|
+
if (!code.endsWith(";")) code += ";";
|
|
1697
|
+
const deps = [];
|
|
1698
|
+
const extractIds = (n) => {
|
|
1699
|
+
if (!n || typeof n !== "object") return;
|
|
1700
|
+
if (n.type === "Identifier") deps.push(n.name);
|
|
1701
|
+
for (const key in n) {
|
|
1702
|
+
if (n.type === "MemberExpression" && key === "property" && !n.computed) continue;
|
|
1703
|
+
const val = n[key];
|
|
1704
|
+
if (val && typeof val === "object") if (Array.isArray(val)) val.forEach(extractIds);
|
|
1705
|
+
else extractIds(val);
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
extractIds(decl.init);
|
|
1709
|
+
const results = [];
|
|
1710
|
+
for (const dep of deps) {
|
|
1711
|
+
const depCode = traceIdentifier(dep, parents.slice(i), seen);
|
|
1712
|
+
if (depCode) results.push(depCode);
|
|
1713
|
+
}
|
|
1714
|
+
results.push(code);
|
|
1715
|
+
return results.join("\n");
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return {
|
|
1720
|
+
Program: { enter(node, ctx) {
|
|
1721
|
+
if (ctx.isZeroConfig) ctx.boundaryStack[0].active = true;
|
|
1722
|
+
const hasMaybeUI = ctx.fastPathRegex.test(ctx.code);
|
|
1723
|
+
const hasDynamicImport = ctx.code.includes("import(");
|
|
1724
|
+
if (!hasMaybeUI && !hasDynamicImport) {
|
|
1725
|
+
for (const stmt of node.body) {
|
|
1726
|
+
const register = (fn, name) => {
|
|
1727
|
+
if (fn && [
|
|
1728
|
+
"FunctionDeclaration",
|
|
1729
|
+
"ArrowFunctionExpression",
|
|
1730
|
+
"FunctionExpression"
|
|
1731
|
+
].includes(fn.type)) {
|
|
1732
|
+
if (ctx.isZeroConfig) {
|
|
1733
|
+
const bId = `${ctx.fileBoundaryId}:${name}`;
|
|
1734
|
+
ctx.scopeBoundaries.set(fn.start, bId);
|
|
1735
|
+
ctx.localBoundaries.set(name, bId);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
if (stmt.type === "ImportDeclaration") {
|
|
1740
|
+
if (["StringLiteral", "Literal"].includes(stmt.source.type)) {
|
|
1741
|
+
const sourceVal = stmt.source.value, bindings = [];
|
|
1742
|
+
if (stmt.specifiers) {
|
|
1743
|
+
for (const spec of stmt.specifiers) if (spec.type === "ImportSpecifier") bindings.push(spec.imported.name || spec.imported.value);
|
|
1744
|
+
else if (spec.type === "ImportDefaultSpecifier") bindings.push("default");
|
|
1745
|
+
}
|
|
1746
|
+
if (isRuntimeSpecifier(sourceVal, ctx.runtimePackage)) {
|
|
1747
|
+
ctx.zintlImportGroup = {
|
|
1748
|
+
start: stmt.start,
|
|
1749
|
+
end: stmt.end,
|
|
1750
|
+
source: sourceVal
|
|
1751
|
+
};
|
|
1752
|
+
for (const name of bindings) ctx.runtimeImports.push(name);
|
|
1753
|
+
if (!stmt.specifiers?.length) {
|
|
1754
|
+
ctx.mode = "entry";
|
|
1755
|
+
ctx.hasZintlMarker = true;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
ctx.addDependency(sourceVal, false, bindings);
|
|
1759
|
+
}
|
|
1760
|
+
} else if (stmt.type === "FunctionDeclaration" && stmt.id) register(stmt, stmt.id.name);
|
|
1761
|
+
else if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
1762
|
+
const decl = stmt.declaration;
|
|
1763
|
+
if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
1764
|
+
register(decl, decl.id.name);
|
|
1765
|
+
ctx.exportedBoundaries.set(decl.id.name, `${ctx.fileBoundaryId}:${decl.id.name}`);
|
|
1766
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
1767
|
+
for (const d of decl.declarations) if (d.id.type === "Identifier" && d.init) {
|
|
1768
|
+
register(d.init, d.id.name);
|
|
1769
|
+
ctx.exportedBoundaries.set(d.id.name, `${ctx.fileBoundaryId}:${d.id.name}`);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
} else if (stmt.type === "ExportDefaultDeclaration") {
|
|
1773
|
+
register(stmt.declaration, "default");
|
|
1774
|
+
ctx.exportedBoundaries.set("default", `${ctx.fileBoundaryId}:default`);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
const topLevelAnchors = [], functionalAnchors = [];
|
|
1780
|
+
walk(node, { CallExpression(child, _ctx, parents) {
|
|
1781
|
+
const isZintl = child.callee.type === "Identifier" && child.callee.name === "zintl";
|
|
1782
|
+
const isLoader = child.callee.type === "Identifier" && child.callee.name === "loadI18nInstance";
|
|
1783
|
+
if (isZintl || isLoader) {
|
|
1784
|
+
if (isZintl) _ctx.hasZintlMacro = true;
|
|
1785
|
+
const anchorInfo = getAnchorInfo(child, parents);
|
|
1786
|
+
if (anchorInfo.isAnchor) if (anchorInfo.isTopLevel) topLevelAnchors.push(child);
|
|
1787
|
+
else {
|
|
1788
|
+
const fnParent = parents.find((p) => [
|
|
1789
|
+
"FunctionDeclaration",
|
|
1790
|
+
"FunctionExpression",
|
|
1791
|
+
"ArrowFunctionExpression",
|
|
1792
|
+
"MethodDefinition"
|
|
1793
|
+
].includes(p.type));
|
|
1794
|
+
if (fnParent) functionalAnchors.push({
|
|
1795
|
+
node: child,
|
|
1796
|
+
fnParent
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
} }, ctx);
|
|
1801
|
+
for (const { node: anchorNode, fnParent } of functionalAnchors) {
|
|
1802
|
+
let fnId = fnParent.type === "FunctionDeclaration" && fnParent.id?.name || fnParent.type === "MethodDefinition" && fnParent.key.type === "Identifier" && fnParent.key.name || `f_${anchorNode.start}`;
|
|
1803
|
+
const bId = `${ctx.fileBoundaryId}:${fnId}`;
|
|
1804
|
+
ctx.scopeBoundaries.set(fnParent.start, bId);
|
|
1805
|
+
if (fnParent.type === "FunctionDeclaration" && fnParent.id) ctx.localBoundaries.set(fnParent.id.name, bId);
|
|
1806
|
+
}
|
|
1807
|
+
const assignBoundary = (fn, name) => {
|
|
1808
|
+
if (!fn || ![
|
|
1809
|
+
"FunctionDeclaration",
|
|
1810
|
+
"ArrowFunctionExpression",
|
|
1811
|
+
"FunctionExpression"
|
|
1812
|
+
].includes(fn.type)) return;
|
|
1813
|
+
if (ctx.scopeBoundaries.has(fn.start)) return;
|
|
1814
|
+
const bId = `${ctx.fileBoundaryId}:${name}`;
|
|
1815
|
+
ctx.scopeBoundaries.set(fn.start, bId);
|
|
1816
|
+
ctx.localBoundaries.set(name, bId);
|
|
1817
|
+
};
|
|
1818
|
+
for (const stmt of node.body) if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
1819
|
+
if (stmt.declaration.type === "FunctionDeclaration" && stmt.declaration.id) assignBoundary(stmt.declaration, stmt.declaration.id.name);
|
|
1820
|
+
else if (stmt.declaration.type === "VariableDeclaration") {
|
|
1821
|
+
for (const decl of stmt.declaration.declarations) if (decl.id.type === "Identifier" && decl.init) assignBoundary(decl.init, decl.id.name);
|
|
1822
|
+
}
|
|
1823
|
+
} else if (stmt.type === "ExportDefaultDeclaration") assignBoundary(stmt.declaration, "default");
|
|
1824
|
+
else if (ctx.isZeroConfig) {
|
|
1825
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id) assignBoundary(stmt, stmt.id.name);
|
|
1826
|
+
else if (stmt.type === "VariableDeclaration") {
|
|
1827
|
+
for (const decl of stmt.declarations) if (decl.id.type === "Identifier" && decl.init) assignBoundary(decl.init, decl.id.name);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
if (topLevelAnchors.length > 0) {
|
|
1831
|
+
ctx.hasTopLevelAnchor = true;
|
|
1832
|
+
ctx.scopeBoundaries.set(node.start, ctx.fileBoundaryId);
|
|
1833
|
+
}
|
|
1834
|
+
for (const stmt of node.body) if (stmt.type === "ExportNamedDeclaration") {
|
|
1835
|
+
if (stmt.declaration) {
|
|
1836
|
+
if (stmt.declaration.type === "FunctionDeclaration" && stmt.declaration.id) {
|
|
1837
|
+
const name = stmt.declaration.id.name;
|
|
1838
|
+
const bId = ctx.scopeBoundaries.get(stmt.declaration.start) || ctx.localBoundaries.get(name) || ctx.fileBoundaryId;
|
|
1839
|
+
ctx.exportedBoundaries.set(name, bId);
|
|
1840
|
+
} else if (stmt.declaration.type === "VariableDeclaration") {
|
|
1841
|
+
for (const decl of stmt.declaration.declarations) if (decl.id.type === "Identifier") {
|
|
1842
|
+
const name = decl.id.name;
|
|
1843
|
+
const bId = decl.init && ctx.scopeBoundaries.get(decl.init.start) || ctx.localBoundaries.get(name) || ctx.fileBoundaryId;
|
|
1844
|
+
ctx.exportedBoundaries.set(name, bId);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
} else if (stmt.specifiers) for (const spec of stmt.specifiers) {
|
|
1848
|
+
const localName = spec.local.name;
|
|
1849
|
+
const exportedName = spec.exported.name;
|
|
1850
|
+
const bId = ctx.localBoundaries.get(localName) || ctx.fileBoundaryId;
|
|
1851
|
+
ctx.exportedBoundaries.set(exportedName, bId);
|
|
1852
|
+
}
|
|
1853
|
+
} else if (stmt.type === "ExportDefaultDeclaration") {
|
|
1854
|
+
let bId = ctx.fileBoundaryId;
|
|
1855
|
+
const decl = stmt.declaration;
|
|
1856
|
+
if (decl.type === "Identifier") bId = ctx.localBoundaries.get(decl.name) || ctx.fileBoundaryId;
|
|
1857
|
+
else if (decl.type === "FunctionDeclaration" && decl.id) bId = ctx.scopeBoundaries.get(decl.start) || ctx.localBoundaries.get(decl.id.name) || ctx.fileBoundaryId;
|
|
1858
|
+
else bId = ctx.scopeBoundaries.get(decl.start) || ctx.fileBoundaryId;
|
|
1859
|
+
ctx.exportedBoundaries.set("default", bId);
|
|
1860
|
+
}
|
|
1861
|
+
} },
|
|
1862
|
+
FunctionDeclaration: {
|
|
1863
|
+
enter(node, ctx, parents) {
|
|
1864
|
+
const comments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1865
|
+
if (comments.ignore) ctx.pushSuppression(comments);
|
|
1866
|
+
if (checkSuppression(node, ctx, parents)) {
|
|
1867
|
+
ctx.suppressionLevel++;
|
|
1868
|
+
node.__zintl_suppressed = true;
|
|
1869
|
+
}
|
|
1870
|
+
const pb = ctx.getActiveBoundary(), id = ctx.scopeBoundaries.get(node.start);
|
|
1871
|
+
ctx.boundaryStack.push({
|
|
1872
|
+
id: id || pb.id,
|
|
1873
|
+
active: id ? true : pb.active
|
|
1874
|
+
});
|
|
1875
|
+
},
|
|
1876
|
+
exit(node, ctx, parents) {
|
|
1877
|
+
ctx.popSuppression(getAttachedComments(node, parents, ctx.trivias, ctx.code));
|
|
1878
|
+
if (node.__zintl_suppressed) ctx.suppressionLevel--;
|
|
1879
|
+
ctx.boundaryStack.pop();
|
|
1880
|
+
}
|
|
1881
|
+
},
|
|
1882
|
+
FunctionExpression: {
|
|
1883
|
+
enter(node, ctx, parents) {
|
|
1884
|
+
const comments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1885
|
+
if (comments.ignore) ctx.pushSuppression(comments);
|
|
1886
|
+
const pb = ctx.getActiveBoundary(), id = ctx.scopeBoundaries.get(node.start);
|
|
1887
|
+
ctx.boundaryStack.push({
|
|
1888
|
+
id: id || pb.id,
|
|
1889
|
+
active: id ? true : pb.active
|
|
1890
|
+
});
|
|
1891
|
+
},
|
|
1892
|
+
exit(node, ctx, parents) {
|
|
1893
|
+
ctx.popSuppression(getAttachedComments(node, parents, ctx.trivias, ctx.code));
|
|
1894
|
+
ctx.boundaryStack.pop();
|
|
1895
|
+
}
|
|
1896
|
+
},
|
|
1897
|
+
ArrowFunctionExpression: {
|
|
1898
|
+
enter(node, ctx, parents) {
|
|
1899
|
+
const comments = getAttachedComments(node, parents, ctx.trivias, ctx.code);
|
|
1900
|
+
if (comments.ignore) ctx.pushSuppression(comments);
|
|
1901
|
+
const pb = ctx.getActiveBoundary(), id = ctx.scopeBoundaries.get(node.start);
|
|
1902
|
+
ctx.boundaryStack.push({
|
|
1903
|
+
id: id || pb.id,
|
|
1904
|
+
active: id ? true : pb.active
|
|
1905
|
+
});
|
|
1906
|
+
},
|
|
1907
|
+
exit(node, ctx, parents) {
|
|
1908
|
+
ctx.popSuppression(getAttachedComments(node, parents, ctx.trivias, ctx.code));
|
|
1909
|
+
ctx.boundaryStack.pop();
|
|
1910
|
+
}
|
|
1911
|
+
},
|
|
1912
|
+
VariableDeclarator: {
|
|
1913
|
+
enter(node, ctx, parents) {
|
|
1914
|
+
if (checkSuppression(node, ctx, parents, node.init?.start)) {
|
|
1915
|
+
ctx.suppressionLevel++;
|
|
1916
|
+
node.__zintl_suppressed = true;
|
|
1917
|
+
}
|
|
1918
|
+
},
|
|
1919
|
+
exit(node, ctx, _parents) {
|
|
1920
|
+
if (node.__zintl_suppressed) ctx.suppressionLevel--;
|
|
1921
|
+
}
|
|
1922
|
+
},
|
|
1923
|
+
CallExpression(node, ctx, parents) {
|
|
1924
|
+
if (ctx.suppressionLevel > 0) return;
|
|
1925
|
+
if (node.callee.type === "Identifier") {
|
|
1926
|
+
const lbId = ctx.localBoundaries.get(node.callee.name);
|
|
1927
|
+
if (lbId) ctx.addInternalDependency(lbId);
|
|
1928
|
+
}
|
|
1929
|
+
const isZintl = node.callee.type === "Identifier" && node.callee.name === "zintl";
|
|
1930
|
+
const isLoader = node.callee.type === "Identifier" && node.callee.name === "loadI18nInstance";
|
|
1931
|
+
if (isZintl || isLoader) {
|
|
1932
|
+
const anchorInfo = getAnchorInfo(node, parents);
|
|
1933
|
+
if (!anchorInfo.isAnchor) return;
|
|
1934
|
+
const { isTopLevel } = anchorInfo;
|
|
1935
|
+
if (isLoader && node.arguments[0]?.type === "ObjectExpression" && node.arguments[0].properties.some((p) => p.key.name === "loaders")) return;
|
|
1936
|
+
if (isZintl) ctx.hasZintlMacro = true;
|
|
1937
|
+
if (isTopLevel) ctx.mode = "entry";
|
|
1938
|
+
let originalArgs = "", argType = "expression";
|
|
1939
|
+
if (node.arguments[0]) {
|
|
1940
|
+
originalArgs = ctx.code.slice(node.arguments[0].start, node.arguments[0].end);
|
|
1941
|
+
if (["StringLiteral", "Literal"].includes(node.arguments[0].type)) argType = "literal";
|
|
1942
|
+
}
|
|
1943
|
+
ctx.anchorSites.push({
|
|
1944
|
+
start: node.start,
|
|
1945
|
+
end: node.end,
|
|
1946
|
+
scope: isTopLevel ? "module" : "function",
|
|
1947
|
+
boundaryId: ctx.getActiveBoundary().id,
|
|
1948
|
+
originalArgs,
|
|
1949
|
+
argType,
|
|
1950
|
+
isTopLevel,
|
|
1951
|
+
originalName: node.callee.name,
|
|
1952
|
+
statementRange: anchorInfo.statementRange,
|
|
1953
|
+
detectionCode: (() => {
|
|
1954
|
+
if (argType !== "expression" || !node.arguments[0]) return void 0;
|
|
1955
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1956
|
+
const collectIds = (n) => {
|
|
1957
|
+
if (!n || typeof n !== "object") return;
|
|
1958
|
+
if (n.type === "Identifier") ids.add(n.name);
|
|
1959
|
+
for (const k in n) {
|
|
1960
|
+
if (n.type === "MemberExpression" && k === "property" && !n.computed) continue;
|
|
1961
|
+
const v = n[k];
|
|
1962
|
+
if (Array.isArray(v)) v.forEach(collectIds);
|
|
1963
|
+
else if (v && typeof v === "object") collectIds(v);
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
collectIds(node.arguments[0]);
|
|
1967
|
+
const results = /* @__PURE__ */ new Set();
|
|
1968
|
+
for (const id of ids) {
|
|
1969
|
+
const code = traceIdentifier(id, parents);
|
|
1970
|
+
if (code) results.add(code);
|
|
1971
|
+
}
|
|
1972
|
+
return results.size > 0 ? Array.from(results).join("\n") : void 0;
|
|
1973
|
+
})()
|
|
1974
|
+
});
|
|
1975
|
+
ctx.getActiveBoundary().active = true;
|
|
1976
|
+
} else if (node.callee.type === "Identifier" && node.callee.name === "t") {
|
|
1977
|
+
ctx.registerComponentFunction(parents);
|
|
1978
|
+
if (getAttachedComments(node, parents, ctx.trivias, ctx.code).ignore) return;
|
|
1979
|
+
if (node.arguments[0] && ["StringLiteral", "Literal"].includes(node.arguments[0].type)) {
|
|
1980
|
+
const text = node.arguments[0].value;
|
|
1981
|
+
ctx.usedKeys.add(generateMessageId(text, "Manual"));
|
|
1982
|
+
ctx.rawManualTranslations.push({
|
|
1983
|
+
start: node.start,
|
|
1984
|
+
end: node.end,
|
|
1985
|
+
key: text,
|
|
1986
|
+
line: node.start,
|
|
1987
|
+
column: node.start,
|
|
1988
|
+
boundaryId: ctx.getActiveBoundary().id,
|
|
1989
|
+
paramsSource: node.arguments.length > 1 ? ctx.code.slice(node.arguments[1].start, node.arguments[node.arguments.length - 1].end) : void 0
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
},
|
|
1994
|
+
ImportExpression(node, ctx) {
|
|
1995
|
+
if (node.source && ["StringLiteral", "Literal"].includes(node.source.type)) ctx.addDependency(node.source.value, true);
|
|
1996
|
+
},
|
|
1997
|
+
ImportDeclaration(node, ctx) {
|
|
1998
|
+
if (["StringLiteral", "Literal"].includes(node.source.type)) {
|
|
1999
|
+
const sourceVal = node.source.value, bindings = [];
|
|
2000
|
+
if (node.specifiers) {
|
|
2001
|
+
for (const spec of node.specifiers) if (spec.type === "ImportSpecifier") bindings.push(spec.imported.name || spec.imported.value);
|
|
2002
|
+
else if (spec.type === "ImportDefaultSpecifier") bindings.push("default");
|
|
2003
|
+
}
|
|
2004
|
+
if (isRuntimeSpecifier(sourceVal, ctx.runtimePackage)) {
|
|
2005
|
+
ctx.zintlImportGroup = {
|
|
2006
|
+
start: node.start,
|
|
2007
|
+
end: node.end,
|
|
2008
|
+
source: sourceVal
|
|
2009
|
+
};
|
|
2010
|
+
for (const name of bindings) ctx.runtimeImports.push(name);
|
|
2011
|
+
if (!node.specifiers?.length) {
|
|
2012
|
+
ctx.mode = "entry";
|
|
2013
|
+
ctx.hasZintlMarker = true;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
ctx.addDependency(sourceVal, false, bindings);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
//#endregion
|
|
2022
|
+
//#region src/visitors/index.ts
|
|
2023
|
+
function createCombinedVisitor(ctx) {
|
|
2024
|
+
for (const trivia of ctx.trivias) if (trivia.value.includes("@zintl-ignore-file")) {
|
|
2025
|
+
ctx.isIgnoredFile = true;
|
|
2026
|
+
break;
|
|
2027
|
+
}
|
|
2028
|
+
const visitors = ctx.fastPathRegex.test(ctx.code) ? [
|
|
2029
|
+
...ctx.hasJsxSinks ? [createJsxVisitor(ctx)] : [],
|
|
2030
|
+
createBindingVisitor(ctx),
|
|
2031
|
+
createProgramVisitor(ctx)
|
|
2032
|
+
] : [createProgramVisitor(ctx)];
|
|
2033
|
+
if (ctx.targetPlugins) {
|
|
2034
|
+
for (const plugin of ctx.targetPlugins) if (plugin.createVisitor) visitors.push(plugin.createVisitor(ctx));
|
|
2035
|
+
}
|
|
2036
|
+
const combined = {};
|
|
2037
|
+
for (const visitor of visitors) for (const [key, value] of Object.entries(visitor)) {
|
|
2038
|
+
if (!combined[key]) {
|
|
2039
|
+
combined[key] = value;
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
const existing = combined[key];
|
|
2043
|
+
const next = value;
|
|
2044
|
+
combined[key] = {
|
|
2045
|
+
enter(node, ctx, parents) {
|
|
2046
|
+
if (typeof existing === "function") existing(node, ctx, parents);
|
|
2047
|
+
else if (existing.enter) existing.enter(node, ctx, parents);
|
|
2048
|
+
if (typeof next === "function") next(node, ctx, parents);
|
|
2049
|
+
else if (next.enter) next.enter(node, ctx, parents);
|
|
2050
|
+
},
|
|
2051
|
+
exit(node, ctx, parents) {
|
|
2052
|
+
if (typeof existing !== "function" && existing.exit) existing.exit(node, ctx, parents);
|
|
2053
|
+
if (typeof next !== "function" && next.exit) next.exit(node, ctx, parents);
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
return combined;
|
|
2058
|
+
}
|
|
2059
|
+
//#endregion
|
|
2060
|
+
//#region src/html.ts
|
|
2061
|
+
/**
|
|
2062
|
+
* Extracts translatable configuration and template strings from HTML files/templates.
|
|
2063
|
+
* Supports: <title>, <meta name="description">, module <script src="...">,
|
|
2064
|
+
* HTML text nodes (using stitchHTML), and translatable attributes (alt, placeholder, aria-label, title).
|
|
2065
|
+
*/
|
|
2066
|
+
function extractHtml(code, filePath, fileBoundaryId, options = {}) {
|
|
2067
|
+
code = code.replace(/\r\n/g, "\n");
|
|
2068
|
+
const ctx = new ExtractionContext(code, filePath, fileBoundaryId, options);
|
|
2069
|
+
ctx.logger.debug(`Extracting HTML configurations from ${filePath}`);
|
|
2070
|
+
const projection = { scripts: [] };
|
|
2071
|
+
const cleanCode = code.replace(/<!--[\s\S]*?-->/g, (m) => " ".repeat(m.length));
|
|
2072
|
+
const htmlDirMatch = cleanCode.match(/<html[^>]*dir=["'](ltr|rtl|auto)["']/i);
|
|
2073
|
+
if (htmlDirMatch) projection.dir = htmlDirMatch[1].toLowerCase();
|
|
2074
|
+
const titleMatch = cleanCode.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
2075
|
+
if (titleMatch) projection.title = titleMatch[1].trim();
|
|
2076
|
+
const descMatch = cleanCode.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']*)["']/i) || cleanCode.match(/<meta[^>]*content=["']([^"']*)["'][^>]*name=["']description["']/i);
|
|
2077
|
+
if (descMatch) projection.description = descMatch[1].trim();
|
|
2078
|
+
const scriptRegex = /<script[^>]*src=["']([^"']*)["']/gi;
|
|
2079
|
+
let match;
|
|
2080
|
+
while ((match = scriptRegex.exec(cleanCode)) !== null) projection.scripts.push(match[1]);
|
|
2081
|
+
let activeContent = code;
|
|
2082
|
+
let contentOffset = 0;
|
|
2083
|
+
if (options.activeRange) {
|
|
2084
|
+
activeContent = code.substring(options.activeRange.start, options.activeRange.end);
|
|
2085
|
+
contentOffset = options.activeRange.start;
|
|
2086
|
+
} else if (options.isSfcTemplate) {
|
|
2087
|
+
activeContent = code;
|
|
2088
|
+
contentOffset = 0;
|
|
2089
|
+
} else if (filePath.endsWith(".html")) {
|
|
2090
|
+
const bodyMatch = /<body\b[^>]*>([\s\S]*?)<\/body>/i.exec(code);
|
|
2091
|
+
if (bodyMatch) {
|
|
2092
|
+
activeContent = bodyMatch[1];
|
|
2093
|
+
contentOffset = code.indexOf(bodyMatch[1], bodyMatch.index);
|
|
2094
|
+
} else activeContent = "";
|
|
2095
|
+
} else {
|
|
2096
|
+
activeContent = code;
|
|
2097
|
+
contentOffset = 0;
|
|
2098
|
+
}
|
|
2099
|
+
if (activeContent.trim()) {
|
|
2100
|
+
ctx.stitchHTML(activeContent, (trimmed, note, passVars, start, end, tagMap) => {
|
|
2101
|
+
let processedText = trimmed;
|
|
2102
|
+
const variables = [];
|
|
2103
|
+
if (ctx.mustacheRegex) {
|
|
2104
|
+
const regex = ctx.mustacheRegex;
|
|
2105
|
+
regex.lastIndex = 0;
|
|
2106
|
+
let match;
|
|
2107
|
+
let offsetShift = 0;
|
|
2108
|
+
let varIndex = 0;
|
|
2109
|
+
const matches = [];
|
|
2110
|
+
while ((match = regex.exec(trimmed)) !== null) matches.push({
|
|
2111
|
+
raw: match[0],
|
|
2112
|
+
expr: match[1].trim(),
|
|
2113
|
+
index: match.index
|
|
2114
|
+
});
|
|
2115
|
+
for (const m of matches) {
|
|
2116
|
+
let varName = m.expr;
|
|
2117
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(varName)) varName = `var${varIndex++}`;
|
|
2118
|
+
const replacement = `{${varName}}`;
|
|
2119
|
+
const matchIndexInProcessed = m.index + offsetShift;
|
|
2120
|
+
processedText = processedText.substring(0, matchIndexInProcessed) + replacement + processedText.substring(matchIndexInProcessed + m.raw.length);
|
|
2121
|
+
offsetShift += replacement.length - m.raw.length;
|
|
2122
|
+
variables.push({
|
|
2123
|
+
name: varName,
|
|
2124
|
+
originalName: varName,
|
|
2125
|
+
expression: m.expr,
|
|
2126
|
+
start: contentOffset + start + m.index,
|
|
2127
|
+
end: contentOffset + start + m.index + m.raw.length
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
if (processedText.replace(/\{[a-zA-Z_$][a-zA-Z0-9_$]*\}/g, "").trim() === "") return;
|
|
2132
|
+
const msgId = generateMessageId(processedText, "HTML_TEXT", note);
|
|
2133
|
+
ctx.addMessage(msgId, processedText, "HTML_TEXT", fileBoundaryId, {
|
|
2134
|
+
line: 0,
|
|
2135
|
+
column: 0
|
|
2136
|
+
}, variables.map((v) => v.name), note, "HTML_TEXT", passVars);
|
|
2137
|
+
ctx.addRawSink({
|
|
2138
|
+
text: processedText,
|
|
2139
|
+
sinkType: "HTML_TEXT",
|
|
2140
|
+
start: contentOffset + start,
|
|
2141
|
+
end: contentOffset + end,
|
|
2142
|
+
line: 0,
|
|
2143
|
+
column: 0,
|
|
2144
|
+
boundaryId: fileBoundaryId,
|
|
2145
|
+
variables,
|
|
2146
|
+
note,
|
|
2147
|
+
passVars,
|
|
2148
|
+
isFragment: false,
|
|
2149
|
+
tagMap
|
|
2150
|
+
});
|
|
2151
|
+
}, void 0, {}, (s, e) => ({
|
|
2152
|
+
start: s,
|
|
2153
|
+
end: e
|
|
2154
|
+
}));
|
|
2155
|
+
const tagRegex = /<([a-zA-Z0-9:-]+)\s*([^>]*?)\/?>/g;
|
|
2156
|
+
let tagMatch;
|
|
2157
|
+
while ((tagMatch = tagRegex.exec(activeContent)) !== null) {
|
|
2158
|
+
tagMatch[1].toLowerCase();
|
|
2159
|
+
const attrsString = tagMatch[2];
|
|
2160
|
+
if (!attrsString) continue;
|
|
2161
|
+
const attrRegex = /\b([a-zA-Z0-9:-]+)\s*=\s*(?:'([^']*)'|"([^"]*)"|([^\s>]+))/gi;
|
|
2162
|
+
let attrMatch;
|
|
2163
|
+
const attrsIndex = tagMatch[0].indexOf(attrsString);
|
|
2164
|
+
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
2165
|
+
const attrName = attrMatch[1].toLowerCase();
|
|
2166
|
+
const attrVal = attrMatch[2] || attrMatch[3] || attrMatch[4] || "";
|
|
2167
|
+
if (attrVal && ctx.htmlAttributes.has(attrName)) {
|
|
2168
|
+
const attrStart = contentOffset + tagMatch.index + attrsIndex + attrMatch.index;
|
|
2169
|
+
const attrEnd = attrStart + attrMatch[0].length;
|
|
2170
|
+
const msgId = generateMessageId(attrVal, "HTML_ATTR");
|
|
2171
|
+
ctx.addMessage(msgId, attrVal, "HTML_ATTR", fileBoundaryId, {
|
|
2172
|
+
line: 0,
|
|
2173
|
+
column: 0
|
|
2174
|
+
}, [], void 0, `html:attr:${attrName}`);
|
|
2175
|
+
ctx.addRawSink({
|
|
2176
|
+
text: attrVal,
|
|
2177
|
+
sinkType: `html:attr:${attrName}`,
|
|
2178
|
+
start: attrStart,
|
|
2179
|
+
end: attrEnd,
|
|
2180
|
+
line: 0,
|
|
2181
|
+
column: 0,
|
|
2182
|
+
boundaryId: fileBoundaryId,
|
|
2183
|
+
variables: [],
|
|
2184
|
+
isFragment: false
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
messages: Array.from(ctx.messages.values()),
|
|
2192
|
+
code,
|
|
2193
|
+
transforms: ctx.transforms,
|
|
2194
|
+
needsLoader: ctx.messages.size > 0 || ctx.usedKeys.size > 0,
|
|
2195
|
+
hasZintlMacro: ctx.hasZintlMacro,
|
|
2196
|
+
hasZintlMarker: ctx.hasZintlMarker,
|
|
2197
|
+
anchorSites: ctx.anchorSites,
|
|
2198
|
+
mode: ctx.mode,
|
|
2199
|
+
runtimeImports: ctx.runtimeImports,
|
|
2200
|
+
dependencies: projection.scripts.map((s) => ({
|
|
2201
|
+
id: s,
|
|
2202
|
+
dynamic: false
|
|
2203
|
+
})),
|
|
2204
|
+
usedKeys: ctx.usedKeys,
|
|
2205
|
+
boundaryHashes: ctx.computeBoundaryHashes(),
|
|
2206
|
+
exportedBoundaries: Object.fromEntries(ctx.exportedBoundaries),
|
|
2207
|
+
internalDeps: Object.fromEntries(Array.from(ctx.internalDeps.entries()).map(([k, v]) => [k, Array.from(v)])),
|
|
2208
|
+
rawSinks: ctx.rawSinks,
|
|
2209
|
+
rawManualTranslations: ctx.rawManualTranslations,
|
|
2210
|
+
htmlProjection: options.isSfcTemplate ? void 0 : projection
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
//#endregion
|
|
2214
|
+
//#region src/parser.ts
|
|
2215
|
+
function extract(code, filePath, fileBoundaryId, options = {}) {
|
|
2216
|
+
code = code.replace(/\r\n/g, "\n");
|
|
2217
|
+
const compiledState = options.compiledState ?? resolveTargets(options.targets ?? []);
|
|
2218
|
+
const normalizedOptions = {
|
|
2219
|
+
...options,
|
|
2220
|
+
compiledState
|
|
2221
|
+
};
|
|
2222
|
+
const sfcRules = [...compiledState.sfcRules, ...normalizedOptions.sfcRules || []];
|
|
2223
|
+
const ext = "." + filePath.split(".").pop();
|
|
2224
|
+
const sfcRule = sfcRules.find((rule) => rule.extensions.includes(ext));
|
|
2225
|
+
const isLikelyUI = compiledState.fastPathRegex.test(code);
|
|
2226
|
+
const isLikelyBridge = code.includes("import");
|
|
2227
|
+
if (!isLikelyUI && !isLikelyBridge) return {
|
|
2228
|
+
messages: [],
|
|
2229
|
+
code,
|
|
2230
|
+
transforms: [],
|
|
2231
|
+
needsLoader: false,
|
|
2232
|
+
hasZintlMacro: false,
|
|
2233
|
+
hasZintlMarker: false,
|
|
2234
|
+
anchorSites: [],
|
|
2235
|
+
mode: "boundary",
|
|
2236
|
+
runtimeImports: [],
|
|
2237
|
+
dependencies: [],
|
|
2238
|
+
usedKeys: /* @__PURE__ */ new Set(),
|
|
2239
|
+
boundaryHashes: {},
|
|
2240
|
+
exportedBoundaries: {},
|
|
2241
|
+
internalDeps: {},
|
|
2242
|
+
rawSinks: [],
|
|
2243
|
+
rawManualTranslations: []
|
|
2244
|
+
};
|
|
2245
|
+
if (filePath.endsWith(".html")) return extractHtml(code, filePath, fileBoundaryId, normalizedOptions);
|
|
2246
|
+
if (sfcRule) return extractSfc(code, filePath, fileBoundaryId, normalizedOptions, sfcRule);
|
|
2247
|
+
const ctx = new ExtractionContext(code, filePath, fileBoundaryId, normalizedOptions);
|
|
2248
|
+
ctx.logger.debug(`Extracting messages from ${filePath}`);
|
|
2249
|
+
const result = parseSync(code.includes("<") && !filePath.endsWith(".tsx") ? filePath + ".tsx" : filePath, code, { sourceType: "module" });
|
|
2250
|
+
if (result.errors.length > 0) ctx.logger.warn(`OXC Parse Errors in ${filePath}:`, result.errors);
|
|
2251
|
+
ctx.trivias = result.comments;
|
|
2252
|
+
result.program.body.forEach((stmt) => {
|
|
2253
|
+
if (stmt.type === "ImportDeclaration") {
|
|
2254
|
+
const sourceVal = stmt.source?.value ?? "";
|
|
2255
|
+
const isZintl = isRuntimeSpecifier(sourceVal, ctx.runtimePackage);
|
|
2256
|
+
const hasNoSpecifiers = !stmt.specifiers || stmt.specifiers.length === 0;
|
|
2257
|
+
if (isZintl) {
|
|
2258
|
+
ctx.zintlImportGroup = {
|
|
2259
|
+
start: stmt.start,
|
|
2260
|
+
end: stmt.end,
|
|
2261
|
+
source: sourceVal
|
|
2262
|
+
};
|
|
2263
|
+
if (hasNoSpecifiers) {
|
|
2264
|
+
ctx.hasZintlMarker = true;
|
|
2265
|
+
ctx.mode = "entry";
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
const visitor = createCombinedVisitor(ctx);
|
|
2271
|
+
walk(result.program, visitor, ctx);
|
|
2272
|
+
return {
|
|
2273
|
+
messages: Array.from(ctx.messages.values()),
|
|
2274
|
+
code,
|
|
2275
|
+
transforms: ctx.transforms,
|
|
2276
|
+
needsLoader: ctx.messages.size > 0 || ctx.usedKeys.size > 0,
|
|
2277
|
+
hasZintlMacro: ctx.hasZintlMacro,
|
|
2278
|
+
hasZintlMarker: ctx.hasZintlMarker,
|
|
2279
|
+
anchorSites: ctx.anchorSites,
|
|
2280
|
+
mode: ctx.mode,
|
|
2281
|
+
runtimeImports: ctx.runtimeImports,
|
|
2282
|
+
zintlImportGroup: ctx.zintlImportGroup,
|
|
2283
|
+
rawSinks: ctx.rawSinks,
|
|
2284
|
+
rawManualTranslations: ctx.rawManualTranslations,
|
|
2285
|
+
exportedBoundaries: Object.fromEntries(ctx.exportedBoundaries),
|
|
2286
|
+
internalDeps: Object.fromEntries(Array.from(ctx.internalDeps.entries()).map(([k, v]) => [k, Array.from(v)])),
|
|
2287
|
+
usedKeys: ctx.usedKeys,
|
|
2288
|
+
boundaryHashes: ctx.computeBoundaryHashes(),
|
|
2289
|
+
dependencies: Array.from(ctx.dependencyPaths, ([id, dynamic]) => ({
|
|
2290
|
+
id,
|
|
2291
|
+
dynamic,
|
|
2292
|
+
bindings: Array.from(ctx.dependencyBindings.get(id) || [])
|
|
2293
|
+
})),
|
|
2294
|
+
componentFunctions: Array.from(ctx.componentFunctions)
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
function extractSfc(code, filePath, fileBoundaryId, options, rule) {
|
|
2298
|
+
const jsBlocks = [];
|
|
2299
|
+
const ignoreMatches = [];
|
|
2300
|
+
const htmlMatches = [];
|
|
2301
|
+
for (const block of rule.blocks) {
|
|
2302
|
+
block.pattern.lastIndex = 0;
|
|
2303
|
+
let match;
|
|
2304
|
+
while ((match = block.pattern.exec(code)) !== null) {
|
|
2305
|
+
let attrs = "";
|
|
2306
|
+
let blockContent = "";
|
|
2307
|
+
if (match.length >= 3) {
|
|
2308
|
+
attrs = match[1] || "";
|
|
2309
|
+
blockContent = match[2] || "";
|
|
2310
|
+
} else if (match.length === 2) blockContent = match[1] || "";
|
|
2311
|
+
else blockContent = match[0] || "";
|
|
2312
|
+
const matchStart = code.indexOf(blockContent, match.index);
|
|
2313
|
+
const matchEnd = matchStart + blockContent.length;
|
|
2314
|
+
if (block.action === "javascript") {
|
|
2315
|
+
const startLine = (code.substring(0, matchStart).match(/\n/g) || []).length;
|
|
2316
|
+
const virtualExt = block.resolveVirtualExtension ? block.resolveVirtualExtension(attrs) : ".ts";
|
|
2317
|
+
jsBlocks.push({
|
|
2318
|
+
id: block.id,
|
|
2319
|
+
content: blockContent,
|
|
2320
|
+
start: matchStart,
|
|
2321
|
+
startLine,
|
|
2322
|
+
virtualExt
|
|
2323
|
+
});
|
|
2324
|
+
ignoreMatches.push({
|
|
2325
|
+
start: match.index,
|
|
2326
|
+
end: match.index + match[0].length
|
|
2327
|
+
});
|
|
2328
|
+
} else if (block.action === "ignore") ignoreMatches.push({
|
|
2329
|
+
start: match.index,
|
|
2330
|
+
end: match.index + match[0].length
|
|
2331
|
+
});
|
|
2332
|
+
else if (block.action === "html") htmlMatches.push({
|
|
2333
|
+
start: matchStart,
|
|
2334
|
+
end: matchEnd,
|
|
2335
|
+
isActive: block.isActiveContent || false
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
const jsResults = [];
|
|
2340
|
+
for (const block of jsBlocks) {
|
|
2341
|
+
if (!block.content.trim()) continue;
|
|
2342
|
+
const res = extract(block.content, filePath + block.virtualExt, fileBoundaryId, options);
|
|
2343
|
+
if (res.messages) for (const msg of res.messages) msg.location.line += block.startLine;
|
|
2344
|
+
if (res.transforms) for (const t of res.transforms) {
|
|
2345
|
+
t.start += block.start;
|
|
2346
|
+
t.end += block.start;
|
|
2347
|
+
}
|
|
2348
|
+
if (res.rawSinks) for (const s of res.rawSinks) {
|
|
2349
|
+
s.start += block.start;
|
|
2350
|
+
s.end += block.start;
|
|
2351
|
+
s.line += block.startLine;
|
|
2352
|
+
if (s.hostStart !== void 0) s.hostStart += block.start;
|
|
2353
|
+
if (s.hostEnd !== void 0) s.hostEnd += block.start;
|
|
2354
|
+
if (s.fragmentStart !== void 0) s.fragmentStart += block.start;
|
|
2355
|
+
if (s.fragmentEnd !== void 0) s.fragmentEnd += block.start;
|
|
2356
|
+
if (s.variables) for (const v of s.variables) {
|
|
2357
|
+
v.start += block.start;
|
|
2358
|
+
v.end += block.start;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
if (res.rawManualTranslations) for (const t of res.rawManualTranslations) {
|
|
2362
|
+
t.start += block.start;
|
|
2363
|
+
t.end += block.start;
|
|
2364
|
+
t.line += block.start;
|
|
2365
|
+
}
|
|
2366
|
+
if (res.anchorSites) for (const s of res.anchorSites) {
|
|
2367
|
+
s.start += block.start;
|
|
2368
|
+
s.end += block.start;
|
|
2369
|
+
if (s.statementRange) {
|
|
2370
|
+
s.statementRange.start += block.start;
|
|
2371
|
+
s.statementRange.end += block.start;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
if (res.zintlImportGroup) {
|
|
2375
|
+
res.zintlImportGroup.start += block.start;
|
|
2376
|
+
res.zintlImportGroup.end += block.start;
|
|
2377
|
+
}
|
|
2378
|
+
jsResults.push(res);
|
|
2379
|
+
}
|
|
2380
|
+
let templateHtml = code;
|
|
2381
|
+
const sortedIgnores = [...ignoreMatches].sort((a, b) => b.start - a.start);
|
|
2382
|
+
for (const m of sortedIgnores) {
|
|
2383
|
+
const len = m.end - m.start;
|
|
2384
|
+
templateHtml = templateHtml.substring(0, m.start) + " ".repeat(len) + templateHtml.substring(m.end);
|
|
2385
|
+
}
|
|
2386
|
+
const activeHtml = htmlMatches.find((m) => m.isActive);
|
|
2387
|
+
const templateExt = extractHtml(templateHtml, filePath + ".html", fileBoundaryId, {
|
|
2388
|
+
...options,
|
|
2389
|
+
isSfcTemplate: true,
|
|
2390
|
+
activeRange: activeHtml ? {
|
|
2391
|
+
start: activeHtml.start,
|
|
2392
|
+
end: activeHtml.end
|
|
2393
|
+
} : void 0
|
|
2394
|
+
});
|
|
2395
|
+
const messages = [...jsResults.flatMap((r) => r.messages), ...templateExt?.messages || []];
|
|
2396
|
+
const transforms = [...jsResults.flatMap((r) => r.transforms), ...templateExt?.transforms || []];
|
|
2397
|
+
const dependencies = [...jsResults.flatMap((r) => r.dependencies), ...templateExt?.dependencies || []];
|
|
2398
|
+
const rawSinks = [...jsResults.flatMap((r) => r.rawSinks), ...templateExt?.rawSinks || []];
|
|
2399
|
+
const rawManualTranslations = [...jsResults.flatMap((r) => r.rawManualTranslations), ...templateExt?.rawManualTranslations || []];
|
|
2400
|
+
const usedKeys = /* @__PURE__ */ new Set([...jsResults.flatMap((r) => Array.from(r.usedKeys)), ...templateExt?.usedKeys || []]);
|
|
2401
|
+
const boundaryHashes = {};
|
|
2402
|
+
for (const r of jsResults) Object.assign(boundaryHashes, r.boundaryHashes);
|
|
2403
|
+
if (templateExt) Object.assign(boundaryHashes, templateExt.boundaryHashes);
|
|
2404
|
+
const exportedBoundaries = {};
|
|
2405
|
+
for (const r of jsResults) Object.assign(exportedBoundaries, r.exportedBoundaries);
|
|
2406
|
+
if (templateExt) Object.assign(exportedBoundaries, templateExt.exportedBoundaries);
|
|
2407
|
+
const internalDeps = {};
|
|
2408
|
+
const allDeps = [...jsResults.map((r) => r.internalDeps), templateExt?.internalDeps || {}];
|
|
2409
|
+
for (const deps of allDeps) for (const [k, v] of Object.entries(deps)) internalDeps[k] = [...internalDeps[k] || [], ...v];
|
|
2410
|
+
return {
|
|
2411
|
+
messages,
|
|
2412
|
+
code,
|
|
2413
|
+
transforms,
|
|
2414
|
+
needsLoader: jsResults.some((r) => r.needsLoader) || (templateExt?.needsLoader ?? false),
|
|
2415
|
+
hasZintlMacro: jsResults.some((r) => r.hasZintlMacro) || (templateExt?.hasZintlMacro ?? false),
|
|
2416
|
+
hasZintlMarker: jsResults.some((r) => r.hasZintlMarker) || (templateExt?.hasZintlMarker ?? false),
|
|
2417
|
+
anchorSites: [...jsResults.flatMap((r) => r.anchorSites), ...templateExt?.anchorSites || []],
|
|
2418
|
+
mode: jsResults.some((r) => r.mode === "entry") || templateExt?.mode === "entry" ? "entry" : "boundary",
|
|
2419
|
+
runtimeImports: [...jsResults.flatMap((r) => r.runtimeImports), ...templateExt?.runtimeImports || []],
|
|
2420
|
+
dependencies,
|
|
2421
|
+
usedKeys,
|
|
2422
|
+
boundaryHashes,
|
|
2423
|
+
zintlImportGroup: jsResults.find((r) => r.zintlImportGroup)?.zintlImportGroup || templateExt?.zintlImportGroup,
|
|
2424
|
+
exportedBoundaries,
|
|
2425
|
+
internalDeps,
|
|
2426
|
+
rawSinks,
|
|
2427
|
+
rawManualTranslations,
|
|
2428
|
+
componentFunctions: [...jsResults.flatMap((r) => r.componentFunctions || []), ...templateExt?.componentFunctions || []],
|
|
2429
|
+
htmlProjection: templateExt?.htmlProjection
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
//#endregion
|
|
2433
|
+
export { HTML_TAG_SPLIT_REGEX, RUNTIME_PACKAGE, RUNTIME_SPECIFIERS, ZINTL_MACRO, ZintlLogger, extract, generateMessageId, isRuntimeSpecifier, logger, resolveTargets };
|