eslint-plugin-formatjs 6.6.3 → 7.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +666 -124
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -16,6 +16,56 @@ var __exportAll = (all, no_symbols) => {
|
|
|
16
16
|
return target;
|
|
17
17
|
};
|
|
18
18
|
//#endregion
|
|
19
|
+
//#region packages/eslint-plugin-formatjs/message-types.ts
|
|
20
|
+
function collectMessageArguments(ast) {
|
|
21
|
+
const argumentsByName = /* @__PURE__ */ new Map();
|
|
22
|
+
function visit(elements) {
|
|
23
|
+
for (const element of elements) {
|
|
24
|
+
if (element.type === TYPE.literal || element.type === TYPE.pound) continue;
|
|
25
|
+
const types = argumentsByName.get(element.value) ?? /* @__PURE__ */ new Set();
|
|
26
|
+
argumentsByName.set(element.value, types);
|
|
27
|
+
switch (element.type) {
|
|
28
|
+
case TYPE.argument: break;
|
|
29
|
+
case TYPE.number:
|
|
30
|
+
case TYPE.plural:
|
|
31
|
+
types.add("number");
|
|
32
|
+
break;
|
|
33
|
+
case TYPE.date:
|
|
34
|
+
case TYPE.time:
|
|
35
|
+
types.add("date");
|
|
36
|
+
break;
|
|
37
|
+
case TYPE.select:
|
|
38
|
+
types.add("select");
|
|
39
|
+
break;
|
|
40
|
+
case TYPE.tag:
|
|
41
|
+
types.add("tag");
|
|
42
|
+
visit(element.children);
|
|
43
|
+
}
|
|
44
|
+
if (element.type === TYPE.select || element.type === TYPE.plural) for (const option of Object.values(element.options)) visit(option.value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
visit(ast);
|
|
48
|
+
return argumentsByName;
|
|
49
|
+
}
|
|
50
|
+
/** Derive one contract from the runtime parser's AST, including every branch. */
|
|
51
|
+
function messageTypes(ast, module, ignoreList = []) {
|
|
52
|
+
const argumentsByName = collectMessageArguments(ast);
|
|
53
|
+
const ignored = new Set(ignoreList);
|
|
54
|
+
for (const name of ignored) if (!argumentsByName.has(name)) argumentsByName.set(name, /* @__PURE__ */ new Set());
|
|
55
|
+
const fields = [...argumentsByName].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([name, types]) => {
|
|
56
|
+
let type;
|
|
57
|
+
if (!types.size) type = `import(${JSON.stringify(module)}).MessageValue`;
|
|
58
|
+
else if (types.size === 2 && types.has("number") && types.has("date")) type = "number";
|
|
59
|
+
else if (types.size > 1) throw new Error(`Incompatible uses of argument ${JSON.stringify(name)}`);
|
|
60
|
+
else if (types.has("number")) type = "number | bigint";
|
|
61
|
+
else if (types.has("date")) type = "number | Date";
|
|
62
|
+
else if (types.has("select")) type = "string";
|
|
63
|
+
else type = `import(${JSON.stringify(module)}).MessageTag`;
|
|
64
|
+
return `readonly ${JSON.stringify(name)}${ignored.has(name) ? "?" : ""}: ${type}`;
|
|
65
|
+
});
|
|
66
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
19
69
|
//#region packages/eslint-plugin-formatjs/util.ts
|
|
20
70
|
const FORMAT_FUNCTION_NAMES = /* @__PURE__ */ new Set([
|
|
21
71
|
"$formatMessage",
|
|
@@ -220,11 +270,605 @@ function patchMessage(messageNode, ast, patcher) {
|
|
|
220
270
|
return null;
|
|
221
271
|
}
|
|
222
272
|
//#endregion
|
|
273
|
+
//#region packages/eslint-plugin-formatjs/placeholder-checks.ts
|
|
274
|
+
function messageIgnoreTag(context, node) {
|
|
275
|
+
let ignoreTag = getSettings(context).ignoreTag ?? false;
|
|
276
|
+
if (node.type !== "CallExpression" || !node.arguments[2]) return ignoreTag;
|
|
277
|
+
const options = node.arguments[2];
|
|
278
|
+
if (options.type !== "ObjectExpression") return;
|
|
279
|
+
for (const property of options.properties) {
|
|
280
|
+
if (property.type !== "Property" || property.computed || property.method || property.kind !== "init") return;
|
|
281
|
+
if ((property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? property.key.value : void 0) !== "ignoreTag") continue;
|
|
282
|
+
if (property.value.type !== "Literal" || typeof property.value.value !== "boolean") return;
|
|
283
|
+
ignoreTag = property.value.value;
|
|
284
|
+
}
|
|
285
|
+
return ignoreTag;
|
|
286
|
+
}
|
|
287
|
+
function checkPlaceholders(context, node) {
|
|
288
|
+
const settings = getSettings(context);
|
|
289
|
+
const ignoreTag = messageIgnoreTag(context, node);
|
|
290
|
+
if (ignoreTag === void 0) return;
|
|
291
|
+
const msgs = extractMessages(node, {
|
|
292
|
+
excludeMessageDeclCalls: true,
|
|
293
|
+
...settings
|
|
294
|
+
});
|
|
295
|
+
const { options: [opt] } = context;
|
|
296
|
+
const ignoreList = new Set(opt?.ignoreList || []);
|
|
297
|
+
for (const [{ message: { defaultMessage }, messageNode }, values] of msgs) {
|
|
298
|
+
if (!defaultMessage || !messageNode) continue;
|
|
299
|
+
if (values && values.type !== "ObjectExpression") continue;
|
|
300
|
+
if (values?.properties.find((prop) => prop.type === "SpreadElement")) continue;
|
|
301
|
+
const literalElementByLiteralKey = /* @__PURE__ */ new Map();
|
|
302
|
+
if (values) {
|
|
303
|
+
for (const prop of values.properties) if (prop.type === "Property" && !prop.computed) {
|
|
304
|
+
const name = prop.key.type === "Identifier" ? prop.key.name : String(prop.key.value);
|
|
305
|
+
literalElementByLiteralKey.set(name, prop);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
let ast;
|
|
309
|
+
try {
|
|
310
|
+
ast = parse(defaultMessage, { ignoreTag });
|
|
311
|
+
} catch (e) {
|
|
312
|
+
context.report({
|
|
313
|
+
node: messageNode,
|
|
314
|
+
messageId: "parseError",
|
|
315
|
+
data: { error: e instanceof Error ? e.message : String(e) }
|
|
316
|
+
});
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const placeholderNames = new Set(collectMessageArguments(ast).keys());
|
|
320
|
+
const missingPlaceholders = [];
|
|
321
|
+
placeholderNames.forEach((name) => {
|
|
322
|
+
if (!ignoreList.has(name) && !literalElementByLiteralKey.has(name)) missingPlaceholders.push(name);
|
|
323
|
+
});
|
|
324
|
+
if (missingPlaceholders.length > 0) context.report({
|
|
325
|
+
node: messageNode,
|
|
326
|
+
messageId: "missingValue",
|
|
327
|
+
data: { list: missingPlaceholders.join(", ") }
|
|
328
|
+
});
|
|
329
|
+
literalElementByLiteralKey.forEach((element, key) => {
|
|
330
|
+
if (!ignoreList.has(key) && !placeholderNames.has(key)) context.report({
|
|
331
|
+
node: element,
|
|
332
|
+
messageId: "unusedValue"
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
223
338
|
//#region packages/eslint-plugin-formatjs/messages.ts
|
|
224
339
|
const CORE_MESSAGES = { parseError: `Failed to parse message string {{error}}` };
|
|
225
340
|
//#endregion
|
|
341
|
+
//#region packages/eslint-plugin-formatjs/rules/enforce-placeholders.ts
|
|
342
|
+
const name$23 = "enforce-placeholders";
|
|
343
|
+
const rule$21 = {
|
|
344
|
+
meta: {
|
|
345
|
+
type: "problem",
|
|
346
|
+
docs: {
|
|
347
|
+
description: "Enforce that all messages with placeholders have enough passed-in values",
|
|
348
|
+
url: "https://formatjs.github.io/docs/tooling/linter#enforce-placeholders"
|
|
349
|
+
},
|
|
350
|
+
schema: [{
|
|
351
|
+
type: "object",
|
|
352
|
+
properties: { ignoreList: {
|
|
353
|
+
type: "array",
|
|
354
|
+
items: { type: "string" }
|
|
355
|
+
} },
|
|
356
|
+
additionalProperties: false
|
|
357
|
+
}],
|
|
358
|
+
messages: {
|
|
359
|
+
...CORE_MESSAGES,
|
|
360
|
+
missingValue: "Missing value(s) for the following placeholder(s): {{list}}.",
|
|
361
|
+
unusedValue: "Value not used by the message."
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
create(context) {
|
|
365
|
+
const callExpressionVisitor = (node) => checkPlaceholders(context, node);
|
|
366
|
+
const parserServices = context.sourceCode.parserServices;
|
|
367
|
+
if (parserServices?.defineTemplateBodyVisitor) return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
368
|
+
return {
|
|
369
|
+
JSXOpeningElement: (node) => checkPlaceholders(context, node),
|
|
370
|
+
CallExpression: callExpressionVisitor
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region packages/eslint-plugin-formatjs/rules/enforce-message-types.ts
|
|
376
|
+
const name$22 = "enforce-message-types";
|
|
377
|
+
const modules = /* @__PURE__ */ new Set([
|
|
378
|
+
"@formatjs/intl",
|
|
379
|
+
"react-intl",
|
|
380
|
+
"react-intl/server",
|
|
381
|
+
"intl-messageformat"
|
|
382
|
+
]);
|
|
383
|
+
function staticObject(node) {
|
|
384
|
+
return node?.type === "ObjectExpression" && node.properties.every((property) => property.type === "Property" && !property.computed && property.kind === "init" && !property.method);
|
|
385
|
+
}
|
|
386
|
+
function staticMessage(node) {
|
|
387
|
+
if (!node) return false;
|
|
388
|
+
if (node.type === "Literal") return typeof node.value === "string";
|
|
389
|
+
if (node.type === "TemplateLiteral") return node.expressions.length === 0;
|
|
390
|
+
if (node.type === "BinaryExpression") return node.operator === "+" && staticMessage(node.left) && staticMessage(node.right);
|
|
391
|
+
const wrapper = node;
|
|
392
|
+
if ([
|
|
393
|
+
"TSAsExpression",
|
|
394
|
+
"TSSatisfiesExpression",
|
|
395
|
+
"TSNonNullExpression",
|
|
396
|
+
"TSTypeAssertion"
|
|
397
|
+
].includes(wrapper.type)) return staticMessage(wrapper.expression);
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
function importedHelper(context, node) {
|
|
401
|
+
const callee = node.callee;
|
|
402
|
+
const identifier = callee.type === "Identifier" ? callee : callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" ? callee.object : void 0;
|
|
403
|
+
if (!identifier) return;
|
|
404
|
+
let scope = context.sourceCode.getScope(node);
|
|
405
|
+
while (scope) {
|
|
406
|
+
const variable = scope.set.get(identifier.name);
|
|
407
|
+
if (variable) {
|
|
408
|
+
const definition = variable.defs[0];
|
|
409
|
+
if (definition?.type !== "ImportBinding") return;
|
|
410
|
+
const module = definition.parent.source.value;
|
|
411
|
+
if (typeof module !== "string" || !modules.has(module)) return;
|
|
412
|
+
const specifier = definition.node;
|
|
413
|
+
let helper;
|
|
414
|
+
if (callee.type === "Identifier" && specifier.type === "ImportSpecifier") helper = specifier.imported.type === "Identifier" ? specifier.imported.name : String(specifier.imported.value);
|
|
415
|
+
else if (callee.type === "MemberExpression" && callee.property.type === "Identifier" && specifier.type === "ImportNamespaceSpecifier") helper = callee.property.name;
|
|
416
|
+
if (node.type === "NewExpression" && module === "intl-messageformat") {
|
|
417
|
+
if (callee.type === "Identifier" && specifier.type === "ImportDefaultSpecifier" || helper === "IntlMessageFormat" || helper === "default") return {
|
|
418
|
+
module,
|
|
419
|
+
helper: "IntlMessageFormat"
|
|
420
|
+
};
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (module === "intl-messageformat") return;
|
|
424
|
+
if (helper === "defineMessage" || helper === "defineMessages") return {
|
|
425
|
+
module,
|
|
426
|
+
helper
|
|
427
|
+
};
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
scope = scope.upper;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function staticString(node) {
|
|
434
|
+
if (!node) return;
|
|
435
|
+
if (node.type === "Literal") return typeof node.value === "string" ? node.value : void 0;
|
|
436
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked ?? void 0;
|
|
437
|
+
if (node.type === "BinaryExpression" && node.operator === "+") {
|
|
438
|
+
const left = staticString(node.left);
|
|
439
|
+
const right = staticString(node.right);
|
|
440
|
+
if (left !== void 0 && right !== void 0) return left + right;
|
|
441
|
+
}
|
|
442
|
+
const wrapper = node;
|
|
443
|
+
if ([
|
|
444
|
+
"TSAsExpression",
|
|
445
|
+
"TSSatisfiesExpression",
|
|
446
|
+
"TSNonNullExpression",
|
|
447
|
+
"TSTypeAssertion"
|
|
448
|
+
].includes(wrapper.type)) return staticString(wrapper.expression);
|
|
449
|
+
}
|
|
450
|
+
function renderType(node) {
|
|
451
|
+
if (!node) return;
|
|
452
|
+
const keywords = {
|
|
453
|
+
TSNumberKeyword: "number",
|
|
454
|
+
TSBigIntKeyword: "bigint",
|
|
455
|
+
TSStringKeyword: "string"
|
|
456
|
+
};
|
|
457
|
+
if (keywords[node.type]) return keywords[node.type];
|
|
458
|
+
if (node.type === "TSTypeReference" && node.typeName?.type === "Identifier") {
|
|
459
|
+
const parameters = (node.typeArguments ?? node.typeParameters)?.params;
|
|
460
|
+
if (!parameters) return node.typeName.name;
|
|
461
|
+
const types = parameters.map(renderType);
|
|
462
|
+
if (types.every((type) => type !== void 0)) return node.typeName.name + "<" + types.join(", ") + ">";
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (node.type === "TSUnionType") {
|
|
466
|
+
const types = node.types?.map(renderType);
|
|
467
|
+
if (types?.every((type) => type !== void 0)) return types.join(" | ");
|
|
468
|
+
}
|
|
469
|
+
if (node.type === "TSImportType" && node.qualifier?.type === "Identifier") {
|
|
470
|
+
const module = node.source?.value ?? node.argument?.literal?.value ?? node.argument?.value;
|
|
471
|
+
if (typeof module === "string") return `import(${JSON.stringify(module)}).${node.qualifier.name}`;
|
|
472
|
+
}
|
|
473
|
+
if (node.type === "TSTypeLiteral" && node.members) {
|
|
474
|
+
const fields = [];
|
|
475
|
+
for (const member of node.members) {
|
|
476
|
+
if (member.type !== "TSPropertySignature" || member.computed) return;
|
|
477
|
+
const key = member.key?.type === "Identifier" ? member.key.name : member.key?.value;
|
|
478
|
+
const type = renderType(member.typeAnnotation?.typeAnnotation);
|
|
479
|
+
if (key === void 0 || type === void 0) return;
|
|
480
|
+
fields.push(`${member.readonly ? "readonly " : ""}${JSON.stringify(String(key))}${member.optional ? "?" : ""}: ${type}`);
|
|
481
|
+
}
|
|
482
|
+
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function hoistTypes(context, node, contract) {
|
|
486
|
+
const source = context.sourceCode;
|
|
487
|
+
const imports = /* @__PURE__ */ new Map();
|
|
488
|
+
const occupied = new Set(source.scopeManager?.scopes.flatMap((scope) => scope.variables.map((variable) => variable.name)));
|
|
489
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
490
|
+
return {
|
|
491
|
+
text: contract.replace(/import\(("(?:[^"\\]|\\.)*")\)\.(MessageTag|MessageValue|TypedMessageDescriptor)/g, (reference, quotedModule, imported) => {
|
|
492
|
+
if (resolved.has(reference)) return resolved.get(reference);
|
|
493
|
+
const module = JSON.parse(quotedModule);
|
|
494
|
+
for (const declaration of source.ast.body) {
|
|
495
|
+
if (declaration.type !== "ImportDeclaration" || declaration.source.value !== module) continue;
|
|
496
|
+
for (const specifier of declaration.specifiers) {
|
|
497
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
498
|
+
if ((specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== imported) continue;
|
|
499
|
+
let scope = source.getScope(node);
|
|
500
|
+
while (scope && !scope.set.has(specifier.local.name)) scope = scope.upper;
|
|
501
|
+
if (scope?.set.get(specifier.local.name)?.defs.some((def) => def.node === specifier)) {
|
|
502
|
+
resolved.set(reference, specifier.local.name);
|
|
503
|
+
return specifier.local.name;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
let local = imported;
|
|
508
|
+
for (let suffix = 1; occupied.has(local); suffix++) local = imported + suffix;
|
|
509
|
+
occupied.add(local);
|
|
510
|
+
const specifiers = imports.get(quotedModule) ?? [];
|
|
511
|
+
specifiers.push(imported + (local === imported ? "" : " as " + local));
|
|
512
|
+
imports.set(quotedModule, specifiers);
|
|
513
|
+
resolved.set(reference, local);
|
|
514
|
+
return local;
|
|
515
|
+
}),
|
|
516
|
+
fix(fixer) {
|
|
517
|
+
if (!imports.size) return [];
|
|
518
|
+
let anchor = source.ast.body.filter((statement) => statement.type === "ImportDeclaration").at(-1);
|
|
519
|
+
if (!anchor) for (const statement of source.ast.body) {
|
|
520
|
+
if (statement.type !== "ExpressionStatement" || statement.expression.type !== "Literal" || typeof statement.expression.value !== "string") break;
|
|
521
|
+
anchor = statement;
|
|
522
|
+
}
|
|
523
|
+
const block = [...imports].map(([module, specifiers]) => "import type {" + specifiers.join(", ") + "} from " + module + ";").join("\n");
|
|
524
|
+
return anchor ? [fixer.insertTextAfter(anchor, "\n" + block)] : [fixer.insertTextBefore(source.ast.body[0], block + "\n")];
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function catalogAnnotation(context, node) {
|
|
529
|
+
const parent = node.parent;
|
|
530
|
+
if (parent?.type !== "VariableDeclarator" || parent.init !== node) return;
|
|
531
|
+
const annotation = parent.id.typeAnnotation?.typeAnnotation;
|
|
532
|
+
if (!annotation) return;
|
|
533
|
+
function imported(type, name) {
|
|
534
|
+
if (type.type !== "TSTypeReference" || type.typeName?.type !== "Identifier") return false;
|
|
535
|
+
let scope = context.sourceCode.getScope(node);
|
|
536
|
+
while (scope) {
|
|
537
|
+
const binding = scope.set.get(type.typeName.name);
|
|
538
|
+
if (binding) {
|
|
539
|
+
const definition = binding.defs[0];
|
|
540
|
+
return definition?.type === "ImportBinding" && descriptorModules.has(String(definition.parent.source.value)) && definition.node.type === "ImportSpecifier" && (definition.node.imported.type === "Identifier" ? definition.node.imported.name : definition.node.imported.value) === name;
|
|
541
|
+
}
|
|
542
|
+
scope = scope.upper;
|
|
543
|
+
}
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
function broad(type) {
|
|
547
|
+
if (type.type === "TSTypeLiteral") return !!type.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "MessageDescriptor"));
|
|
548
|
+
let scope = context.sourceCode.getScope(node);
|
|
549
|
+
while (scope) {
|
|
550
|
+
if (scope.set.get(type.typeName?.name ?? "")?.defs.length) return false;
|
|
551
|
+
scope = scope.upper;
|
|
552
|
+
}
|
|
553
|
+
const parameters = (type.typeArguments ?? type.typeParameters)?.params;
|
|
554
|
+
if (type.typeName?.name === "Readonly" && parameters?.length === 1) return broad(parameters[0]);
|
|
555
|
+
return type.typeName?.name === "Record" && parameters?.length === 2 && imported(parameters[1], "MessageDescriptor");
|
|
556
|
+
}
|
|
557
|
+
function typedMap(type) {
|
|
558
|
+
return type.type === "TSTypeLiteral" && !!type.members?.every((member) => member.type === "TSPropertySignature" && !member.computed && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"));
|
|
559
|
+
}
|
|
560
|
+
let base = annotation;
|
|
561
|
+
let generated = typedMap(annotation) ? annotation : void 0;
|
|
562
|
+
if (annotation.type === "TSIntersectionType" && annotation.types?.length === 2) {
|
|
563
|
+
const [left, right] = annotation.types;
|
|
564
|
+
if (right.type === "TSTypeLiteral" && right.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"))) {
|
|
565
|
+
base = left;
|
|
566
|
+
generated = right;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
annotation,
|
|
571
|
+
base,
|
|
572
|
+
generated,
|
|
573
|
+
supported: broad(base) || typedMap(base)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const descriptorModules = /* @__PURE__ */ new Set([
|
|
577
|
+
"@formatjs/intl",
|
|
578
|
+
"react-intl",
|
|
579
|
+
"react-intl/server"
|
|
580
|
+
]);
|
|
581
|
+
function checkInlineMessage(context, node) {
|
|
582
|
+
if (node.type !== "CallExpression") return;
|
|
583
|
+
const settings = getSettings(context);
|
|
584
|
+
if (node.callee.type === "MemberExpression" && node.callee.computed) return;
|
|
585
|
+
if (!isIntlFormatMessageCall({
|
|
586
|
+
...node,
|
|
587
|
+
arguments: [{
|
|
588
|
+
type: "ObjectExpression",
|
|
589
|
+
properties: []
|
|
590
|
+
}]
|
|
591
|
+
}, settings.additionalFunctionNames)) return;
|
|
592
|
+
const source = context.sourceCode;
|
|
593
|
+
const call = node;
|
|
594
|
+
const generic = call.typeArguments ?? call.typeParameters;
|
|
595
|
+
if (!generic && !context.options[0]?.generateTypes) return;
|
|
596
|
+
const generated = !!generic && /^<\s*\/\* @formatjs-generated \*\//.test(source.getText(generic));
|
|
597
|
+
const descriptor = node.arguments[0];
|
|
598
|
+
if (!staticObject(descriptor)) {
|
|
599
|
+
if (generic) context.report({
|
|
600
|
+
node,
|
|
601
|
+
messageId: "dynamic"
|
|
602
|
+
});
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const existingModule = source.ast.body.find((statement) => statement.type === "ImportDeclaration" && typeof statement.source.value === "string" && descriptorModules.has(statement.source.value));
|
|
606
|
+
const module = context.options[0]?.moduleSource ?? (existingModule?.type === "ImportDeclaration" ? String(existingModule.source.value) : "@formatjs/intl");
|
|
607
|
+
const ignoreTag = messageIgnoreTag(context, node);
|
|
608
|
+
if (ignoreTag === void 0) {
|
|
609
|
+
if (generic) context.report({
|
|
610
|
+
node,
|
|
611
|
+
messageId: "dynamic"
|
|
612
|
+
});
|
|
613
|
+
return !!generic;
|
|
614
|
+
}
|
|
615
|
+
const messages = extractMessages(node, settings);
|
|
616
|
+
const message = messages[0]?.[0];
|
|
617
|
+
if (messages.length !== 1 || !message || typeof message.message.defaultMessage !== "string" || !staticMessage(message.messageNode ?? void 0)) {
|
|
618
|
+
if (generic) context.report({
|
|
619
|
+
node,
|
|
620
|
+
messageId: "dynamic"
|
|
621
|
+
});
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
let contract;
|
|
625
|
+
try {
|
|
626
|
+
contract = messageTypes(parse(message.message.defaultMessage, { ignoreTag }), module, context.options[0]?.ignoreList);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
context.report({
|
|
629
|
+
node,
|
|
630
|
+
messageId: "invalid",
|
|
631
|
+
data: { error: error instanceof Error ? error.message : String(error) }
|
|
632
|
+
});
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
const imports = hoistTypes(context, node, contract);
|
|
636
|
+
contract = imports.text;
|
|
637
|
+
const parameters = generic?.params;
|
|
638
|
+
if (parameters && parameters.length >= 1 && parameters.length <= 2 && renderType(parameters[0]) === contract && !generated) return true;
|
|
639
|
+
if (generic && (!parameters || parameters.length > 2)) {
|
|
640
|
+
context.report({
|
|
641
|
+
node: generic,
|
|
642
|
+
messageId: "manual"
|
|
643
|
+
});
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
context.report({
|
|
647
|
+
node,
|
|
648
|
+
messageId: "contract",
|
|
649
|
+
fix(fixer) {
|
|
650
|
+
return [...imports.fix(fixer), generic && parameters?.[0] ? fixer.replaceTextRange([generic.range[0] + 1, parameters[0].range[1]], contract) : fixer.insertTextAfter(node.optional ? source.getTokenAfter(node.callee) : node.callee, "<" + contract + ">")];
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
const rule$20 = {
|
|
656
|
+
meta: {
|
|
657
|
+
type: "problem",
|
|
658
|
+
docs: { description: "Check ICU placeholders and optionally generate TypeScript argument contracts" },
|
|
659
|
+
fixable: "code",
|
|
660
|
+
schema: [{
|
|
661
|
+
type: "object",
|
|
662
|
+
properties: {
|
|
663
|
+
generateTypes: { type: "boolean" },
|
|
664
|
+
ignoreList: {
|
|
665
|
+
type: "array",
|
|
666
|
+
items: { type: "string" }
|
|
667
|
+
},
|
|
668
|
+
moduleSource: {
|
|
669
|
+
type: "string",
|
|
670
|
+
enum: [...descriptorModules]
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
additionalProperties: false
|
|
674
|
+
}],
|
|
675
|
+
messages: {
|
|
676
|
+
...rule$21.meta.messages,
|
|
677
|
+
contract: "Generate or refresh the ICU argument contract.",
|
|
678
|
+
invalid: "Cannot generate message types: {{error}}.",
|
|
679
|
+
manual: "Unsupported number of generic arguments; update the call manually.",
|
|
680
|
+
annotation: "Catalog annotation may erase ICU contracts; use per-message TypedMessageDescriptor types.",
|
|
681
|
+
dynamic: "Typed messages require static message strings and parser options for contract verification."
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
create(context) {
|
|
685
|
+
const listeners = {
|
|
686
|
+
JSXOpeningElement(node) {
|
|
687
|
+
checkPlaceholders(context, node);
|
|
688
|
+
},
|
|
689
|
+
NewExpression(node) {
|
|
690
|
+
if (node.type !== "NewExpression" || !/\.[cm]?tsx?$/.test(context.filename)) return;
|
|
691
|
+
const imported = importedHelper(context, node);
|
|
692
|
+
if (imported?.helper !== "IntlMessageFormat") return;
|
|
693
|
+
const source = context.sourceCode;
|
|
694
|
+
const call = node;
|
|
695
|
+
const generic = call.typeArguments ?? call.typeParameters;
|
|
696
|
+
if (!generic && !context.options[0]?.generateTypes) return;
|
|
697
|
+
const generated = !!generic && /^<\s*\/\* @formatjs-generated \*\//.test(source.getText(generic));
|
|
698
|
+
const message = staticString(node.arguments[0]);
|
|
699
|
+
const options = node.arguments[3];
|
|
700
|
+
let ignoreTag = false;
|
|
701
|
+
if (message === void 0 || node.arguments.some((argument) => argument.type === "SpreadElement") || options && !staticObject(options)) {
|
|
702
|
+
if (generic) context.report({
|
|
703
|
+
node,
|
|
704
|
+
messageId: "dynamic"
|
|
705
|
+
});
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (staticObject(options)) for (const property of options.properties) {
|
|
709
|
+
if (property.type !== "Property") continue;
|
|
710
|
+
if ((property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? property.key.value : void 0) !== "ignoreTag") continue;
|
|
711
|
+
if (property.value.type !== "Literal" || typeof property.value.value !== "boolean") {
|
|
712
|
+
if (generic) context.report({
|
|
713
|
+
node,
|
|
714
|
+
messageId: "dynamic"
|
|
715
|
+
});
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
ignoreTag = property.value.value;
|
|
719
|
+
}
|
|
720
|
+
let contract;
|
|
721
|
+
try {
|
|
722
|
+
contract = messageTypes(parse(message, { ignoreTag }), imported.module, context.options[0]?.ignoreList);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
context.report({
|
|
725
|
+
node,
|
|
726
|
+
messageId: "invalid",
|
|
727
|
+
data: { error: error instanceof Error ? error.message : String(error) }
|
|
728
|
+
});
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const imports = hoistTypes(context, node, contract);
|
|
732
|
+
contract = imports.text;
|
|
733
|
+
const parameters = generic?.params;
|
|
734
|
+
if (parameters?.length === 1 && renderType(parameters[0]) === contract && !generated) return;
|
|
735
|
+
if (generic && parameters?.length !== 1) {
|
|
736
|
+
context.report({
|
|
737
|
+
node: generic,
|
|
738
|
+
messageId: "manual"
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
context.report({
|
|
743
|
+
node,
|
|
744
|
+
messageId: "contract",
|
|
745
|
+
fix(fixer) {
|
|
746
|
+
const expected = `<${contract}>`;
|
|
747
|
+
return [...imports.fix(fixer), generic ? fixer.replaceText(generic, expected) : fixer.insertTextAfter(node.callee, expected)];
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
CallExpression(node) {
|
|
752
|
+
if (node.type !== "CallExpression") return;
|
|
753
|
+
if (!/\.[cm]?tsx?$/.test(context.filename)) {
|
|
754
|
+
checkPlaceholders(context, node);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const imported = importedHelper(context, node);
|
|
758
|
+
if (!imported) {
|
|
759
|
+
if (!checkInlineMessage(context, node)) checkPlaceholders(context, node);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const source = context.sourceCode;
|
|
763
|
+
const call = node;
|
|
764
|
+
const generic = call.typeArguments ?? call.typeParameters;
|
|
765
|
+
const generated = !!generic && /^<\s*\/\* @formatjs-generated \*\//.test(source.getText(generic));
|
|
766
|
+
const options = node.arguments[1];
|
|
767
|
+
const typed = staticObject(options) && options.properties.length === 1 && options.properties.some((p) => p.type === "Property" && (p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? p.key.value : void 0) === "typed" && p.value.type === "Literal" && p.value.value === true);
|
|
768
|
+
if (!typed && !generated && !context.options[0]?.generateTypes) return;
|
|
769
|
+
if (!typed && generic && !generated && !context.options[0]?.generateTypes) return;
|
|
770
|
+
if (node.arguments.length > 2 || options && !typed) return;
|
|
771
|
+
const descriptor = node.arguments[0];
|
|
772
|
+
if (!staticObject(descriptor)) {
|
|
773
|
+
if (typed || generated) context.report({
|
|
774
|
+
node,
|
|
775
|
+
messageId: "dynamic"
|
|
776
|
+
});
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (imported.helper === "defineMessages" && !descriptor.properties.every((p) => p.type === "Property" && staticObject(p.value))) {
|
|
780
|
+
if (typed || generated) context.report({
|
|
781
|
+
node,
|
|
782
|
+
messageId: "dynamic"
|
|
783
|
+
});
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const messages = extractMessages({
|
|
787
|
+
...node,
|
|
788
|
+
callee: {
|
|
789
|
+
type: "Identifier",
|
|
790
|
+
name: imported.helper
|
|
791
|
+
}
|
|
792
|
+
}, getSettings(context));
|
|
793
|
+
if (messages.some(([message]) => typeof message.message.defaultMessage !== "string" || !staticMessage(message.messageNode ?? void 0)) || messages.length !== (imported.helper === "defineMessage" ? 1 : descriptor.properties.length)) {
|
|
794
|
+
if (typed || generated) context.report({
|
|
795
|
+
node,
|
|
796
|
+
messageId: "dynamic"
|
|
797
|
+
});
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
const annotation = imported.helper === "defineMessages" ? catalogAnnotation(context, node) : void 0;
|
|
801
|
+
if (annotation && !annotation.supported) {
|
|
802
|
+
context.report({
|
|
803
|
+
node: annotation.annotation,
|
|
804
|
+
messageId: "annotation"
|
|
805
|
+
});
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
let contract;
|
|
809
|
+
let catalogType;
|
|
810
|
+
try {
|
|
811
|
+
const types = messages.map(([message]) => messageTypes(parse(message.message.defaultMessage, { ignoreTag: getSettings(context).ignoreTag }), imported.module, context.options[0]?.ignoreList));
|
|
812
|
+
if (annotation) {
|
|
813
|
+
catalogType = "{ " + descriptor.properties.map((property, index) => {
|
|
814
|
+
if (property.type !== "Property") throw new Error("Unexpected spread");
|
|
815
|
+
const key = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0;
|
|
816
|
+
if (key === void 0) throw new Error("Unsupported catalog key");
|
|
817
|
+
return "readonly " + JSON.stringify(key) + ": import(" + JSON.stringify(imported.module) + ").TypedMessageDescriptor<" + types[index] + ">";
|
|
818
|
+
}).join("; ") + " }";
|
|
819
|
+
if (!descriptor.properties.length) catalogType = "{}";
|
|
820
|
+
}
|
|
821
|
+
contract = imported.helper === "defineMessage" ? types[0] : descriptor.properties.length === 0 ? "{}" : `{ ${descriptor.properties.map((p, i) => {
|
|
822
|
+
if (p.type !== "Property") throw new Error("Unexpected spread");
|
|
823
|
+
const key = p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : void 0;
|
|
824
|
+
if (key === void 0) throw new Error("Unsupported catalog key");
|
|
825
|
+
return `readonly ${JSON.stringify(key)}: ${types[i]}`;
|
|
826
|
+
}).join("; ")} }`;
|
|
827
|
+
} catch (error) {
|
|
828
|
+
context.report({
|
|
829
|
+
node,
|
|
830
|
+
messageId: "invalid",
|
|
831
|
+
data: { error: error instanceof Error ? error.message : String(error) }
|
|
832
|
+
});
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const imports = hoistTypes(context, node, contract + (catalogType ? "\n" + catalogType : ""));
|
|
836
|
+
[contract, catalogType] = imports.text.split("\n");
|
|
837
|
+
const annotationMatches = !annotation || annotation.annotation === annotation.generated && renderType(annotation.generated) === catalogType;
|
|
838
|
+
const expected = `<${contract}>`;
|
|
839
|
+
const parameters = generic?.params;
|
|
840
|
+
if (parameters && parameters.length >= 1 && parameters.length <= 2 && renderType(parameters[0]) === contract && typed && !generated && annotationMatches) return;
|
|
841
|
+
if (generic && (!parameters || parameters.length < 1 || parameters.length > 2)) {
|
|
842
|
+
context.report({
|
|
843
|
+
node: generic,
|
|
844
|
+
messageId: "manual"
|
|
845
|
+
});
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
context.report({
|
|
849
|
+
node,
|
|
850
|
+
messageId: "contract",
|
|
851
|
+
fix(fixer) {
|
|
852
|
+
const edits = [...imports.fix(fixer), generic && parameters?.[0] ? fixer.replaceTextRange([generic.range[0] + 1, parameters[0].range[1]], contract) : fixer.insertTextAfter(node.optional ? source.getTokenAfter(node.callee) : node.callee, expected)];
|
|
853
|
+
if (annotation && !annotationMatches) edits.push(fixer.replaceText(annotation.annotation, catalogType));
|
|
854
|
+
if (!typed) {
|
|
855
|
+
const close = source.getLastToken(node);
|
|
856
|
+
const previous = source.getTokenBefore(close);
|
|
857
|
+
edits.push(fixer.insertTextBefore(close, `${previous.value === "," ? "" : ","} {typed: true}`));
|
|
858
|
+
}
|
|
859
|
+
return edits;
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
const parserServices = context.sourceCode.parserServices;
|
|
865
|
+
if (parserServices?.defineTemplateBodyVisitor) return parserServices.defineTemplateBodyVisitor({ CallExpression: listeners.CallExpression }, { CallExpression: listeners.CallExpression });
|
|
866
|
+
return listeners;
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
//#endregion
|
|
226
870
|
//#region packages/eslint-plugin-formatjs/rules/blocklist-elements.ts
|
|
227
|
-
const name$
|
|
871
|
+
const name$21 = "blocklist-elements";
|
|
228
872
|
function getMessage(type) {
|
|
229
873
|
return {
|
|
230
874
|
messageId: "blocklist",
|
|
@@ -277,7 +921,7 @@ function verifyAst$7(blocklist, ast) {
|
|
|
277
921
|
}
|
|
278
922
|
return errors;
|
|
279
923
|
}
|
|
280
|
-
function checkNode$
|
|
924
|
+
function checkNode$16(context, node) {
|
|
281
925
|
const settings = getSettings(context);
|
|
282
926
|
const msgs = extractMessages(node, settings);
|
|
283
927
|
if (!msgs.length) return;
|
|
@@ -303,7 +947,7 @@ function checkNode$17(context, node) {
|
|
|
303
947
|
});
|
|
304
948
|
}
|
|
305
949
|
}
|
|
306
|
-
const rule$
|
|
950
|
+
const rule$19 = {
|
|
307
951
|
meta: {
|
|
308
952
|
type: "problem",
|
|
309
953
|
docs: {
|
|
@@ -347,11 +991,11 @@ const rule$20 = {
|
|
|
347
991
|
}
|
|
348
992
|
},
|
|
349
993
|
create(context) {
|
|
350
|
-
const callExpressionVisitor = (node) => checkNode$
|
|
994
|
+
const callExpressionVisitor = (node) => checkNode$16(context, node);
|
|
351
995
|
const parserServices = context.sourceCode.parserServices;
|
|
352
996
|
if (parserServices?.defineTemplateBodyVisitor) return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
353
997
|
return {
|
|
354
|
-
JSXOpeningElement: (node) => checkNode$
|
|
998
|
+
JSXOpeningElement: (node) => checkNode$16(context, node),
|
|
355
999
|
CallExpression: callExpressionVisitor
|
|
356
1000
|
};
|
|
357
1001
|
}
|
|
@@ -363,8 +1007,8 @@ let Option$1 = /* @__PURE__ */ function(Option) {
|
|
|
363
1007
|
Option["anything"] = "anything";
|
|
364
1008
|
return Option;
|
|
365
1009
|
}({});
|
|
366
|
-
const name$
|
|
367
|
-
function checkNode$
|
|
1010
|
+
const name$20 = "enforce-default-message";
|
|
1011
|
+
function checkNode$15(context, node) {
|
|
368
1012
|
const msgs = extractMessages(node, getSettings(context));
|
|
369
1013
|
const { options: [type] } = context;
|
|
370
1014
|
for (const [{ message: { defaultMessage }, messageNode, messageDescriptorNode }] of msgs) if (!defaultMessage) {
|
|
@@ -378,7 +1022,7 @@ function checkNode$16(context, node) {
|
|
|
378
1022
|
});
|
|
379
1023
|
}
|
|
380
1024
|
}
|
|
381
|
-
const rule$
|
|
1025
|
+
const rule$18 = {
|
|
382
1026
|
meta: {
|
|
383
1027
|
type: "problem",
|
|
384
1028
|
docs: {
|
|
@@ -398,11 +1042,11 @@ const rule$19 = {
|
|
|
398
1042
|
}
|
|
399
1043
|
},
|
|
400
1044
|
create(context) {
|
|
401
|
-
const callExpressionVisitor = (node) => checkNode$
|
|
1045
|
+
const callExpressionVisitor = (node) => checkNode$15(context, node);
|
|
402
1046
|
const parserServices = context.sourceCode.parserServices;
|
|
403
1047
|
if (parserServices?.defineTemplateBodyVisitor) return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
404
1048
|
return {
|
|
405
|
-
JSXOpeningElement: (node) => checkNode$
|
|
1049
|
+
JSXOpeningElement: (node) => checkNode$15(context, node),
|
|
406
1050
|
CallExpression: callExpressionVisitor
|
|
407
1051
|
};
|
|
408
1052
|
}
|
|
@@ -428,7 +1072,7 @@ function normalizeOptions(raw) {
|
|
|
428
1072
|
minLength: void 0
|
|
429
1073
|
};
|
|
430
1074
|
}
|
|
431
|
-
function checkNode$
|
|
1075
|
+
function checkNode$14(context, node) {
|
|
432
1076
|
const msgs = extractMessages(node, getSettings(context));
|
|
433
1077
|
const { mode: type, minLength } = normalizeOptions(context.options[0]);
|
|
434
1078
|
for (const [{ message: { description }, descriptionNode, messageDescriptorNode }] of msgs) if (!description) {
|
|
@@ -449,8 +1093,8 @@ function checkNode$15(context, node) {
|
|
|
449
1093
|
}
|
|
450
1094
|
});
|
|
451
1095
|
}
|
|
452
|
-
const name$
|
|
453
|
-
const rule$
|
|
1096
|
+
const name$19 = "enforce-description";
|
|
1097
|
+
const rule$17 = {
|
|
454
1098
|
meta: {
|
|
455
1099
|
type: "problem",
|
|
456
1100
|
docs: {
|
|
@@ -483,18 +1127,18 @@ const rule$18 = {
|
|
|
483
1127
|
}
|
|
484
1128
|
},
|
|
485
1129
|
create(context) {
|
|
486
|
-
const callExpressionVisitor = (node) => checkNode$
|
|
1130
|
+
const callExpressionVisitor = (node) => checkNode$14(context, node);
|
|
487
1131
|
const parserServices = context.sourceCode.parserServices;
|
|
488
1132
|
if (parserServices?.defineTemplateBodyVisitor) return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
489
1133
|
return {
|
|
490
|
-
JSXOpeningElement: (node) => checkNode$
|
|
1134
|
+
JSXOpeningElement: (node) => checkNode$14(context, node),
|
|
491
1135
|
CallExpression: callExpressionVisitor
|
|
492
1136
|
};
|
|
493
1137
|
}
|
|
494
1138
|
};
|
|
495
1139
|
//#endregion
|
|
496
1140
|
//#region packages/eslint-plugin-formatjs/rules/enforce-id.ts
|
|
497
|
-
function checkNode$
|
|
1141
|
+
function checkNode$13(context, node, { idInterpolationPattern, idWhitelistRegexps, quoteStyle }) {
|
|
498
1142
|
const msgs = extractMessages(node, getSettings(context));
|
|
499
1143
|
for (const [{ message: { defaultMessage, description, id }, idPropNode, descriptionNode, messagePropNode, messageDescriptorNode }] of msgs) {
|
|
500
1144
|
if (!idInterpolationPattern && !idPropNode) {
|
|
@@ -552,8 +1196,8 @@ function checkNode$14(context, node, { idInterpolationPattern, idWhitelistRegexp
|
|
|
552
1196
|
}
|
|
553
1197
|
}
|
|
554
1198
|
}
|
|
555
|
-
const name$
|
|
556
|
-
const rule$
|
|
1199
|
+
const name$18 = "enforce-id";
|
|
1200
|
+
const rule$16 = {
|
|
557
1201
|
meta: {
|
|
558
1202
|
type: "problem",
|
|
559
1203
|
docs: {
|
|
@@ -604,114 +1248,11 @@ Actual: {{actual}}`
|
|
|
604
1248
|
const { idWhitelist } = tmp;
|
|
605
1249
|
opts.idWhitelistRegexps = idWhitelist.map((str) => new RegExp(str, "i"));
|
|
606
1250
|
}
|
|
607
|
-
const callExpressionVisitor = (node) => checkNode$
|
|
608
|
-
const parserServices = context.sourceCode.parserServices;
|
|
609
|
-
if (parserServices?.defineTemplateBodyVisitor) return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
610
|
-
return {
|
|
611
|
-
JSXOpeningElement: (node) => checkNode$14(context, node, opts),
|
|
612
|
-
CallExpression: callExpressionVisitor
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
};
|
|
616
|
-
//#endregion
|
|
617
|
-
//#region packages/eslint-plugin-formatjs/rules/enforce-placeholders.ts
|
|
618
|
-
function collectPlaceholderNames(ast) {
|
|
619
|
-
const placeholderNames = /* @__PURE__ */ new Set();
|
|
620
|
-
_traverse(ast);
|
|
621
|
-
return placeholderNames;
|
|
622
|
-
function _traverse(ast) {
|
|
623
|
-
for (const element of ast) switch (element.type) {
|
|
624
|
-
case TYPE.literal:
|
|
625
|
-
case TYPE.pound: break;
|
|
626
|
-
case TYPE.tag:
|
|
627
|
-
placeholderNames.add(element.value);
|
|
628
|
-
_traverse(element.children);
|
|
629
|
-
break;
|
|
630
|
-
case TYPE.plural:
|
|
631
|
-
case TYPE.select:
|
|
632
|
-
placeholderNames.add(element.value);
|
|
633
|
-
for (const { value } of Object.values(element.options)) _traverse(value);
|
|
634
|
-
break;
|
|
635
|
-
default: placeholderNames.add(element.value);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
function checkNode$13(context, node) {
|
|
640
|
-
const settings = getSettings(context);
|
|
641
|
-
const msgs = extractMessages(node, {
|
|
642
|
-
excludeMessageDeclCalls: true,
|
|
643
|
-
...settings
|
|
644
|
-
});
|
|
645
|
-
const { options: [opt] } = context;
|
|
646
|
-
const ignoreList = new Set(opt?.ignoreList || []);
|
|
647
|
-
for (const [{ message: { defaultMessage }, messageNode }, values] of msgs) {
|
|
648
|
-
if (!defaultMessage || !messageNode) continue;
|
|
649
|
-
if (values && values.type !== "ObjectExpression") continue;
|
|
650
|
-
if (values?.properties.find((prop) => prop.type === "SpreadElement")) continue;
|
|
651
|
-
const literalElementByLiteralKey = /* @__PURE__ */ new Map();
|
|
652
|
-
if (values) {
|
|
653
|
-
for (const prop of values.properties) if (prop.type === "Property" && !prop.computed) {
|
|
654
|
-
const name = prop.key.type === "Identifier" ? prop.key.name : String(prop.key.value);
|
|
655
|
-
literalElementByLiteralKey.set(name, prop);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
let ast;
|
|
659
|
-
try {
|
|
660
|
-
ast = parse(defaultMessage, { ignoreTag: settings.ignoreTag });
|
|
661
|
-
} catch (e) {
|
|
662
|
-
context.report({
|
|
663
|
-
node: messageNode,
|
|
664
|
-
messageId: "parseError",
|
|
665
|
-
data: { error: e instanceof Error ? e.message : String(e) }
|
|
666
|
-
});
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
const placeholderNames = collectPlaceholderNames(ast);
|
|
670
|
-
const missingPlaceholders = [];
|
|
671
|
-
placeholderNames.forEach((name) => {
|
|
672
|
-
if (!ignoreList.has(name) && !literalElementByLiteralKey.has(name)) missingPlaceholders.push(name);
|
|
673
|
-
});
|
|
674
|
-
if (missingPlaceholders.length > 0) context.report({
|
|
675
|
-
node: messageNode,
|
|
676
|
-
messageId: "missingValue",
|
|
677
|
-
data: { list: missingPlaceholders.join(", ") }
|
|
678
|
-
});
|
|
679
|
-
literalElementByLiteralKey.forEach((element, key) => {
|
|
680
|
-
if (!ignoreList.has(key) && !placeholderNames.has(key)) context.report({
|
|
681
|
-
node: element,
|
|
682
|
-
messageId: "unusedValue"
|
|
683
|
-
});
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
const name$18 = "enforce-placeholders";
|
|
688
|
-
const rule$16 = {
|
|
689
|
-
meta: {
|
|
690
|
-
type: "problem",
|
|
691
|
-
docs: {
|
|
692
|
-
description: "Enforce that all messages with placeholders have enough passed-in values",
|
|
693
|
-
url: "https://formatjs.github.io/docs/tooling/linter#enforce-placeholders"
|
|
694
|
-
},
|
|
695
|
-
schema: [{
|
|
696
|
-
type: "object",
|
|
697
|
-
properties: { ignoreList: {
|
|
698
|
-
type: "array",
|
|
699
|
-
items: { type: "string" }
|
|
700
|
-
} },
|
|
701
|
-
additionalProperties: false
|
|
702
|
-
}],
|
|
703
|
-
messages: {
|
|
704
|
-
...CORE_MESSAGES,
|
|
705
|
-
missingValue: "Missing value(s) for the following placeholder(s): {{list}}.",
|
|
706
|
-
unusedValue: "Value not used by the message."
|
|
707
|
-
}
|
|
708
|
-
},
|
|
709
|
-
create(context) {
|
|
710
|
-
const callExpressionVisitor = (node) => checkNode$13(context, node);
|
|
1251
|
+
const callExpressionVisitor = (node) => checkNode$13(context, node, opts);
|
|
711
1252
|
const parserServices = context.sourceCode.parserServices;
|
|
712
1253
|
if (parserServices?.defineTemplateBodyVisitor) return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
713
1254
|
return {
|
|
714
|
-
JSXOpeningElement: (node) => checkNode$13(context, node),
|
|
1255
|
+
JSXOpeningElement: (node) => checkNode$13(context, node, opts),
|
|
715
1256
|
CallExpression: callExpressionVisitor
|
|
716
1257
|
};
|
|
717
1258
|
}
|
|
@@ -4583,7 +5124,7 @@ var package_exports = /* @__PURE__ */ __exportAll({
|
|
|
4583
5124
|
version: () => version$1
|
|
4584
5125
|
});
|
|
4585
5126
|
var name$1 = "eslint-plugin-formatjs";
|
|
4586
|
-
var version$1 = "
|
|
5127
|
+
var version$1 = "7.0.0-rc.0";
|
|
4587
5128
|
var description = "ESLint plugin for formatjs";
|
|
4588
5129
|
var keywords = [
|
|
4589
5130
|
"eslint",
|
|
@@ -4644,6 +5185,7 @@ const plugin = {
|
|
|
4644
5185
|
[name$20]: rule$18,
|
|
4645
5186
|
[name$19]: rule$17,
|
|
4646
5187
|
[name$18]: rule$16,
|
|
5188
|
+
[name$23]: rule$21,
|
|
4647
5189
|
[name$17]: rule$15,
|
|
4648
5190
|
[name$16]: rule$14,
|
|
4649
5191
|
[name$15]: rule$13,
|