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.0.2",
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.5.0",
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.6.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.9",
33
- "@formatjs/ts-transformer": "3.13.15"
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.patchMessage = exports.extractMessages = exports.extractMessageDescriptor = exports.isIntlFormatMessageCall = exports.getSettings = void 0;
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;