@x18n/builtins 0.0.0 → 0.0.2
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/package.json +2 -2
- package/dist/checks.d.mts +0 -19
- package/dist/checks.mjs +0 -330
- package/dist/extractors.d.mts +0 -51
- package/dist/extractors.mjs +0 -247
- package/dist/finders-Cf-IEaDm.mjs +0 -188
- package/dist/finders.d.mts +0 -29
- package/dist/finders.mjs +0 -3
- package/dist/formats-BAogSrvw.mjs +0 -1331
- package/dist/formats.d.mts +0 -34
- package/dist/formats.mjs +0 -1
- package/dist/index.d.mts +0 -5
- package/dist/index.mjs +0 -38
- package/dist/manifests.d.mts +0 -10
- package/dist/manifests.mjs +0 -49
- package/dist/placeholders.d.mts +0 -27
- package/dist/placeholders.mjs +0 -131
- package/dist/presets-CxJ8CsTD.mjs +0 -1096
- package/dist/presets.d.mts +0 -58
- package/dist/presets.mjs +0 -1
- package/dist/regex-350J_An-.mjs +0 -89
- package/dist/reporters.d.mts +0 -14
- package/dist/reporters.mjs +0 -203
- package/dist/scanners--prozYaE.mjs +0 -119
- package/dist/scanners.d.mts +0 -48
- package/dist/scanners.mjs +0 -4
- package/dist/sfc-blocks-CQhgEeT6.mjs +0 -161
- package/dist/vue-6FYzIrQl.mjs +0 -730
- package/dist/yaml-plugin.d.mts +0 -8
- package/dist/yaml-plugin.mjs +0 -67
package/dist/vue-6FYzIrQl.mjs
DELETED
|
@@ -1,730 +0,0 @@
|
|
|
1
|
-
//#region src/scanners/js.ts
|
|
2
|
-
const hardStringIgnoredAttributes = /* @__PURE__ */ new Set([
|
|
3
|
-
"class",
|
|
4
|
-
"className",
|
|
5
|
-
"style",
|
|
6
|
-
"key",
|
|
7
|
-
"ref",
|
|
8
|
-
"id",
|
|
9
|
-
"href",
|
|
10
|
-
"src",
|
|
11
|
-
"type",
|
|
12
|
-
"name",
|
|
13
|
-
"for",
|
|
14
|
-
"htmlFor",
|
|
15
|
-
"role",
|
|
16
|
-
"slot",
|
|
17
|
-
"as",
|
|
18
|
-
"to",
|
|
19
|
-
"target",
|
|
20
|
-
"rel",
|
|
21
|
-
"lang",
|
|
22
|
-
"xmlns",
|
|
23
|
-
"viewBox",
|
|
24
|
-
"d",
|
|
25
|
-
"fill",
|
|
26
|
-
"stroke"
|
|
27
|
-
]);
|
|
28
|
-
function isNode(value) {
|
|
29
|
-
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string" && "start" in value;
|
|
30
|
-
}
|
|
31
|
-
function isStringLiteral(node) {
|
|
32
|
-
return node?.type === "Literal" && typeof node.value === "string";
|
|
33
|
-
}
|
|
34
|
-
/** Inner range of a quoted literal. */
|
|
35
|
-
function innerRange(node) {
|
|
36
|
-
return {
|
|
37
|
-
start: node.start + 1,
|
|
38
|
-
end: node.end - 1
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function unwrap(node) {
|
|
42
|
-
let current = node;
|
|
43
|
-
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression" || current.type === "ParenthesizedExpression" || current.type === "AwaitExpression" || current.type === "ChainExpression") current = current.type === "AwaitExpression" ? current.argument : current.expression;
|
|
44
|
-
return current;
|
|
45
|
-
}
|
|
46
|
-
/** `a.b.c` for identifier and member chains, `this.` dropped; undefined for anything else. */
|
|
47
|
-
function calleeName(node) {
|
|
48
|
-
const target = unwrap(node);
|
|
49
|
-
if (target.type === "Identifier") return target.name;
|
|
50
|
-
if (target.type === "ThisExpression") return "this";
|
|
51
|
-
if (target.type === "MemberExpression" && !target.computed && target.property.type === "Identifier") {
|
|
52
|
-
const object = calleeName(target.object);
|
|
53
|
-
if (object === void 0) return void 0;
|
|
54
|
-
return object === "this" ? target.property.name : `${object}.${target.property.name}`;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function jsxName(node) {
|
|
58
|
-
if (node.type === "JSXIdentifier") return node.name;
|
|
59
|
-
if (node.type === "JSXMemberExpression") {
|
|
60
|
-
const object = node.object.type === "JSXIdentifier" ? node.object.name : node.object.type === "JSXMemberExpression" ? jsxName(node.object) : void 0;
|
|
61
|
-
return object ? `${object}.${node.property.name}` : void 0;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function propertyName(node) {
|
|
65
|
-
if (node.type === "Identifier") return node.name;
|
|
66
|
-
if (isStringLiteral(node)) return node.value;
|
|
67
|
-
}
|
|
68
|
-
function objectProperty(object, name) {
|
|
69
|
-
for (const property of object.properties) if (property.type === "Property" && !property.computed && propertyName(property.key) === name) return property.value;
|
|
70
|
-
}
|
|
71
|
-
var FileScan = class {
|
|
72
|
-
text;
|
|
73
|
-
offset;
|
|
74
|
-
collectHardStrings;
|
|
75
|
-
ignoredCallees;
|
|
76
|
-
usages = [];
|
|
77
|
-
keySlots = [];
|
|
78
|
-
hardStrings = [];
|
|
79
|
-
frames = [/* @__PURE__ */ new Map()];
|
|
80
|
-
constants = /* @__PURE__ */ new Map();
|
|
81
|
-
/** Several specs may share a callee, for example lingui `t('id')` and `t({ id })`. */
|
|
82
|
-
declared = /* @__PURE__ */ new Map();
|
|
83
|
-
factories = /* @__PURE__ */ new Map();
|
|
84
|
-
typed = /* @__PURE__ */ new Map();
|
|
85
|
-
consumed = /* @__PURE__ */ new Set();
|
|
86
|
-
constructor(text, offset, calls, collectHardStrings, ignoredCallees = /* @__PURE__ */ new Set()) {
|
|
87
|
-
this.text = text;
|
|
88
|
-
this.offset = offset;
|
|
89
|
-
this.collectHardStrings = collectHardStrings;
|
|
90
|
-
this.ignoredCallees = ignoredCallees;
|
|
91
|
-
for (const spec of calls) for (const name of [spec.callee].flat()) if (spec.factory) {
|
|
92
|
-
const list = this.factories.get(name) ?? [];
|
|
93
|
-
this.factories.set(name, list);
|
|
94
|
-
list.push(spec);
|
|
95
|
-
} else if (spec.types?.length) {
|
|
96
|
-
for (const type of spec.types) this.typed.set(type, spec);
|
|
97
|
-
this.declare(name, spec);
|
|
98
|
-
} else this.declare(name, spec);
|
|
99
|
-
}
|
|
100
|
-
range(range) {
|
|
101
|
-
return {
|
|
102
|
-
start: range.start + this.offset,
|
|
103
|
-
end: range.end + this.offset
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
declare(name, spec) {
|
|
107
|
-
const list = this.declared.get(name) ?? [];
|
|
108
|
-
this.declared.set(name, list);
|
|
109
|
-
list.push(spec);
|
|
110
|
-
}
|
|
111
|
-
lookup(name) {
|
|
112
|
-
return this.lookupAll(name)[0];
|
|
113
|
-
}
|
|
114
|
-
lookupAll(name) {
|
|
115
|
-
for (let i = this.frames.length - 1; i >= 0; i--) {
|
|
116
|
-
const binding = this.frames[i].get(name);
|
|
117
|
-
if (binding) return [binding];
|
|
118
|
-
}
|
|
119
|
-
const specs = this.declared.get(name);
|
|
120
|
-
if (specs) return specs.map((spec) => ({ spec }));
|
|
121
|
-
const dot = name.indexOf(".");
|
|
122
|
-
if (dot > 0) {
|
|
123
|
-
const root = this.lookup(name.slice(0, dot));
|
|
124
|
-
if (root?.spec.returns?.some((ret) => ret.endsWith(name.slice(dot)))) return [root];
|
|
125
|
-
}
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
bind(name, binding) {
|
|
129
|
-
this.frames.at(-1).set(name, binding);
|
|
130
|
-
}
|
|
131
|
-
/** ---- Reading arguments ---- */
|
|
132
|
-
stringArg(value) {
|
|
133
|
-
if (!value) return void 0;
|
|
134
|
-
if ("props" in value) return void 0;
|
|
135
|
-
const node = unwrap(value);
|
|
136
|
-
if (isStringLiteral(node)) return node.value;
|
|
137
|
-
if (node.type === "ArrayExpression" && node.elements[0] && isStringLiteral(node.elements[0])) return node.elements[0].value;
|
|
138
|
-
if (node.type === "JSXExpressionContainer" && node.expression.type !== "JSXEmptyExpression") return this.stringArg(node.expression);
|
|
139
|
-
if (node.type === "Identifier") {
|
|
140
|
-
const constant = this.constants.get(node.name);
|
|
141
|
-
return constant && isStringLiteral(constant) ? constant.value : void 0;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
argValues(args, source, fallback) {
|
|
145
|
-
const arg = args[typeof source === "number" ? source : source?.arg ?? fallback];
|
|
146
|
-
const property = typeof source === "object" ? source.property : void 0;
|
|
147
|
-
if (!arg || property === void 0) return arg ? [arg] : [];
|
|
148
|
-
return property.split(".").reduce((values, segment) => values.flatMap((value) => {
|
|
149
|
-
if ("props" in value) {
|
|
150
|
-
const prop = value.props.get(segment);
|
|
151
|
-
return prop ? [prop] : [];
|
|
152
|
-
}
|
|
153
|
-
const node = unwrap(value);
|
|
154
|
-
if (node.type === "JSXExpressionContainer" && node.expression.type === "ObjectExpression") return segment === "*" ? this.allValues(node.expression) : [objectProperty(node.expression, segment)].filter(isNode);
|
|
155
|
-
if (node.type !== "ObjectExpression") return [];
|
|
156
|
-
return segment === "*" ? this.allValues(node) : [objectProperty(node, segment)].filter(isNode);
|
|
157
|
-
}), [arg]);
|
|
158
|
-
}
|
|
159
|
-
allValues(object) {
|
|
160
|
-
return object.properties.flatMap((p) => p.type === "Property" ? [p.value] : []);
|
|
161
|
-
}
|
|
162
|
-
/** ---- Usages ---- */
|
|
163
|
-
addKey(value, binding, namespace) {
|
|
164
|
-
if ("props" in value) return;
|
|
165
|
-
const node = unwrap(value);
|
|
166
|
-
const base = {
|
|
167
|
-
namespace: namespace ?? binding.namespace,
|
|
168
|
-
keyPrefix: binding.keyPrefix
|
|
169
|
-
};
|
|
170
|
-
if (isStringLiteral(node)) {
|
|
171
|
-
this.consumed.add(node.start);
|
|
172
|
-
this.keySlots.push(this.range(innerRange(node)));
|
|
173
|
-
this.usages.push({
|
|
174
|
-
...base,
|
|
175
|
-
raw: node.value,
|
|
176
|
-
range: this.range(innerRange(node)),
|
|
177
|
-
kind: "call",
|
|
178
|
-
dynamic: false
|
|
179
|
-
});
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (node.type === "JSXExpressionContainer") {
|
|
183
|
-
if (node.expression.type !== "JSXEmptyExpression") this.addKey(node.expression, binding, namespace);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (node.type === "TemplateLiteral") {
|
|
187
|
-
const first = node.quasis[0];
|
|
188
|
-
const raw = first.value.cooked ?? first.value.raw;
|
|
189
|
-
this.consumed.add(node.start);
|
|
190
|
-
const range = this.range({
|
|
191
|
-
start: node.start + 1,
|
|
192
|
-
end: node.start + 1 + raw.length
|
|
193
|
-
});
|
|
194
|
-
this.usages.push({
|
|
195
|
-
...base,
|
|
196
|
-
raw,
|
|
197
|
-
range,
|
|
198
|
-
kind: "call",
|
|
199
|
-
dynamic: node.expressions.length === 0 ? false : raw ? "prefix" : "unknown"
|
|
200
|
-
});
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (node.type === "ConditionalExpression") {
|
|
204
|
-
this.addKey(node.consequent, binding, namespace);
|
|
205
|
-
this.addKey(node.alternate, binding, namespace);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (node.type === "ArrowFunctionExpression" && node.params[0]?.type === "Identifier") return this.addSelector(node.params[0].name, node.body, base);
|
|
209
|
-
const constant = node.type === "Identifier" ? this.constants.get(node.name) : node.type === "MemberExpression" ? this.memberConstant(node) : void 0;
|
|
210
|
-
if (constant && isStringLiteral(constant)) {
|
|
211
|
-
this.usages.push({
|
|
212
|
-
...base,
|
|
213
|
-
raw: constant.value,
|
|
214
|
-
range: this.range(innerRange(constant)),
|
|
215
|
-
kind: "constant",
|
|
216
|
-
dynamic: false
|
|
217
|
-
});
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
this.usages.push({
|
|
221
|
-
...base,
|
|
222
|
-
raw: this.text.slice(node.start, node.end),
|
|
223
|
-
range: this.range({
|
|
224
|
-
start: node.start,
|
|
225
|
-
end: node.end
|
|
226
|
-
}),
|
|
227
|
-
kind: "call",
|
|
228
|
-
dynamic: "unknown"
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
/** i18next `t($ => $.a.b)`. */
|
|
232
|
-
addSelector(param, body, base) {
|
|
233
|
-
const segments = [];
|
|
234
|
-
let node = unwrap(body);
|
|
235
|
-
let end = node.end;
|
|
236
|
-
while (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
|
|
237
|
-
segments.unshift(node.property.name);
|
|
238
|
-
node = unwrap(node.object);
|
|
239
|
-
}
|
|
240
|
-
if (node.type !== "Identifier" || node.name !== param || segments.length === 0) return;
|
|
241
|
-
const start = node.end + 1;
|
|
242
|
-
end = Math.max(end, start);
|
|
243
|
-
this.usages.push({
|
|
244
|
-
...base,
|
|
245
|
-
raw: segments.join("."),
|
|
246
|
-
range: this.range({
|
|
247
|
-
start,
|
|
248
|
-
end
|
|
249
|
-
}),
|
|
250
|
-
kind: "selector",
|
|
251
|
-
dynamic: false
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
memberConstant(node) {
|
|
255
|
-
if (node.computed || node.property.type !== "Identifier" || node.object.type !== "Identifier") return void 0;
|
|
256
|
-
const object = this.constants.get(node.object.name);
|
|
257
|
-
return object && object.type === "ObjectExpression" ? objectProperty(object, node.property.name) : void 0;
|
|
258
|
-
}
|
|
259
|
-
handleCall(name, args) {
|
|
260
|
-
const bindings = this.lookupAll(name).filter((binding) => !binding.spec.factory);
|
|
261
|
-
for (const binding of bindings) {
|
|
262
|
-
const namespace = binding.spec.namespace ? this.stringArg(this.argValues(args, binding.spec.namespace, 1)[0]) : void 0;
|
|
263
|
-
for (const value of this.argValues(args, binding.spec.key, 0)) {
|
|
264
|
-
if (bindings.length > 1 && !("props" in value) && unwrap(value).type === "ObjectExpression") continue;
|
|
265
|
-
this.addKey(value, binding, namespace);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return bindings.length > 0;
|
|
269
|
-
}
|
|
270
|
-
call(node) {
|
|
271
|
-
const name = calleeName(node.callee);
|
|
272
|
-
if (name !== void 0) this.handleCall(name, node.arguments);
|
|
273
|
-
}
|
|
274
|
-
jsx(node) {
|
|
275
|
-
const name = jsxName(node.name);
|
|
276
|
-
if (!name || !this.lookup(name)) return;
|
|
277
|
-
const props = /* @__PURE__ */ new Map();
|
|
278
|
-
for (const attribute of node.attributes) if (attribute.type === "JSXAttribute" && attribute.value && attribute.name.type === "JSXIdentifier") {
|
|
279
|
-
props.set(attribute.name.name, attribute.value);
|
|
280
|
-
if (isStringLiteral(attribute.value)) this.consumed.add(attribute.value.start);
|
|
281
|
-
}
|
|
282
|
-
this.handleCall(name, [{ props }]);
|
|
283
|
-
}
|
|
284
|
-
/** ---- Bindings ---- */
|
|
285
|
-
declarator(id, init) {
|
|
286
|
-
if (!init) return;
|
|
287
|
-
const value = unwrap(init);
|
|
288
|
-
if (id.type === "Identifier" && (isStringLiteral(value) || value.type === "ObjectExpression")) this.constants.set(id.name, value);
|
|
289
|
-
if (value.type === "CallExpression") {
|
|
290
|
-
const factory = calleeName(value.callee);
|
|
291
|
-
const specs = factory ? this.factories.get(factory) : void 0;
|
|
292
|
-
if (specs) this.bindFactory(id, value, specs);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (id.type === "Identifier" && (value.type === "Identifier" || value.type === "MemberExpression")) {
|
|
296
|
-
const name = calleeName(value);
|
|
297
|
-
const binding = name ? this.lookup(name) : void 0;
|
|
298
|
-
if (binding) this.bind(id.name, binding);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
/** The first factory spec whose namespace and prefix sources all resolve, else the first spec. */
|
|
302
|
-
pickFactory(call, specs) {
|
|
303
|
-
const resolved = specs.map((spec) => {
|
|
304
|
-
const namespace = spec.namespace ? this.stringArg(this.argValues(call.arguments, spec.namespace, 0)[0]) : void 0;
|
|
305
|
-
const keyPrefix = spec.keyPrefix ? this.stringArg(this.argValues(call.arguments, spec.keyPrefix, 0)[0]) : void 0;
|
|
306
|
-
const complete = (!spec.namespace || namespace !== void 0) && (!spec.keyPrefix || keyPrefix !== void 0);
|
|
307
|
-
return {
|
|
308
|
-
binding: {
|
|
309
|
-
spec: {
|
|
310
|
-
callee: spec.callee,
|
|
311
|
-
returns: spec.returns
|
|
312
|
-
},
|
|
313
|
-
namespace,
|
|
314
|
-
keyPrefix
|
|
315
|
-
},
|
|
316
|
-
complete
|
|
317
|
-
};
|
|
318
|
-
});
|
|
319
|
-
return (resolved.find((r) => r.complete) ?? resolved[0]).binding;
|
|
320
|
-
}
|
|
321
|
-
bindFactory(id, call, specs) {
|
|
322
|
-
const binding = this.pickFactory(call, specs);
|
|
323
|
-
const returns = (binding.spec.returns ?? ["t"]).filter((name) => !name.includes("."));
|
|
324
|
-
if (id.type === "Identifier") {
|
|
325
|
-
this.bind(id.name, binding);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (id.type !== "ObjectPattern") return;
|
|
329
|
-
for (const property of id.properties) {
|
|
330
|
-
if (property.type !== "Property") continue;
|
|
331
|
-
const key = propertyName(property.key);
|
|
332
|
-
const local = unwrap(property.value);
|
|
333
|
-
if (key && returns.includes(key) && local.type === "Identifier") this.bind(local.name, binding);
|
|
334
|
-
if (key && local.type === "Identifier" && this.declared.has(`${key}.t`)) this.bind(`${local.name}.t`, binding);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
params(params) {
|
|
338
|
-
for (const param of params) {
|
|
339
|
-
const target = param.type === "TSParameterProperty" ? param.parameter : param;
|
|
340
|
-
if (target.type !== "Identifier" || !target.typeAnnotation) continue;
|
|
341
|
-
const annotation = target.typeAnnotation.typeAnnotation;
|
|
342
|
-
const typeName = annotation.type === "TSTypeReference" && annotation.typeName.type === "Identifier" ? annotation.typeName.name : void 0;
|
|
343
|
-
const spec = typeName ? this.typed.get(typeName) : void 0;
|
|
344
|
-
if (spec) this.bind(target.name, { spec: {
|
|
345
|
-
...spec,
|
|
346
|
-
types: void 0
|
|
347
|
-
} });
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
importSpecifier(node) {
|
|
351
|
-
const imported = node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
|
|
352
|
-
const spec = this.declared.get(imported)?.[0];
|
|
353
|
-
if (spec && imported !== node.local.name) this.bind(node.local.name, { spec });
|
|
354
|
-
}
|
|
355
|
-
/** ---- Hard strings ---- */
|
|
356
|
-
hard(text, range, kind, attribute) {
|
|
357
|
-
if (!this.collectHardStrings || !/\p{L}/u.test(text)) return;
|
|
358
|
-
this.hardStrings.push({
|
|
359
|
-
text,
|
|
360
|
-
range: this.range(range),
|
|
361
|
-
kind,
|
|
362
|
-
...attribute ? { attribute } : {}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
ignoredCall(name) {
|
|
366
|
-
return name !== void 0 && (name.startsWith("console.") || name === "require" || this.ignoredCallees.has(name));
|
|
367
|
-
}
|
|
368
|
-
/** ---- Walk ---- */
|
|
369
|
-
walk(node, parent, parentKey) {
|
|
370
|
-
const scoped = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "BlockStatement";
|
|
371
|
-
if (scoped) this.frames.push(/* @__PURE__ */ new Map());
|
|
372
|
-
this.visit(node, parent, parentKey);
|
|
373
|
-
for (const [key, child] of Object.entries(node)) {
|
|
374
|
-
if (key === "parent") continue;
|
|
375
|
-
if (Array.isArray(child)) {
|
|
376
|
-
for (const item of child) if (isNode(item)) this.walk(item, node, key);
|
|
377
|
-
} else if (isNode(child)) this.walk(child, node, key);
|
|
378
|
-
}
|
|
379
|
-
if (scoped) this.frames.pop();
|
|
380
|
-
}
|
|
381
|
-
visit(node, parent, parentKey) {
|
|
382
|
-
switch (node.type) {
|
|
383
|
-
case "CallExpression":
|
|
384
|
-
this.call(node);
|
|
385
|
-
break;
|
|
386
|
-
case "JSXOpeningElement":
|
|
387
|
-
this.jsx(node);
|
|
388
|
-
break;
|
|
389
|
-
case "VariableDeclarator":
|
|
390
|
-
this.declarator(node.id, node.init);
|
|
391
|
-
break;
|
|
392
|
-
case "FunctionDeclaration":
|
|
393
|
-
case "FunctionExpression":
|
|
394
|
-
case "ArrowFunctionExpression":
|
|
395
|
-
this.params(node.params);
|
|
396
|
-
break;
|
|
397
|
-
case "ImportSpecifier":
|
|
398
|
-
this.importSpecifier(node);
|
|
399
|
-
break;
|
|
400
|
-
case "JSXText": {
|
|
401
|
-
const text = node.value.trim();
|
|
402
|
-
if (text) this.hard(text, {
|
|
403
|
-
start: node.start + node.value.indexOf(text),
|
|
404
|
-
end: node.start + node.value.indexOf(text) + text.length
|
|
405
|
-
}, "jsx-text");
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
case "Literal":
|
|
409
|
-
case "TemplateLiteral": this.literal(node, parent, parentKey);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
literal(node, parent, parentKey) {
|
|
413
|
-
if (this.consumed.has(node.start) || !parent) return;
|
|
414
|
-
if (parent.type === "JSXAttribute") {
|
|
415
|
-
const name = parent.name.type === "JSXIdentifier" ? parent.name.name : "";
|
|
416
|
-
if (isStringLiteral(node) && !hardStringIgnoredAttributes.has(name) && !name.startsWith("data-") && !name.startsWith("aria-")) this.hard(node.value, {
|
|
417
|
-
start: parent.start,
|
|
418
|
-
end: parent.end
|
|
419
|
-
}, "jsx-attribute", name);
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (parent.type === "ImportDeclaration" || parent.type === "ExportNamedDeclaration" || parent.type === "ExportAllDeclaration" || parent.type === "TSLiteralType" || parent.type === "ImportExpression" || parent.type === "TSExternalModuleReference" || parent.type === "TSModuleDeclaration" || parent.type === "Property" && parentKey === "key" || parent.type === "MemberExpression" && parentKey === "property" || parent.type === "ExpressionStatement" || parent.type === "BinaryExpression" || parent.type === "SwitchCase" || parent.type === "CallExpression" && this.ignoredCall(calleeName(parent.callee))) return;
|
|
423
|
-
if (isStringLiteral(node)) this.hard(node.value, {
|
|
424
|
-
start: node.start,
|
|
425
|
-
end: node.end
|
|
426
|
-
}, "js-string");
|
|
427
|
-
else if (node.type === "TemplateLiteral") this.template(node);
|
|
428
|
-
}
|
|
429
|
-
/** `Hello ${user.name}` becomes `Hello {0}` with the argument `name: user.name`. */
|
|
430
|
-
template(node) {
|
|
431
|
-
if (!this.collectHardStrings) return;
|
|
432
|
-
const args = node.expressions.map((expression, i) => {
|
|
433
|
-
const source = this.text.slice(expression.start, expression.end);
|
|
434
|
-
return {
|
|
435
|
-
name: expression.type === "Identifier" ? expression.name : expression.type === "MemberExpression" && expression.property.type === "Identifier" ? expression.property.name : `arg${i}`,
|
|
436
|
-
expression: source
|
|
437
|
-
};
|
|
438
|
-
});
|
|
439
|
-
const text = node.quasis.map((q, i) => `${q.value.cooked ?? q.value.raw}${i < args.length ? `{${i}}` : ""}`).join("");
|
|
440
|
-
if (/\p{L}/u.test(text)) this.hardStrings.push({
|
|
441
|
-
text,
|
|
442
|
-
range: this.range({
|
|
443
|
-
start: node.start,
|
|
444
|
-
end: node.end
|
|
445
|
-
}),
|
|
446
|
-
kind: "js-template",
|
|
447
|
-
args
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
markers(program, comments) {
|
|
451
|
-
for (const comment of comments) {
|
|
452
|
-
const marker = /@x18n-keys?\b/.exec(comment.value)?.[0];
|
|
453
|
-
if (!marker) continue;
|
|
454
|
-
const next = this.firstAfter(program, comment.end);
|
|
455
|
-
if (!next) continue;
|
|
456
|
-
const literals = marker === "@x18n-keys" ? this.literalsIn(next) : isStringLiteral(next) ? [next] : [];
|
|
457
|
-
for (const literal of literals) if (isStringLiteral(literal)) this.usages.push({
|
|
458
|
-
raw: literal.value,
|
|
459
|
-
range: this.range(innerRange(literal)),
|
|
460
|
-
kind: "marker",
|
|
461
|
-
dynamic: false
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
firstAfter(root, offset) {
|
|
466
|
-
let best;
|
|
467
|
-
const visit = (node) => {
|
|
468
|
-
if (node.end <= offset || best && node.start > best.start) return;
|
|
469
|
-
if (node.start >= offset && (node.type === "Literal" || node.type === "ArrayExpression" || node.type === "ObjectExpression")) {
|
|
470
|
-
if (!best || node.start < best.start) best = node;
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
for (const [key, child] of Object.entries(node)) {
|
|
474
|
-
if (key === "parent") continue;
|
|
475
|
-
for (const item of [child].flat()) if (isNode(item)) visit(item);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
visit(root);
|
|
479
|
-
return best;
|
|
480
|
-
}
|
|
481
|
-
literalsIn(node) {
|
|
482
|
-
if (isStringLiteral(node)) return [node];
|
|
483
|
-
if (node.type === "ArrayExpression") return node.elements.flatMap((element) => element ? this.literalsIn(element) : []);
|
|
484
|
-
if (node.type === "ObjectExpression") return this.allValues(node).flatMap((value) => this.literalsIn(value));
|
|
485
|
-
return [];
|
|
486
|
-
}
|
|
487
|
-
result() {
|
|
488
|
-
return {
|
|
489
|
-
usages: this.usages,
|
|
490
|
-
keySlots: this.keySlots,
|
|
491
|
-
hardStrings: this.hardStrings
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
let parser;
|
|
496
|
-
/** Loads oxc lazily: native in Node, WASM through the package's browser export. */
|
|
497
|
-
function loadParser() {
|
|
498
|
-
parser ??= import("oxc-parser").then((mod) => mod.parseSync);
|
|
499
|
-
return parser;
|
|
500
|
-
}
|
|
501
|
-
function scanSource(parse, text, options) {
|
|
502
|
-
const source = options.expression ? `(${text})` : text;
|
|
503
|
-
const shift = options.expression ? -1 : 0;
|
|
504
|
-
const result = parse(options.filename, source, {
|
|
505
|
-
sourceType: "module",
|
|
506
|
-
range: false
|
|
507
|
-
});
|
|
508
|
-
const scan = new FileScan(source, (options.offset ?? 0) + shift, options.calls, options.hardStrings !== false, new Set(options.ignoreCallees));
|
|
509
|
-
scan.walk(result.program, void 0, "");
|
|
510
|
-
scan.markers(result.program, result.comments);
|
|
511
|
-
return scan.result();
|
|
512
|
-
}
|
|
513
|
-
/** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
|
|
514
|
-
function js() {
|
|
515
|
-
return {
|
|
516
|
-
name: "x18n:scanner-js",
|
|
517
|
-
scanners: [{
|
|
518
|
-
id: "js",
|
|
519
|
-
extensions: [
|
|
520
|
-
"js",
|
|
521
|
-
"jsx",
|
|
522
|
-
"mjs",
|
|
523
|
-
"cjs",
|
|
524
|
-
"ts",
|
|
525
|
-
"tsx",
|
|
526
|
-
"mts",
|
|
527
|
-
"cts"
|
|
528
|
-
],
|
|
529
|
-
async scan(file, ctx) {
|
|
530
|
-
return scanSource(await loadParser(), file.text, {
|
|
531
|
-
calls: ctx.calls,
|
|
532
|
-
filename: file.path,
|
|
533
|
-
hardStrings: true,
|
|
534
|
-
ignoreCallees: ctx.config.extract.ignoreCallees
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
}]
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
//#endregion
|
|
541
|
-
//#region src/scanners/vue.ts
|
|
542
|
-
let compiler;
|
|
543
|
-
function loadVueCompiler() {
|
|
544
|
-
compiler ??= import("@vue/compiler-sfc");
|
|
545
|
-
return compiler;
|
|
546
|
-
}
|
|
547
|
-
const ELEMENT = 1;
|
|
548
|
-
const TEXT = 2;
|
|
549
|
-
const SIMPLE_EXPRESSION = 4;
|
|
550
|
-
const INTERPOLATION = 5;
|
|
551
|
-
const ATTRIBUTE = 6;
|
|
552
|
-
const keyPathComponents = /* @__PURE__ */ new Map([
|
|
553
|
-
["i18n-t", "keypath"],
|
|
554
|
-
["I18nT", "keypath"],
|
|
555
|
-
["i18n", "path"],
|
|
556
|
-
["i18n-d", "path"]
|
|
557
|
-
]);
|
|
558
|
-
const ignoredAttributes = /* @__PURE__ */ new Set([
|
|
559
|
-
"class",
|
|
560
|
-
"style",
|
|
561
|
-
"key",
|
|
562
|
-
"ref",
|
|
563
|
-
"id",
|
|
564
|
-
"name",
|
|
565
|
-
"type",
|
|
566
|
-
"href",
|
|
567
|
-
"src",
|
|
568
|
-
"to",
|
|
569
|
-
"for",
|
|
570
|
-
"role",
|
|
571
|
-
"lang",
|
|
572
|
-
"target",
|
|
573
|
-
"rel",
|
|
574
|
-
"is",
|
|
575
|
-
"slot"
|
|
576
|
-
]);
|
|
577
|
-
function merge(into, result) {
|
|
578
|
-
into.usages.push(...result.usages);
|
|
579
|
-
into.keySlots.push(...result.keySlots ?? []);
|
|
580
|
-
into.hardStrings.push(...result.hardStrings ?? []);
|
|
581
|
-
}
|
|
582
|
-
var TemplateScan = class {
|
|
583
|
-
parse;
|
|
584
|
-
calls;
|
|
585
|
-
out = {
|
|
586
|
-
usages: [],
|
|
587
|
-
keySlots: [],
|
|
588
|
-
hardStrings: []
|
|
589
|
-
};
|
|
590
|
-
constructor(parse, calls) {
|
|
591
|
-
this.parse = parse;
|
|
592
|
-
this.calls = calls;
|
|
593
|
-
}
|
|
594
|
-
expression(content, offset) {
|
|
595
|
-
if (!content.trim()) return;
|
|
596
|
-
try {
|
|
597
|
-
merge(this.out, scanSource(this.parse, content, {
|
|
598
|
-
calls: this.calls,
|
|
599
|
-
offset,
|
|
600
|
-
filename: "template.ts",
|
|
601
|
-
expression: true,
|
|
602
|
-
hardStrings: false
|
|
603
|
-
}));
|
|
604
|
-
} catch {}
|
|
605
|
-
}
|
|
606
|
-
/** `v-t="'key'"` and `v-t="{ path: 'key' }"`. */
|
|
607
|
-
vt(dir) {
|
|
608
|
-
const exp = dir.exp;
|
|
609
|
-
if (!exp || !("content" in exp)) return;
|
|
610
|
-
const offset = exp.loc.start.offset;
|
|
611
|
-
const literal = /^\s*(['"`])(.*)\1\s*$/.exec(exp.content) ?? /path\s*:\s*(['"`])(.*?)\1/.exec(exp.content);
|
|
612
|
-
if (!literal) return;
|
|
613
|
-
const start = offset + exp.content.indexOf(literal[2], literal.index);
|
|
614
|
-
const range = {
|
|
615
|
-
start,
|
|
616
|
-
end: start + literal[2].length
|
|
617
|
-
};
|
|
618
|
-
this.out.usages.push({
|
|
619
|
-
raw: literal[2],
|
|
620
|
-
range,
|
|
621
|
-
kind: "call",
|
|
622
|
-
dynamic: false
|
|
623
|
-
});
|
|
624
|
-
this.out.keySlots.push(range);
|
|
625
|
-
}
|
|
626
|
-
attribute(element, prop) {
|
|
627
|
-
if (prop.type === ATTRIBUTE) {
|
|
628
|
-
const value = prop.value;
|
|
629
|
-
if (!value) return;
|
|
630
|
-
const range = {
|
|
631
|
-
start: value.loc.start.offset + 1,
|
|
632
|
-
end: value.loc.end.offset - 1
|
|
633
|
-
};
|
|
634
|
-
if (keyPathComponents.get(element.tag) === prop.name) {
|
|
635
|
-
this.out.usages.push({
|
|
636
|
-
raw: value.content,
|
|
637
|
-
range,
|
|
638
|
-
kind: "call",
|
|
639
|
-
dynamic: false
|
|
640
|
-
});
|
|
641
|
-
this.out.keySlots.push(range);
|
|
642
|
-
} else if (!ignoredAttributes.has(prop.name) && !prop.name.startsWith("data-") && /\p{L}/u.test(value.content)) this.out.hardStrings.push({
|
|
643
|
-
text: value.content,
|
|
644
|
-
range: {
|
|
645
|
-
start: prop.loc.start.offset,
|
|
646
|
-
end: prop.loc.end.offset
|
|
647
|
-
},
|
|
648
|
-
kind: "vue-attribute",
|
|
649
|
-
attribute: prop.name
|
|
650
|
-
});
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
if (prop.name === "t") {
|
|
654
|
-
this.vt(prop);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
const exp = prop.exp;
|
|
658
|
-
if (exp && "content" in exp) {
|
|
659
|
-
if (prop.name === "bind" && prop.arg && "content" in prop.arg && keyPathComponents.get(element.tag) === prop.arg.content && /^\s*(['"`]).*\1\s*$/.test(exp.content)) {
|
|
660
|
-
const start = exp.loc.start.offset + exp.content.search(/['"`]/) + 1;
|
|
661
|
-
const raw = exp.content.trim().slice(1, -1);
|
|
662
|
-
this.out.usages.push({
|
|
663
|
-
raw,
|
|
664
|
-
range: {
|
|
665
|
-
start,
|
|
666
|
-
end: start + raw.length
|
|
667
|
-
},
|
|
668
|
-
kind: "call",
|
|
669
|
-
dynamic: false
|
|
670
|
-
});
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
this.expression(exp.content, exp.loc.start.offset);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
node(node) {
|
|
677
|
-
if (node.type === INTERPOLATION && "content" in node.content) this.expression(node.content.content, node.content.loc.start.offset);
|
|
678
|
-
if (node.type === TEXT) {
|
|
679
|
-
const text = node.content.trim();
|
|
680
|
-
if (text && /\p{L}/u.test(text)) {
|
|
681
|
-
const start = node.loc.start.offset + node.content.indexOf(text);
|
|
682
|
-
this.out.hardStrings.push({
|
|
683
|
-
text,
|
|
684
|
-
range: {
|
|
685
|
-
start,
|
|
686
|
-
end: start + text.length
|
|
687
|
-
},
|
|
688
|
-
kind: "vue-template-text"
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
if (node.type === ELEMENT) for (const prop of node.props) this.attribute(node, prop);
|
|
693
|
-
if ("children" in node) {
|
|
694
|
-
for (const child of node.children) if (typeof child === "object" && child.type !== SIMPLE_EXPRESSION) this.node(child);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
/** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
|
|
699
|
-
function vue() {
|
|
700
|
-
return {
|
|
701
|
-
name: "x18n:scanner-vue",
|
|
702
|
-
scanners: [{
|
|
703
|
-
id: "vue",
|
|
704
|
-
extensions: ["vue"],
|
|
705
|
-
async scan(file, ctx) {
|
|
706
|
-
const [{ parse }, parseJs] = await Promise.all([loadVueCompiler(), loadParser()]);
|
|
707
|
-
const { descriptor } = parse(file.text, {
|
|
708
|
-
filename: file.path,
|
|
709
|
-
sourceMap: false,
|
|
710
|
-
ignoreEmpty: false
|
|
711
|
-
});
|
|
712
|
-
const template = new TemplateScan(parseJs, ctx.calls);
|
|
713
|
-
for (const script of [descriptor.script, descriptor.scriptSetup]) {
|
|
714
|
-
if (!script) continue;
|
|
715
|
-
const lang = script.lang ?? "js";
|
|
716
|
-
merge(template.out, scanSource(parseJs, script.content, {
|
|
717
|
-
calls: ctx.calls,
|
|
718
|
-
ignoreCallees: ctx.config.extract.ignoreCallees,
|
|
719
|
-
offset: script.loc.start.offset,
|
|
720
|
-
filename: `script.${lang === "tsx" || lang === "jsx" ? lang : lang === "ts" ? "ts" : "js"}`
|
|
721
|
-
}));
|
|
722
|
-
}
|
|
723
|
-
if (descriptor.template?.ast) template.node(descriptor.template.ast);
|
|
724
|
-
return template.out;
|
|
725
|
-
}
|
|
726
|
-
}]
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
//#endregion
|
|
730
|
-
export { loadParser as a, js as i, vue as n, scanSource as o, calleeName as r, loadVueCompiler as t };
|