eslint-plugin-formatjs 4.2.0 → 4.2.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.
Files changed (88) hide show
  1. package/index.d.ts +22 -0
  2. package/index.d.ts.map +1 -0
  3. package/index.js +38 -0
  4. package/package.json +3 -3
  5. package/rules/blocklist-elements.d.ts +4 -0
  6. package/rules/blocklist-elements.d.ts.map +1 -0
  7. package/rules/blocklist-elements.js +127 -0
  8. package/rules/enforce-default-message.d.ts +4 -0
  9. package/rules/enforce-default-message.d.ts.map +1 -0
  10. package/rules/enforce-default-message.js +57 -0
  11. package/rules/enforce-description.d.ts +4 -0
  12. package/rules/enforce-description.d.ts.map +1 -0
  13. package/rules/enforce-description.js +54 -0
  14. package/rules/enforce-id.d.ts +4 -0
  15. package/rules/enforce-id.d.ts.map +1 -0
  16. package/rules/enforce-id.js +125 -0
  17. package/rules/enforce-placeholders.d.ts +4 -0
  18. package/rules/enforce-placeholders.d.ts.map +1 -0
  19. package/rules/enforce-placeholders.js +118 -0
  20. package/rules/enforce-plural-rules.d.ts +4 -0
  21. package/rules/enforce-plural-rules.d.ts.map +1 -0
  22. package/rules/enforce-plural-rules.js +104 -0
  23. package/rules/no-camel-case.d.ts +4 -0
  24. package/rules/no-camel-case.d.ts.map +1 -0
  25. package/rules/no-camel-case.js +77 -0
  26. package/rules/no-complex-selectors.d.ts +4 -0
  27. package/rules/no-complex-selectors.d.ts.map +1 -0
  28. package/rules/no-complex-selectors.js +99 -0
  29. package/rules/no-emoji.d.ts +4 -0
  30. package/rules/no-emoji.d.ts.map +1 -0
  31. package/rules/no-emoji.js +47 -0
  32. package/rules/no-id.d.ts +4 -0
  33. package/rules/no-id.d.ts.map +1 -0
  34. package/rules/no-id.js +52 -0
  35. package/rules/no-invalid-icu.d.ts +4 -0
  36. package/rules/no-invalid-icu.d.ts.map +1 -0
  37. package/rules/no-invalid-icu.js +54 -0
  38. package/rules/no-literal-string-in-jsx.d.ts +4 -0
  39. package/rules/no-literal-string-in-jsx.d.ts.map +1 -0
  40. package/rules/no-literal-string-in-jsx.js +166 -0
  41. package/rules/no-multiple-plurals.d.ts +4 -0
  42. package/rules/no-multiple-plurals.d.ts.map +1 -0
  43. package/rules/no-multiple-plurals.js +71 -0
  44. package/rules/no-multiple-whitespaces.d.ts +4 -0
  45. package/rules/no-multiple-whitespaces.d.ts.map +1 -0
  46. package/rules/no-multiple-whitespaces.js +146 -0
  47. package/rules/no-offset.d.ts +4 -0
  48. package/rules/no-offset.d.ts.map +1 -0
  49. package/rules/no-offset.js +70 -0
  50. package/util.d.ts +24 -0
  51. package/util.d.ts.map +1 -0
  52. package/util.js +240 -0
  53. package/BUILD +0 -89
  54. package/CHANGELOG.md +0 -892
  55. package/index.ts +0 -38
  56. package/rules/blocklist-elements.ts +0 -159
  57. package/rules/enforce-default-message.ts +0 -71
  58. package/rules/enforce-description.ts +0 -68
  59. package/rules/enforce-id.ts +0 -171
  60. package/rules/enforce-placeholders.ts +0 -161
  61. package/rules/enforce-plural-rules.ts +0 -134
  62. package/rules/no-camel-case.ts +0 -97
  63. package/rules/no-complex-selectors.ts +0 -125
  64. package/rules/no-emoji.ts +0 -60
  65. package/rules/no-id.ts +0 -63
  66. package/rules/no-invalid-icu.ts +0 -69
  67. package/rules/no-literal-string-in-jsx.ts +0 -213
  68. package/rules/no-multiple-plurals.ts +0 -89
  69. package/rules/no-multiple-whitespaces.ts +0 -194
  70. package/rules/no-offset.ts +0 -88
  71. package/tests/blocklist-elements.test.ts +0 -120
  72. package/tests/enforce-default-message.test.ts +0 -260
  73. package/tests/enforce-description.test.ts +0 -117
  74. package/tests/enforce-id.test.ts +0 -209
  75. package/tests/enforce-placeholders.test.ts +0 -170
  76. package/tests/enforce-plural-rules.test.ts +0 -86
  77. package/tests/fixtures.ts +0 -15
  78. package/tests/no-camel-case.test.ts +0 -31
  79. package/tests/no-complex-selectors.test.ts +0 -125
  80. package/tests/no-id.test.ts +0 -151
  81. package/tests/no-invalid-icu.test.ts +0 -106
  82. package/tests/no-literal-string-in-jsx.test.ts +0 -213
  83. package/tests/no-multiple-plurals.test.ts +0 -42
  84. package/tests/no-multiple-whitespaces.test.ts +0 -100
  85. package/tests/no-offset.test.ts +0 -41
  86. package/tests/util.ts +0 -26
  87. package/tsconfig.json +0 -5
  88. package/util.ts +0 -307
