eslint-plugin-formatjs 6.4.20 → 6.6.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 +148 -93
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/util.d.ts +8 -9
- package/util.js +1 -4
- package/util.js.map +1 -1
package/index.js
CHANGED
|
@@ -119,7 +119,6 @@ function extractMessageDescriptor(node) {
|
|
|
119
119
|
result.message.id = value;
|
|
120
120
|
result.idValueNode = valueNode;
|
|
121
121
|
result.idPropNode = prop;
|
|
122
|
-
break;
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
return result;
|
|
@@ -166,9 +165,7 @@ function extractMessageDescriptorFromJSXElement(node) {
|
|
|
166
165
|
result.idPropNode = prop;
|
|
167
166
|
if (value) result.message.id = value;
|
|
168
167
|
break;
|
|
169
|
-
case "values":
|
|
170
|
-
if (valueNode?.type === "JSXExpressionContainer" && valueNode.expression.type === "ObjectExpression") values = valueNode.expression;
|
|
171
|
-
break;
|
|
168
|
+
case "values": if (valueNode?.type === "JSXExpressionContainer" && valueNode.expression.type === "ObjectExpression") values = valueNode.expression;
|
|
172
169
|
}
|
|
173
170
|
}
|
|
174
171
|
if (!result.messagePropNode && !result.descriptionNode && !result.idPropNode && hasSpreadAttribute) return;
|
|
@@ -237,23 +234,36 @@ let Element = /* @__PURE__ */ function(Element) {
|
|
|
237
234
|
Element["tag"] = "tag";
|
|
238
235
|
return Element;
|
|
239
236
|
}({});
|
|
237
|
+
function isBlocklisted(blocklist, type, element) {
|
|
238
|
+
return blocklist.some((entry) => {
|
|
239
|
+
if (typeof entry === "string") return entry === type;
|
|
240
|
+
if (entry.type !== type) return false;
|
|
241
|
+
if (!("value" in element) || element.value !== entry.allow.variable) return true;
|
|
242
|
+
const allowedOptions = entry.allow.options;
|
|
243
|
+
if (!allowedOptions) return false;
|
|
244
|
+
if (!isSelectElement(element) && !isPluralElement(element)) return true;
|
|
245
|
+
const options = Object.keys(element.options);
|
|
246
|
+
return options.length !== allowedOptions.length || options.some((option) => !allowedOptions.includes(option));
|
|
247
|
+
});
|
|
248
|
+
}
|
|
240
249
|
function verifyAst$7(blocklist, ast) {
|
|
241
250
|
const errors = [];
|
|
242
251
|
for (const el of ast) {
|
|
243
|
-
if (isLiteralElement(el) && blocklist
|
|
244
|
-
if (isArgumentElement(el) && blocklist
|
|
245
|
-
if (isNumberElement(el) && blocklist
|
|
246
|
-
if (isDateElement(el) && blocklist
|
|
247
|
-
if (isTimeElement(el) && blocklist
|
|
248
|
-
if (isSelectElement(el) && blocklist
|
|
249
|
-
if (isTagElement(el) && blocklist
|
|
252
|
+
if (isLiteralElement(el) && isBlocklisted(blocklist, "literal", el)) errors.push(getMessage("literal"));
|
|
253
|
+
if (isArgumentElement(el) && isBlocklisted(blocklist, "argument", el)) errors.push(getMessage("argument"));
|
|
254
|
+
if (isNumberElement(el) && isBlocklisted(blocklist, "number", el)) errors.push(getMessage("number"));
|
|
255
|
+
if (isDateElement(el) && isBlocklisted(blocklist, "date", el)) errors.push(getMessage("date"));
|
|
256
|
+
if (isTimeElement(el) && isBlocklisted(blocklist, "time", el)) errors.push(getMessage("time"));
|
|
257
|
+
if (isSelectElement(el) && isBlocklisted(blocklist, "select", el)) errors.push(getMessage("select"));
|
|
258
|
+
if (isTagElement(el) && isBlocklisted(blocklist, "tag", el)) errors.push(getMessage("tag"));
|
|
259
|
+
if (isTagElement(el)) errors.push(...verifyAst$7(blocklist, el.children));
|
|
250
260
|
if (isPluralElement(el)) {
|
|
251
|
-
if (blocklist
|
|
252
|
-
if (el.pluralType === "ordinal" && blocklist
|
|
261
|
+
if (isBlocklisted(blocklist, "plural", el)) errors.push(getMessage("argument"));
|
|
262
|
+
if (el.pluralType === "ordinal" && isBlocklisted(blocklist, "selectordinal", el)) errors.push(getMessage("selectordinal"));
|
|
253
263
|
}
|
|
254
264
|
if (isSelectElement(el) || isPluralElement(el)) {
|
|
255
265
|
const { options } = el;
|
|
256
|
-
for (const selector of Object.keys(options)) verifyAst$7(blocklist, options[selector].value);
|
|
266
|
+
for (const selector of Object.keys(options)) errors.push(...verifyAst$7(blocklist, options[selector].value));
|
|
257
267
|
}
|
|
258
268
|
}
|
|
259
269
|
return errors;
|
|
@@ -294,10 +304,33 @@ const rule$20 = {
|
|
|
294
304
|
fixable: "code",
|
|
295
305
|
schema: [{
|
|
296
306
|
type: "array",
|
|
297
|
-
items: {
|
|
307
|
+
items: { oneOf: [{
|
|
298
308
|
type: "string",
|
|
299
309
|
enum: Object.keys(Element)
|
|
300
|
-
}
|
|
310
|
+
}, {
|
|
311
|
+
type: "object",
|
|
312
|
+
additionalProperties: false,
|
|
313
|
+
required: ["type", "allow"],
|
|
314
|
+
properties: {
|
|
315
|
+
type: {
|
|
316
|
+
type: "string",
|
|
317
|
+
enum: Object.keys(Element)
|
|
318
|
+
},
|
|
319
|
+
allow: {
|
|
320
|
+
type: "object",
|
|
321
|
+
additionalProperties: false,
|
|
322
|
+
required: ["variable"],
|
|
323
|
+
properties: {
|
|
324
|
+
variable: { type: "string" },
|
|
325
|
+
options: {
|
|
326
|
+
type: "array",
|
|
327
|
+
uniqueItems: true,
|
|
328
|
+
items: { type: "string" }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}] }
|
|
301
334
|
}],
|
|
302
335
|
messages: {
|
|
303
336
|
...CORE_MESSAGES,
|
|
@@ -462,48 +495,50 @@ function checkNode$14(context, node, { idInterpolationPattern, idWhitelistRegexp
|
|
|
462
495
|
});
|
|
463
496
|
return;
|
|
464
497
|
}
|
|
465
|
-
if (idInterpolationPattern)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (idWhitelistRegexps) {
|
|
484
|
-
messageId = "enforceIdMatchingAllowlisted";
|
|
485
|
-
messageData = {
|
|
486
|
-
...messageData,
|
|
487
|
-
idWhitelist: idWhitelistRegexps.map((r) => `"${r.toString()}"`).join(", ")
|
|
498
|
+
if (idInterpolationPattern) {
|
|
499
|
+
if (!defaultMessage) context.report({
|
|
500
|
+
node: messageDescriptorNode ?? node,
|
|
501
|
+
messageId: "enforceIdDefaultMessage"
|
|
502
|
+
});
|
|
503
|
+
else if (!description && descriptionNode) context.report({
|
|
504
|
+
node: messageDescriptorNode ?? node,
|
|
505
|
+
messageId: "enforceIdDescription"
|
|
506
|
+
});
|
|
507
|
+
else {
|
|
508
|
+
if (idWhitelistRegexps && id && idWhitelistRegexps.some((r) => r.test(id))) continue;
|
|
509
|
+
const correctId = interpolateName({ resourcePath: context.filename }, idInterpolationPattern, { content: description ? `${defaultMessage}#${description}` : defaultMessage });
|
|
510
|
+
if (id !== correctId) {
|
|
511
|
+
let messageId = "enforceIdMatching";
|
|
512
|
+
let messageData = {
|
|
513
|
+
idInterpolationPattern,
|
|
514
|
+
expected: correctId,
|
|
515
|
+
actual: id
|
|
488
516
|
};
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
fix(fixer) {
|
|
496
|
-
if (idPropNode) {
|
|
497
|
-
if (idPropNode.type === "JSXAttribute") return fixer.replaceText(idPropNode, `id="${correctId}"`);
|
|
498
|
-
return fixer.replaceText(idPropNode, `id: ${quote}${correctId}${quote}`);
|
|
499
|
-
}
|
|
500
|
-
if (messagePropNode) {
|
|
501
|
-
if (messagePropNode.type === "JSXAttribute") return fixer.insertTextAfter(messagePropNode, ` id="${correctId}"`);
|
|
502
|
-
return fixer.insertTextAfter(messagePropNode, `, id: ${quote}${correctId}${quote}`);
|
|
503
|
-
}
|
|
504
|
-
return null;
|
|
517
|
+
if (idWhitelistRegexps) {
|
|
518
|
+
messageId = "enforceIdMatchingAllowlisted";
|
|
519
|
+
messageData = {
|
|
520
|
+
...messageData,
|
|
521
|
+
idWhitelist: idWhitelistRegexps.map((r) => `"${r.toString()}"`).join(", ")
|
|
522
|
+
};
|
|
505
523
|
}
|
|
506
|
-
|
|
524
|
+
const quote = quoteStyle === "double" ? "\"" : "'";
|
|
525
|
+
context.report({
|
|
526
|
+
node: idPropNode ?? messageDescriptorNode ?? node,
|
|
527
|
+
messageId,
|
|
528
|
+
data: messageData,
|
|
529
|
+
fix(fixer) {
|
|
530
|
+
if (idPropNode) {
|
|
531
|
+
if (idPropNode.type === "JSXAttribute") return fixer.replaceText(idPropNode, `id="${correctId}"`);
|
|
532
|
+
return fixer.replaceText(idPropNode, `id: ${quote}${correctId}${quote}`);
|
|
533
|
+
}
|
|
534
|
+
if (messagePropNode) {
|
|
535
|
+
if (messagePropNode.type === "JSXAttribute") return fixer.insertTextAfter(messagePropNode, ` id="${correctId}"`);
|
|
536
|
+
return fixer.insertTextAfter(messagePropNode, `, id: ${quote}${correctId}${quote}`);
|
|
537
|
+
}
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
507
542
|
}
|
|
508
543
|
}
|
|
509
544
|
}
|
|
@@ -588,9 +623,7 @@ function collectPlaceholderNames(ast) {
|
|
|
588
623
|
placeholderNames.add(element.value);
|
|
589
624
|
for (const { value } of Object.values(element.options)) _traverse(value);
|
|
590
625
|
break;
|
|
591
|
-
default:
|
|
592
|
-
placeholderNames.add(element.value);
|
|
593
|
-
break;
|
|
626
|
+
default: placeholderNames.add(element.value);
|
|
594
627
|
}
|
|
595
628
|
}
|
|
596
629
|
}
|
|
@@ -857,9 +890,7 @@ function calculateComplexity(ast) {
|
|
|
857
890
|
case TYPE.tag:
|
|
858
891
|
complexity = _calculate(element.children, 0) * _calculate(ast, index + 1);
|
|
859
892
|
break;
|
|
860
|
-
default:
|
|
861
|
-
complexity = _calculate(ast, index + 1);
|
|
862
|
-
break;
|
|
893
|
+
default: complexity = _calculate(ast, index + 1);
|
|
863
894
|
}
|
|
864
895
|
complexityByNode.set(element, complexity);
|
|
865
896
|
return complexity;
|
|
@@ -3578,20 +3609,22 @@ function checkNode$9(context, node) {
|
|
|
3578
3609
|
if (emojiConfig?.versionAbove && isValidEmojiVersion(emojiConfig.versionAbove)) versionAbove = emojiConfig.versionAbove;
|
|
3579
3610
|
for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
|
|
3580
3611
|
if (!defaultMessage || !messageNode) continue;
|
|
3581
|
-
if (hasEmoji(defaultMessage))
|
|
3582
|
-
|
|
3583
|
-
|
|
3612
|
+
if (hasEmoji(defaultMessage)) {
|
|
3613
|
+
if (versionAbove) {
|
|
3614
|
+
const filter = filterEmojis(versionAbove);
|
|
3615
|
+
for (const emoji of extractEmojis(defaultMessage)) if (!filter(emoji)) context.report({
|
|
3616
|
+
node: messageNode,
|
|
3617
|
+
messageId: "notAllowedAboveVersion",
|
|
3618
|
+
data: {
|
|
3619
|
+
versionAbove,
|
|
3620
|
+
emoji
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
} else context.report({
|
|
3584
3624
|
node: messageNode,
|
|
3585
|
-
messageId: "
|
|
3586
|
-
data: {
|
|
3587
|
-
versionAbove,
|
|
3588
|
-
emoji
|
|
3589
|
-
}
|
|
3625
|
+
messageId: "notAllowed"
|
|
3590
3626
|
});
|
|
3591
|
-
}
|
|
3592
|
-
node: messageNode,
|
|
3593
|
-
messageId: "notAllowed"
|
|
3594
|
-
});
|
|
3627
|
+
}
|
|
3595
3628
|
}
|
|
3596
3629
|
}
|
|
3597
3630
|
const rule$12 = {
|
|
@@ -4030,9 +4063,7 @@ function trimMultiWhitespaces(message, ast) {
|
|
|
4030
4063
|
case TYPE.select:
|
|
4031
4064
|
for (const option of Object.values(element.options)) collectLiteralElements(option.value);
|
|
4032
4065
|
break;
|
|
4033
|
-
case TYPE.tag:
|
|
4034
|
-
collectLiteralElements(element.children);
|
|
4035
|
-
break;
|
|
4066
|
+
case TYPE.tag: collectLiteralElements(element.children);
|
|
4036
4067
|
}
|
|
4037
4068
|
};
|
|
4038
4069
|
collectLiteralElements(ast);
|
|
@@ -4290,10 +4321,7 @@ function verifyAst(context, messageNode, ast) {
|
|
|
4290
4321
|
case TYPE.select:
|
|
4291
4322
|
for (const { value } of Object.values(current.options)) _verifyAst(value);
|
|
4292
4323
|
break;
|
|
4293
|
-
case TYPE.tag:
|
|
4294
|
-
_verifyAst(current.children);
|
|
4295
|
-
break;
|
|
4296
|
-
default: break;
|
|
4324
|
+
case TYPE.tag: _verifyAst(current.children);
|
|
4297
4325
|
}
|
|
4298
4326
|
}
|
|
4299
4327
|
}
|
|
@@ -4311,10 +4339,7 @@ function verifyAst(context, messageNode, ast) {
|
|
|
4311
4339
|
case TYPE.tag:
|
|
4312
4340
|
_replacementArgumentWithPound(name, element.children);
|
|
4313
4341
|
break;
|
|
4314
|
-
case TYPE.select:
|
|
4315
|
-
for (const { value } of Object.values(element.options)) _replacementArgumentWithPound(name, value);
|
|
4316
|
-
break;
|
|
4317
|
-
default: break;
|
|
4342
|
+
case TYPE.select: for (const { value } of Object.values(element.options)) _replacementArgumentWithPound(name, value);
|
|
4318
4343
|
}
|
|
4319
4344
|
}
|
|
4320
4345
|
function _removeRangeAndPrependPluralClauses(rangeToRemoveStart, rangeToRemoveEnd, pluralElement, prependText) {
|
|
@@ -4450,18 +4475,27 @@ function getLastBoundaryElement(ast) {
|
|
|
4450
4475
|
if (last.type === TYPE.tag) return getLastBoundaryElement(last.children);
|
|
4451
4476
|
return last;
|
|
4452
4477
|
}
|
|
4453
|
-
function
|
|
4478
|
+
function isPlaceholder(element) {
|
|
4479
|
+
return element.type !== TYPE.literal && element.type !== TYPE.tag;
|
|
4480
|
+
}
|
|
4481
|
+
function findMessageIssues(ast) {
|
|
4454
4482
|
const issues = [];
|
|
4455
4483
|
const first = getFirstBoundaryElement(ast);
|
|
4456
4484
|
const last = getLastBoundaryElement(ast);
|
|
4457
4485
|
if (first === last && first?.type === TYPE.literal && ast.length === 1 && first.value.trim() === "") return issues;
|
|
4458
4486
|
if (first?.type === TYPE.literal && /^\s/.test(first.value)) issues.push("leadingWhitespace");
|
|
4459
4487
|
if (last?.type === TYPE.literal && /\s$/.test(last.value)) issues.push("trailingWhitespace");
|
|
4488
|
+
for (let index = 0; index < ast.length - 1; index++) {
|
|
4489
|
+
const current = ast[index];
|
|
4490
|
+
const next = ast[index + 1];
|
|
4491
|
+
if (current.type === TYPE.literal && /\p{L}$/u.test(current.value) && isPlaceholder(next) || isPlaceholder(current) && next.type === TYPE.literal && /^\p{L}/u.test(next.value)) issues.push("placeholderAdjacentToWord");
|
|
4492
|
+
}
|
|
4460
4493
|
for (const element of ast) switch (element.type) {
|
|
4461
4494
|
case TYPE.plural:
|
|
4462
4495
|
case TYPE.select:
|
|
4463
|
-
for (const option of Object.values(element.options)) issues.push(...
|
|
4496
|
+
for (const option of Object.values(element.options)) issues.push(...findMessageIssues(option.value));
|
|
4464
4497
|
break;
|
|
4498
|
+
case TYPE.tag: issues.push(...findMessageIssues(element.children));
|
|
4465
4499
|
}
|
|
4466
4500
|
return issues;
|
|
4467
4501
|
}
|
|
@@ -4480,7 +4514,7 @@ function checkNode(context, node) {
|
|
|
4480
4514
|
});
|
|
4481
4515
|
return;
|
|
4482
4516
|
}
|
|
4483
|
-
const issues =
|
|
4517
|
+
const issues = findMessageIssues(ast);
|
|
4484
4518
|
const seen = /* @__PURE__ */ new Set();
|
|
4485
4519
|
for (const issue of issues) {
|
|
4486
4520
|
if (seen.has(issue)) continue;
|
|
@@ -4503,7 +4537,8 @@ const rule = {
|
|
|
4503
4537
|
messages: {
|
|
4504
4538
|
...CORE_MESSAGES,
|
|
4505
4539
|
leadingWhitespace: "Messages should be full sentences — leading whitespace suggests string concatenation",
|
|
4506
|
-
trailingWhitespace: "Messages should be full sentences — trailing whitespace suggests string concatenation"
|
|
4540
|
+
trailingWhitespace: "Messages should be full sentences — trailing whitespace suggests string concatenation",
|
|
4541
|
+
placeholderAdjacentToWord: "Placeholders should not be joined directly to words or units — use ICU syntax or an Intl formatter"
|
|
4507
4542
|
},
|
|
4508
4543
|
schema: []
|
|
4509
4544
|
},
|
|
@@ -4538,7 +4573,7 @@ var package_exports = /* @__PURE__ */ __exportAll({
|
|
|
4538
4573
|
version: () => version$1
|
|
4539
4574
|
});
|
|
4540
4575
|
var name$1 = "eslint-plugin-formatjs";
|
|
4541
|
-
var version$1 = "6.
|
|
4576
|
+
var version$1 = "6.6.0";
|
|
4542
4577
|
var description = "ESLint plugin for formatjs";
|
|
4543
4578
|
var keywords = [
|
|
4544
4579
|
"eslint",
|
|
@@ -4564,7 +4599,7 @@ var dependencies = {
|
|
|
4564
4599
|
"@types/estree-jsx": "1",
|
|
4565
4600
|
"@types/picomatch": "4",
|
|
4566
4601
|
"@unicode/unicode-17.0.0": "1",
|
|
4567
|
-
"magic-string": "
|
|
4602
|
+
"magic-string": "1",
|
|
4568
4603
|
"picomatch": "2 || 3 || 4"
|
|
4569
4604
|
};
|
|
4570
4605
|
var peerDependencies = { "eslint": "9 || 10" };
|
|
@@ -4640,7 +4675,17 @@ plugin.configs = {
|
|
|
4640
4675
|
other: true
|
|
4641
4676
|
}],
|
|
4642
4677
|
"formatjs/no-literal-string-in-jsx": ["error", { props: { include: [["*", "{label,placeholder,title}"]] } }],
|
|
4643
|
-
"formatjs/blocklist-elements": ["error", ["selectordinal"
|
|
4678
|
+
"formatjs/blocklist-elements": ["error", ["selectordinal", {
|
|
4679
|
+
type: "select",
|
|
4680
|
+
allow: {
|
|
4681
|
+
variable: "gender",
|
|
4682
|
+
options: [
|
|
4683
|
+
"male",
|
|
4684
|
+
"female",
|
|
4685
|
+
"other"
|
|
4686
|
+
]
|
|
4687
|
+
}
|
|
4688
|
+
}]],
|
|
4644
4689
|
"formatjs/prefer-full-sentence": "error"
|
|
4645
4690
|
}
|
|
4646
4691
|
},
|
|
@@ -4664,7 +4709,17 @@ plugin.configs = {
|
|
|
4664
4709
|
other: true
|
|
4665
4710
|
}],
|
|
4666
4711
|
"formatjs/no-literal-string-in-jsx": ["warn", { props: { include: [["*", "{label,placeholder,title}"]] } }],
|
|
4667
|
-
"formatjs/blocklist-elements": ["error", ["selectordinal"
|
|
4712
|
+
"formatjs/blocklist-elements": ["error", ["selectordinal", {
|
|
4713
|
+
type: "select",
|
|
4714
|
+
allow: {
|
|
4715
|
+
variable: "gender",
|
|
4716
|
+
options: [
|
|
4717
|
+
"male",
|
|
4718
|
+
"female",
|
|
4719
|
+
"other"
|
|
4720
|
+
]
|
|
4721
|
+
}
|
|
4722
|
+
}]],
|
|
4668
4723
|
"formatjs/prefer-full-sentence": "error"
|
|
4669
4724
|
}
|
|
4670
4725
|
}
|