eslint-plugin-formatjs 5.0.2 → 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const no_literal_string_in_jsx_1 = require("./rules/no-literal-string-in-jsx");
|
|
|
18
18
|
const no_useless_message_1 = require("./rules/no-useless-message");
|
|
19
19
|
const prefer_formatted_message_1 = require("./rules/prefer-formatted-message");
|
|
20
20
|
const prefer_pound_in_plural_1 = require("./rules/prefer-pound-in-plural");
|
|
21
|
+
const no_missing_icu_plural_one_placeholders_1 = require("./rules/no-missing-icu-plural-one-placeholders");
|
|
21
22
|
const plugin = {
|
|
22
23
|
rules: {
|
|
23
24
|
[blocklist_elements_1.name]: blocklist_elements_1.rule,
|
|
@@ -38,6 +39,7 @@ const plugin = {
|
|
|
38
39
|
[no_useless_message_1.name]: no_useless_message_1.rule,
|
|
39
40
|
[prefer_formatted_message_1.name]: prefer_formatted_message_1.rule,
|
|
40
41
|
[prefer_pound_in_plural_1.name]: prefer_pound_in_plural_1.rule,
|
|
42
|
+
[no_missing_icu_plural_one_placeholders_1.name]: no_missing_icu_plural_one_placeholders_1.rule,
|
|
41
43
|
},
|
|
42
44
|
};
|
|
43
45
|
module.exports = plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-formatjs",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "ESLint plugin for formatjs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@types/eslint": "9",
|
|
24
24
|
"@types/picomatch": "^2.3.0",
|
|
25
|
-
"@typescript-eslint/utils": "8.
|
|
25
|
+
"@typescript-eslint/utils": "8.10.0",
|
|
26
26
|
"emoji-regex": "^10.2.1",
|
|
27
27
|
"magic-string": "^0.30.0",
|
|
28
28
|
"picomatch": "^2.3.1",
|
|
29
|
-
"tslib": "2.
|
|
29
|
+
"tslib": "^2.7.0",
|
|
30
30
|
"typescript": "5",
|
|
31
31
|
"unicode-emoji-utils": "^1.2.0",
|
|
32
|
-
"@formatjs/icu-messageformat-parser": "2.7.
|
|
33
|
-
"@formatjs/ts-transformer": "3.13.
|
|
32
|
+
"@formatjs/icu-messageformat-parser": "2.7.10",
|
|
33
|
+
"@formatjs/ts-transformer": "3.13.16"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"eslint": "9"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
+
export declare const name = "no-missing-icu-plural-one-placeholders";
|
|
3
|
+
export type MessageIds = 'noMissingIcuPluralOnePlaceholders';
|
|
4
|
+
type Options = [];
|
|
5
|
+
export declare const rule: RuleModule<MessageIds, Options>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rule = exports.name = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
|
|
6
|
+
const magic_string_1 = tslib_1.__importDefault(require("magic-string"));
|
|
7
|
+
const context_compat_1 = require("../context-compat");
|
|
8
|
+
const util_1 = require("../util");
|
|
9
|
+
exports.name = 'no-missing-icu-plural-one-placeholders';
|
|
10
|
+
function verifyAst(context, messageNode, ast) {
|
|
11
|
+
const patches = [];
|
|
12
|
+
_verifyAstAndReplace(ast);
|
|
13
|
+
if (patches.length > 0) {
|
|
14
|
+
const patchedMessage = (0, util_1.patchMessage)(messageNode, ast, content => {
|
|
15
|
+
return patches
|
|
16
|
+
.reduce((magicString, patch) => {
|
|
17
|
+
switch (patch.type) {
|
|
18
|
+
case 'prependLeft':
|
|
19
|
+
return magicString.prependLeft(patch.index, patch.content);
|
|
20
|
+
case 'remove':
|
|
21
|
+
return magicString.remove(patch.start, patch.end);
|
|
22
|
+
case 'update':
|
|
23
|
+
return magicString.update(patch.start, patch.end, patch.content);
|
|
24
|
+
}
|
|
25
|
+
}, new magic_string_1.default(content))
|
|
26
|
+
.toString();
|
|
27
|
+
});
|
|
28
|
+
context.report({
|
|
29
|
+
node: messageNode,
|
|
30
|
+
messageId: 'noMissingIcuPluralOnePlaceholders',
|
|
31
|
+
fix: patchedMessage !== null
|
|
32
|
+
? fixer => fixer.replaceText(messageNode, patchedMessage)
|
|
33
|
+
: null,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function _verifyAstAndReplace(ast, root = true) {
|
|
37
|
+
for (const el of ast) {
|
|
38
|
+
if ((0, icu_messageformat_parser_1.isPluralElement)(el) && el.options['one']) {
|
|
39
|
+
_verifyAstAndReplace(el.options['one'].value, false);
|
|
40
|
+
}
|
|
41
|
+
else if ((0, icu_messageformat_parser_1.isSelectElement)(el)) {
|
|
42
|
+
for (const { value } of Object.values(el.options)) {
|
|
43
|
+
_verifyAstAndReplace(value, root);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if ((0, icu_messageformat_parser_1.isTagElement)(el)) {
|
|
47
|
+
_verifyAstAndReplace(el.children, root);
|
|
48
|
+
}
|
|
49
|
+
else if (!root && (0, icu_messageformat_parser_1.isLiteralElement)(el)) {
|
|
50
|
+
const match = el.value.match(/\b1\b/);
|
|
51
|
+
if (match && el.location) {
|
|
52
|
+
patches.push({
|
|
53
|
+
type: 'update',
|
|
54
|
+
start: el.location.start.offset,
|
|
55
|
+
end: el.location.end.offset,
|
|
56
|
+
content: el.value.replace(match[0], '#'),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function checkNode(context, node) {
|
|
64
|
+
const msgs = (0, util_1.extractMessages)(node);
|
|
65
|
+
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
66
|
+
if (!defaultMessage || !messageNode) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
verifyAst(context, messageNode, (0, icu_messageformat_parser_1.parse)(defaultMessage, { captureLocation: true }));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.rule = {
|
|
73
|
+
meta: {
|
|
74
|
+
type: 'problem',
|
|
75
|
+
docs: {
|
|
76
|
+
description: 'We use `one {# item}` instead of `one {1 item}` in ICU messages as some locales use the `one` formatting for other similar numbers.',
|
|
77
|
+
url: 'https://formatjs.io/docs/tooling/linter#no-explicit-icu-plural',
|
|
78
|
+
},
|
|
79
|
+
fixable: 'code',
|
|
80
|
+
messages: {
|
|
81
|
+
noMissingIcuPluralOnePlaceholders: 'Use `one {# item}` instead of `one {1 item}` in ICU messages.',
|
|
82
|
+
},
|
|
83
|
+
schema: [],
|
|
84
|
+
},
|
|
85
|
+
defaultOptions: [],
|
|
86
|
+
create(context) {
|
|
87
|
+
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
88
|
+
const parserServices = (0, context_compat_1.getParserServices)(context);
|
|
89
|
+
//@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
|
|
90
|
+
if (parserServices.defineTemplateBodyVisitor) {
|
|
91
|
+
//@ts-expect-error
|
|
92
|
+
return parserServices.defineTemplateBodyVisitor({
|
|
93
|
+
CallExpression: callExpressionVisitor,
|
|
94
|
+
}, {
|
|
95
|
+
CallExpression: callExpressionVisitor,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
JSXOpeningElement: (node) => checkNode(context, node),
|
|
100
|
+
CallExpression: callExpressionVisitor,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
};
|
package/util.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getSettings = getSettings;
|
|
4
|
+
exports.isIntlFormatMessageCall = isIntlFormatMessageCall;
|
|
5
|
+
exports.extractMessageDescriptor = extractMessageDescriptor;
|
|
6
|
+
exports.extractMessages = extractMessages;
|
|
7
|
+
exports.patchMessage = patchMessage;
|
|
4
8
|
const FORMAT_FUNCTION_NAMES = new Set(['$formatMessage', 'formatMessage', '$t']);
|
|
5
9
|
const COMPONENT_NAMES = new Set(['FormattedMessage']);
|
|
6
10
|
const DECLARATION_FUNCTION_NAMES = new Set(['defineMessage']);
|
|
7
11
|
function getSettings({ settings }) {
|
|
8
12
|
return settings.formatjs ?? settings;
|
|
9
13
|
}
|
|
10
|
-
exports.getSettings = getSettings;
|
|
11
14
|
function isStringLiteral(node) {
|
|
12
15
|
return node.type === 'Literal' && typeof node.value === 'string';
|
|
13
16
|
}
|
|
@@ -41,7 +44,6 @@ function isIntlFormatMessageCall(node) {
|
|
|
41
44
|
node.arguments.length >= 1 &&
|
|
42
45
|
node.arguments[0].type === 'ObjectExpression');
|
|
43
46
|
}
|
|
44
|
-
exports.isIntlFormatMessageCall = isIntlFormatMessageCall;
|
|
45
47
|
function isSingleMessageDescriptorDeclaration(node, functionNames) {
|
|
46
48
|
return (node.type === 'CallExpression' &&
|
|
47
49
|
node.callee.type === 'Identifier' &&
|
|
@@ -100,7 +102,6 @@ function extractMessageDescriptor(node) {
|
|
|
100
102
|
}
|
|
101
103
|
return result;
|
|
102
104
|
}
|
|
103
|
-
exports.extractMessageDescriptor = extractMessageDescriptor;
|
|
104
105
|
function extractMessageDescriptorFromJSXElement(node) {
|
|
105
106
|
if (!node || !node.attributes) {
|
|
106
107
|
return;
|
|
@@ -243,7 +244,6 @@ function extractMessages(node, { additionalComponentNames, additionalFunctionNam
|
|
|
243
244
|
}
|
|
244
245
|
return [];
|
|
245
246
|
}
|
|
246
|
-
exports.extractMessages = extractMessages;
|
|
247
247
|
/**
|
|
248
248
|
* Apply changes to the ICU message in code. The return value can be used in
|
|
249
249
|
* `fixer.replaceText(messageNode, <return value>)`. If the return value is null,
|
|
@@ -266,4 +266,3 @@ function patchMessage(messageNode, ast, patcher) {
|
|
|
266
266
|
}
|
|
267
267
|
return null;
|
|
268
268
|
}
|
|
269
|
-
exports.patchMessage = patchMessage;
|