package/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ declare const plugin: {
2
+ rules: {
3
+ 'blocklist-elements': import("eslint").Rule.RuleModule;
4
+ 'enforce-default-message': import("eslint").Rule.RuleModule;
5
+ 'enforce-description': import("eslint").Rule.RuleModule;
6
+ 'enforce-id': import("eslint").Rule.RuleModule;
7
+ 'enforce-placeholders': import("eslint").Rule.RuleModule;
8
+ 'enforce-plural-rules': import("eslint").Rule.RuleModule;
9
+ 'no-camel-case': import("eslint").Rule.RuleModule;
10
+ 'no-complex-selectors': import("eslint").Rule.RuleModule;
11
+ 'no-emoji': import("eslint").Rule.RuleModule;
12
+ 'no-id': import("eslint").Rule.RuleModule;
13
+ 'no-literal-string-in-jsx': import("eslint").Rule.RuleModule;
14
+ 'no-multiple-plurals': import("eslint").Rule.RuleModule;
15
+ 'no-multiple-whitespaces': import("eslint").Rule.RuleModule;
16
+ 'no-invalid-icu': import("eslint").Rule.RuleModule;
17
+ 'no-offset': import("eslint").Rule.RuleModule;
18
+ };
19
+ };
20
+ export declare type Plugin = typeof plugin;
21
+ export {};
22
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAeA,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;CAkBX,CAAA;AAED,oBAAY,MAAM,GAAG,OAAO,MAAM,CAAA"}
package/index.js ADDED
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const blocklist_elements_1 = tslib_1.__importDefault(require("./rules/blocklist-elements"));
5
+ const enforce_default_message_1 = tslib_1.__importDefault(require("./rules/enforce-default-message"));
6
+ const enforce_description_1 = tslib_1.__importDefault(require("./rules/enforce-description"));
7
+ const enforce_id_1 = tslib_1.__importDefault(require("./rules/enforce-id"));
8
+ const enforce_placeholders_1 = tslib_1.__importDefault(require("./rules/enforce-placeholders"));
9
+ const no_invalid_icu_1 = tslib_1.__importDefault(require("./rules/no-invalid-icu"));
10
+ const enforce_plural_rules_1 = tslib_1.__importDefault(require("./rules/enforce-plural-rules"));
11
+ const no_camel_case_1 = tslib_1.__importDefault(require("./rules/no-camel-case"));
12
+ const no_complex_selectors_1 = tslib_1.__importDefault(require("./rules/no-complex-selectors"));
13
+ const no_emoji_1 = tslib_1.__importDefault(require("./rules/no-emoji"));
14
+ const no_id_1 = tslib_1.__importDefault(require("./rules/no-id"));
15
+ const no_multiple_plurals_1 = tslib_1.__importDefault(require("./rules/no-multiple-plurals"));
16
+ const no_multiple_whitespaces_1 = tslib_1.__importDefault(require("./rules/no-multiple-whitespaces"));
17
+ const no_offset_1 = tslib_1.__importDefault(require("./rules/no-offset"));
18
+ const no_literal_string_in_jsx_1 = tslib_1.__importDefault(require("./rules/no-literal-string-in-jsx"));
19
+ const plugin = {
20
+ rules: {
21
+ 'blocklist-elements': blocklist_elements_1.default,
22
+ 'enforce-default-message': enforce_default_message_1.default,
23
+ 'enforce-description': enforce_description_1.default,
24
+ 'enforce-id': enforce_id_1.default,
25
+ 'enforce-placeholders': enforce_placeholders_1.default,
26
+ 'enforce-plural-rules': enforce_plural_rules_1.default,
27
+ 'no-camel-case': no_camel_case_1.default,
28
+ 'no-complex-selectors': no_complex_selectors_1.default,
29
+ 'no-emoji': no_emoji_1.default,
30
+ 'no-id': no_id_1.default,
31
+ 'no-literal-string-in-jsx': no_literal_string_in_jsx_1.default,
32
+ 'no-multiple-plurals': no_multiple_plurals_1.default,
33
+ 'no-multiple-whitespaces': no_multiple_whitespaces_1.default,
34
+ 'no-invalid-icu': no_invalid_icu_1.default,
35
+ 'no-offset': no_offset_1.default,
36
+ },
37
+ };
38
+ module.exports = plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-formatjs",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
4
4
  "description": "ESLint plugin for formatjs",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -20,8 +20,8 @@
20
20
  },
21
21
  "homepage": "https://github.com/formatjs/formatjs#readme",
22
22
  "dependencies": {
23
- "@formatjs/icu-messageformat-parser": "2.1.5",
24
- "@formatjs/ts-transformer": "3.9.10",
23
+ "@formatjs/icu-messageformat-parser": "2.1.6",
24
+ "@formatjs/ts-transformer": "3.9.11",
25
25
  "@types/eslint": "7 || 8",
26
26
  "@types/picomatch": "^2.3.0",
27
27
  "@typescript-eslint/typescript-estree": "^5.9.1",
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=blocklist-elements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocklist-elements.d.ts","sourceRoot":"","sources":["blocklist-elements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAmH3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAyChB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("../util");
4
+ const icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
5
+ class BlacklistElement extends Error {
6
+ constructor(type) {
7
+ super();
8
+ this.message = `${type} element is blocklisted`;
9
+ }
10
+ }
11
+ var Element;
12
+ (function (Element) {
13
+ Element["literal"] = "literal";
14
+ Element["argument"] = "argument";
15
+ Element["number"] = "number";
16
+ Element["date"] = "date";
17
+ Element["time"] = "time";
18
+ Element["select"] = "select";
19
+ Element["selectordinal"] = "selectordinal";
20
+ Element["plural"] = "plural";
21
+ Element["tag"] = "tag";
22
+ })(Element || (Element = {}));
23
+ function verifyAst(blocklist, ast) {
24
+ for (const el of ast) {
25
+ if ((0, icu_messageformat_parser_1.isLiteralElement)(el) && blocklist.includes(Element.literal)) {
26
+ throw new BlacklistElement(Element.literal);
27
+ }
28
+ if ((0, icu_messageformat_parser_1.isArgumentElement)(el) && blocklist.includes(Element.argument)) {
29
+ throw new BlacklistElement(Element.argument);
30
+ }
31
+ if ((0, icu_messageformat_parser_1.isNumberElement)(el) && blocklist.includes(Element.number)) {
32
+ throw new BlacklistElement(Element.number);
33
+ }
34
+ if ((0, icu_messageformat_parser_1.isDateElement)(el) && blocklist.includes(Element.date)) {
35
+ throw new BlacklistElement(Element.date);
36
+ }
37
+ if ((0, icu_messageformat_parser_1.isTimeElement)(el) && blocklist.includes(Element.time)) {
38
+ throw new BlacklistElement(Element.time);
39
+ }
40
+ if ((0, icu_messageformat_parser_1.isSelectElement)(el) && blocklist.includes(Element.select)) {
41
+ throw new BlacklistElement(Element.select);
42
+ }
43
+ if ((0, icu_messageformat_parser_1.isTagElement)(el) && blocklist.includes(Element.tag)) {
44
+ throw new BlacklistElement(Element.tag);
45
+ }
46
+ if ((0, icu_messageformat_parser_1.isPluralElement)(el)) {
47
+ if (blocklist.includes(Element.plural)) {
48
+ throw new BlacklistElement(Element.argument);
49
+ }
50
+ if (el.pluralType === 'ordinal' &&
51
+ blocklist.includes(Element.selectordinal)) {
52
+ throw new BlacklistElement(Element.selectordinal);
53
+ }
54
+ }
55
+ if ((0, icu_messageformat_parser_1.isSelectElement)(el) || (0, icu_messageformat_parser_1.isPluralElement)(el)) {
56
+ const { options } = el;
57
+ for (const selector of Object.keys(options)) {
58
+ verifyAst(blocklist, options[selector].value);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ function checkNode(context, node) {
64
+ const settings = (0, util_1.getSettings)(context);
65
+ const msgs = (0, util_1.extractMessages)(node, settings);
66
+ if (!msgs.length) {
67
+ return;
68
+ }
69
+ const blocklist = context.options[0];
70
+ if (!Array.isArray(blocklist) || !blocklist.length) {
71
+ return;
72
+ }
73
+ for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
74
+ if (!defaultMessage || !messageNode) {
75
+ continue;
76
+ }
77
+ try {
78
+ verifyAst(context.options[0], (0, icu_messageformat_parser_1.parse)(defaultMessage, {
79
+ ignoreTag: settings.ignoreTag,
80
+ }));
81
+ }
82
+ catch (e) {
83
+ context.report({
84
+ node: messageNode,
85
+ message: e instanceof Error ? e.message : String(e),
86
+ });
87
+ }
88
+ }
89
+ }
90
+ const rule = {
91
+ meta: {
92
+ type: 'problem',
93
+ docs: {
94
+ description: 'Disallow specific elements in ICU message format',
95
+ category: 'Errors',
96
+ recommended: false,
97
+ url: 'https://formatjs.io/docs/tooling/linter#blocklist-elements',
98
+ },
99
+ fixable: 'code',
100
+ schema: [
101
+ {
102
+ type: 'array',
103
+ properties: {
104
+ items: {
105
+ type: 'string',
106
+ enum: Object.keys(Element),
107
+ },
108
+ },
109
+ },
110
+ ],
111
+ },
112
+ create(context) {
113
+ const callExpressionVisitor = (node) => checkNode(context, node);
114
+ if (context.parserServices.defineTemplateBodyVisitor) {
115
+ return context.parserServices.defineTemplateBodyVisitor({
116
+ CallExpression: callExpressionVisitor,
117
+ }, {
118
+ CallExpression: callExpressionVisitor,
119
+ });
120
+ }
121
+ return {
122
+ JSXOpeningElement: (node) => checkNode(context, node),
123
+ CallExpression: callExpressionVisitor,
124
+ };
125
+ },
126
+ };
127
+ exports.default = rule;
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=enforce-default-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce-default-message.d.ts","sourceRoot":"","sources":["enforce-default-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAiC3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAmChB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("../util");
4
+ function checkNode(context, node) {
5
+ const msgs = (0, util_1.extractMessages)(node, (0, util_1.getSettings)(context));
6
+ const { options: [type], } = context;
7
+ for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
8
+ if (!defaultMessage) {
9
+ if (type === 'literal' && messageNode) {
10
+ context.report({
11
+ node: messageNode,
12
+ message: `"defaultMessage" must be:
13
+ - a string literal or
14
+ - template literal without variable`,
15
+ });
16
+ }
17
+ else if (!messageNode) {
18
+ context.report({
19
+ node: node,
20
+ message: '`defaultMessage` has to be specified in message descriptor',
21
+ });
22
+ }
23
+ }
24
+ }
25
+ }
26
+ const rule = {
27
+ meta: {
28
+ type: 'problem',
29
+ docs: {
30
+ description: 'Enforce defaultMessage in message descriptor',
31
+ category: 'Errors',
32
+ recommended: false,
33
+ url: 'https://formatjs.io/docs/tooling/linter#enforce-default-message',
34
+ },
35
+ fixable: 'code',
36
+ schema: [
37
+ {
38
+ enum: ['literal', 'anything'],
39
+ },
40
+ ],
41
+ },
42
+ create(context) {
43
+ const callExpressionVisitor = (node) => checkNode(context, node);
44
+ if (context.parserServices.defineTemplateBodyVisitor) {
45
+ return context.parserServices.defineTemplateBodyVisitor({
46
+ CallExpression: callExpressionVisitor,
47
+ }, {
48
+ CallExpression: callExpressionVisitor,
49
+ });
50
+ }
51
+ return {
52
+ JSXOpeningElement: (node) => checkNode(context, node),
53
+ CallExpression: callExpressionVisitor,
54
+ };
55
+ },
56
+ };
57
+ exports.default = rule;
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const _default: Rule.RuleModule;
3
+ export default _default;
4
+ //# sourceMappingURL=enforce-description.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce-description.d.ts","sourceRoot":"","sources":["enforce-description.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;;AAgC3B,wBAmCoB"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("../util");
4
+ function checkNode(context, node) {
5
+ const msgs = (0, util_1.extractMessages)(node, (0, util_1.getSettings)(context));
6
+ const { options: [type], } = context;
7
+ for (const [{ message: { description }, descriptionNode, },] of msgs) {
8
+ if (!description) {
9
+ if (type === 'literal' && descriptionNode) {
10
+ context.report({
11
+ node: descriptionNode,
12
+ message: '`description` has to be a string literal (not function call or variable)',
13
+ });
14
+ }
15
+ else if (!descriptionNode) {
16
+ context.report({
17
+ node: node,
18
+ message: '`description` has to be specified in message descriptor',
19
+ });
20
+ }
21
+ }
22
+ }
23
+ }
24
+ exports.default = {
25
+ meta: {
26
+ type: 'problem',
27
+ docs: {
28
+ description: 'Enforce description in message descriptor',
29
+ category: 'Errors',
30
+ recommended: false,
31
+ url: 'https://formatjs.io/docs/tooling/linter#enforce-description',
32
+ },
33
+ fixable: 'code',
34
+ schema: [
35
+ {
36
+ enum: ['literal', 'anything'],
37
+ },
38
+ ],
39
+ },
40
+ create(context) {
41
+ const callExpressionVisitor = (node) => checkNode(context, node);
42
+ if (context.parserServices.defineTemplateBodyVisitor) {
43
+ return context.parserServices.defineTemplateBodyVisitor({
44
+ CallExpression: callExpressionVisitor,
45
+ }, {
46
+ CallExpression: callExpressionVisitor,
47
+ });
48
+ }
49
+ return {
50
+ JSXOpeningElement: (node) => checkNode(context, node),
51
+ CallExpression: callExpressionVisitor,
52
+ };
53
+ },
54
+ };
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const _default: Rule.RuleModule;
3
+ export default _default;
4
+ //# sourceMappingURL=enforce-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce-id.d.ts","sourceRoot":"","sources":["enforce-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;;AA0G3B,wBAgEoB"}
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("../util");
4
+ const ts_transformer_1 = require("@formatjs/ts-transformer");
5
+ function checkNode(context, node, { idInterpolationPattern, idWhitelistRegexps }) {
6
+ const msgs = (0, util_1.extractMessages)(node, (0, util_1.getSettings)(context));
7
+ for (const [{ message: { defaultMessage, description, id }, idPropNode, descriptionNode, messagePropNode, },] of msgs) {
8
+ if (!idInterpolationPattern && !idPropNode) {
9
+ context.report({
10
+ node: node,
11
+ message: `id must be specified`,
12
+ });
13
+ }
14
+ else if (idInterpolationPattern) {
15
+ if (!defaultMessage) {
16
+ context.report({
17
+ node: node,
18
+ message: `defaultMessage must be a string literal to calculate generated IDs`,
19
+ });
20
+ }
21
+ else if (!description && descriptionNode) {
22
+ context.report({
23
+ node: node,
24
+ message: `description must be a string literal to calculate generated IDs`,
25
+ });
26
+ }
27
+ else {
28
+ if (idWhitelistRegexps &&
29
+ id &&
30
+ idWhitelistRegexps.some((r) => r.test(id))) {
31
+ // messageId is allowlisted so skip interpolation id check
32
+ return;
33
+ }
34
+ const correctId = (0, ts_transformer_1.interpolateName)({
35
+ resourcePath: context.getFilename(),
36
+ }, idInterpolationPattern, {
37
+ content: description
38
+ ? `${defaultMessage}#${description}`
39
+ : defaultMessage,
40
+ });
41
+ if (id !== correctId) {
42
+ let message = `"id" does not match with hash pattern ${idInterpolationPattern}`;
43
+ if (idWhitelistRegexps) {
44
+ message += ` or allowlisted patterns ["${idWhitelistRegexps
45
+ .map(r => r.toString())
46
+ .join('", "')}"]`;
47
+ }
48
+ context.report({
49
+ node: node,
50
+ message: `${message}.
51
+ Expected: ${correctId}
52
+ Actual: ${id}`,
53
+ fix(fixer) {
54
+ if (idPropNode) {
55
+ if (idPropNode.type === 'JSXAttribute') {
56
+ return fixer.replaceText(idPropNode, `id="${correctId}"`);
57
+ }
58
+ return fixer.replaceText(idPropNode, `id: '${correctId}'`);
59
+ }
60
+ // Insert after default message node
61
+ if (messagePropNode.type === 'JSXAttribute') {
62
+ return fixer.insertTextAfter(messagePropNode, ` id="${correctId}"`);
63
+ }
64
+ return fixer.replaceText(messagePropNode, `defaultMessage: '${defaultMessage}', id: '${correctId}'`);
65
+ },
66
+ });
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ exports.default = {
73
+ meta: {
74
+ type: 'problem',
75
+ docs: {
76
+ description: 'Enforce (generated) ID in message descriptor',
77
+ category: 'Errors',
78
+ recommended: false,
79
+ url: 'https://formatjs.io/docs/tooling/linter#enforce-id',
80
+ },
81
+ fixable: 'code',
82
+ schema: [
83
+ {
84
+ type: 'object',
85
+ properties: {
86
+ idInterpolationPattern: {
87
+ type: 'string',
88
+ description: 'Pattern to verify ID against. Recommended value: [sha512:contenthash:base64:6]',
89
+ },
90
+ idWhitelist: {
91
+ type: 'array',
92
+ description: "An array of strings with regular expressions. This array allows allowlist custom ids for messages. For example '`\\\\.`' allows any id which has dot; `'^payment_.*'` - allows any custom id which has prefix `payment_`. Be aware that any backslash \\ provided via string must be escaped with an additional backslash.",
93
+ items: {
94
+ type: 'string',
95
+ },
96
+ },
97
+ },
98
+ required: ['idInterpolationPattern'],
99
+ additionalProperties: false,
100
+ },
101
+ ],
102
+ },
103
+ create(context) {
104
+ const tmp = context?.options?.[0];
105
+ const opts = {
106
+ idInterpolationPattern: tmp?.idInterpolationPattern,
107
+ };
108
+ if (Array.isArray(tmp?.idWhitelist)) {
109
+ const { idWhitelist } = tmp;
110
+ opts.idWhitelistRegexps = idWhitelist.map((str) => new RegExp(str, 'i'));
111
+ }
112
+ const callExpressionVisitor = (node) => checkNode(context, node, opts);
113
+ if (context.parserServices.defineTemplateBodyVisitor) {
114
+ return context.parserServices.defineTemplateBodyVisitor({
115
+ CallExpression: callExpressionVisitor,
116
+ }, {
117
+ CallExpression: callExpressionVisitor,
118
+ });
119
+ }
120
+ return {
121
+ JSXOpeningElement: (node) => checkNode(context, node, opts),
122
+ CallExpression: callExpressionVisitor,
123
+ };
124
+ },
125
+ };
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=enforce-placeholders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce-placeholders.d.ts","sourceRoot":"","sources":["enforce-placeholders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAiH3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA6ChB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("../util");
4
+ const icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
5
+ class PlaceholderEnforcement extends Error {
6
+ constructor(message) {
7
+ super();
8
+ this.message = message;
9
+ }
10
+ }
11
+ function keyExistsInExpression(key, values) {
12
+ if (!values) {
13
+ return false;
14
+ }
15
+ if (values.type !== 'ObjectExpression') {
16
+ return true; // True bc we cannot evaluate this
17
+ }
18
+ if (values.properties.find(prop => prop.type === 'SpreadElement')) {
19
+ return true; // True bc there's a spread element
20
+ }
21
+ return !!values.properties.find(prop => {
22
+ if (prop.type !== 'Property') {
23
+ return false;
24
+ }
25
+ switch (prop.key.type) {
26
+ case 'Identifier':
27
+ return prop.key.name === key;
28
+ case 'Literal':
29
+ return prop.key.value === key;
30
+ }
31
+ return false;
32
+ });
33
+ }
34
+ function verifyAst(ast, values, ignoreList) {
35
+ for (const el of ast) {
36
+ if ((0, icu_messageformat_parser_1.isLiteralElement)(el) || (0, icu_messageformat_parser_1.isPoundElement)(el)) {
37
+ continue;
38
+ }
39
+ const key = el.value;
40
+ if (!ignoreList.has(key) && !keyExistsInExpression(key, values)) {
41
+ throw new PlaceholderEnforcement(`Missing value for placeholder "${el.value}"`);
42
+ }
43
+ if ((0, icu_messageformat_parser_1.isPluralElement)(el) || (0, icu_messageformat_parser_1.isSelectElement)(el)) {
44
+ for (const selector of Object.keys(el.options)) {
45
+ verifyAst(el.options[selector].value, values, ignoreList);
46
+ }
47
+ }
48
+ if ((0, icu_messageformat_parser_1.isTagElement)(el)) {
49
+ verifyAst(el.children, values, ignoreList);
50
+ }
51
+ }
52
+ }
53
+ function checkNode(context, node) {
54
+ const settings = (0, util_1.getSettings)(context);
55
+ const msgs = (0, util_1.extractMessages)(node, {
56
+ excludeMessageDeclCalls: true,
57
+ ...settings,
58
+ });
59
+ const { options: [opt], } = context;
60
+ const ignoreList = new Set(opt?.ignoreList || []);
61
+ for (const [{ message: { defaultMessage }, messageNode, }, values,] of msgs) {
62
+ if (!defaultMessage || !messageNode) {
63
+ continue;
64
+ }
65
+ try {
66
+ verifyAst((0, icu_messageformat_parser_1.parse)(defaultMessage, {
67
+ ignoreTag: settings.ignoreTag,
68
+ }), values, ignoreList);
69
+ }
70
+ catch (e) {
71
+ context.report({
72
+ node: messageNode,
73
+ message: e instanceof Error ? e.message : String(e),
74
+ });
75
+ }
76
+ }
77
+ }
78
+ const rule = {
79
+ meta: {
80
+ type: 'problem',
81
+ docs: {
82
+ description: 'Enforce that all messages with placeholders have enough passed-in values',
83
+ category: 'Errors',
84
+ recommended: true,
85
+ url: 'https://formatjs.io/docs/tooling/linter#enforce-placeholders',
86
+ },
87
+ fixable: 'code',
88
+ schema: [
89
+ {
90
+ type: 'object',
91
+ properties: {
92
+ ignoreList: {
93
+ type: 'array',
94
+ items: {
95
+ type: 'string',
96
+ },
97
+ },
98
+ },
99
+ additionalProperties: false,
100
+ },
101
+ ],
102
+ },
103
+ create(context) {
104
+ const callExpressionVisitor = (node) => checkNode(context, node);
105
+ if (context.parserServices.defineTemplateBodyVisitor) {
106
+ return context.parserServices.defineTemplateBodyVisitor({
107
+ CallExpression: callExpressionVisitor,
108
+ }, {
109
+ CallExpression: callExpressionVisitor,
110
+ });
111
+ }
112
+ return {
113
+ JSXOpeningElement: (node) => checkNode(context, node),
114
+ CallExpression: callExpressionVisitor,
115
+ };
116
+ },
117
+ };
118
+ exports.default = rule;
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=enforce-plural-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce-plural-rules.d.ts","sourceRoot":"","sources":["enforce-plural-rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAqF3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA8ChB,CAAA;AAED,eAAe,IAAI,CAAA"}