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
@@ -0,0 +1,71 @@
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 MultiplePlurals extends Error {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.message = 'Cannot specify more than 1 plural rules';
9
+ }
10
+ }
11
+ function verifyAst(ast, pluralCount = { count: 0 }) {
12
+ for (const el of ast) {
13
+ if ((0, icu_messageformat_parser_1.isPluralElement)(el)) {
14
+ pluralCount.count++;
15
+ if (pluralCount.count > 1) {
16
+ throw new MultiplePlurals();
17
+ }
18
+ const { options } = el;
19
+ for (const selector of Object.keys(options)) {
20
+ verifyAst(options[selector].value, pluralCount);
21
+ }
22
+ }
23
+ }
24
+ }
25
+ function checkNode(context, node) {
26
+ const settings = (0, util_1.getSettings)(context);
27
+ const msgs = (0, util_1.extractMessages)(node, settings);
28
+ for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
29
+ if (!defaultMessage || !messageNode) {
30
+ continue;
31
+ }
32
+ try {
33
+ verifyAst((0, icu_messageformat_parser_1.parse)(defaultMessage, {
34
+ ignoreTag: settings.ignoreTag,
35
+ }));
36
+ }
37
+ catch (e) {
38
+ context.report({
39
+ node: messageNode,
40
+ message: e instanceof Error ? e.message : String(e),
41
+ });
42
+ }
43
+ }
44
+ }
45
+ const rule = {
46
+ meta: {
47
+ type: 'problem',
48
+ docs: {
49
+ description: 'Disallow multiple plural rules in the same message',
50
+ category: 'Errors',
51
+ recommended: false,
52
+ url: 'https://formatjs.io/docs/tooling/linter#no-multiple-plurals',
53
+ },
54
+ fixable: 'code',
55
+ },
56
+ create(context) {
57
+ const callExpressionVisitor = (node) => checkNode(context, node);
58
+ if (context.parserServices.defineTemplateBodyVisitor) {
59
+ return context.parserServices.defineTemplateBodyVisitor({
60
+ CallExpression: callExpressionVisitor,
61
+ }, {
62
+ CallExpression: callExpressionVisitor,
63
+ });
64
+ }
65
+ return {
66
+ JSXOpeningElement: (node) => checkNode(context, node),
67
+ CallExpression: callExpressionVisitor,
68
+ };
69
+ },
70
+ };
71
+ exports.default = rule;
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=no-multiple-whitespaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-multiple-whitespaces.d.ts","sourceRoot":"","sources":["no-multiple-whitespaces.ts"],"names":[],"mappings":"AAOA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAyJ3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA+BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
4
+ const util_1 = require("../util");
5
+ function isAstValid(ast) {
6
+ for (const element of ast) {
7
+ switch (element.type) {
8
+ case icu_messageformat_parser_1.TYPE.literal:
9
+ if (/\s{2,}/gm.test(element.value)) {
10
+ return false;
11
+ }
12
+ break;
13
+ case icu_messageformat_parser_1.TYPE.argument:
14
+ case icu_messageformat_parser_1.TYPE.date:
15
+ case icu_messageformat_parser_1.TYPE.literal:
16
+ case icu_messageformat_parser_1.TYPE.number:
17
+ case icu_messageformat_parser_1.TYPE.pound:
18
+ case icu_messageformat_parser_1.TYPE.tag:
19
+ case icu_messageformat_parser_1.TYPE.time:
20
+ break;
21
+ case icu_messageformat_parser_1.TYPE.plural:
22
+ case icu_messageformat_parser_1.TYPE.select: {
23
+ for (const option of Object.values(element.options)) {
24
+ if (!isAstValid(option.value)) {
25
+ return false;
26
+ }
27
+ }
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ return true;
33
+ }
34
+ function trimMultiWhitespaces(message, ast) {
35
+ const literalElements = [];
36
+ const collectLiteralElements = (elements) => {
37
+ for (const element of elements) {
38
+ switch (element.type) {
39
+ case icu_messageformat_parser_1.TYPE.literal:
40
+ literalElements.push(element);
41
+ break;
42
+ case icu_messageformat_parser_1.TYPE.argument:
43
+ case icu_messageformat_parser_1.TYPE.date:
44
+ case icu_messageformat_parser_1.TYPE.literal:
45
+ case icu_messageformat_parser_1.TYPE.number:
46
+ case icu_messageformat_parser_1.TYPE.pound:
47
+ case icu_messageformat_parser_1.TYPE.tag:
48
+ case icu_messageformat_parser_1.TYPE.time:
49
+ break;
50
+ case icu_messageformat_parser_1.TYPE.plural:
51
+ case icu_messageformat_parser_1.TYPE.select: {
52
+ for (const option of Object.values(element.options)) {
53
+ collectLiteralElements(option.value);
54
+ }
55
+ break;
56
+ }
57
+ }
58
+ }
59
+ };
60
+ collectLiteralElements(ast);
61
+ // Surgically trim whitespaces in the literal element ranges.
62
+ // This is to preserve the original whitespaces and newlines info that are lost to parsing.
63
+ let trimmedFragments = [];
64
+ let currentOffset = 0;
65
+ for (const literal of literalElements) {
66
+ const { start, end } = literal.location;
67
+ const startOffset = start.offset;
68
+ const endOffset = end.offset;
69
+ trimmedFragments.push(message.slice(currentOffset, startOffset));
70
+ trimmedFragments.push(message.slice(startOffset, endOffset).replace(/\s{2,}/gm, ' '));
71
+ currentOffset = endOffset;
72
+ }
73
+ trimmedFragments.push(message.slice(currentOffset));
74
+ return trimmedFragments.join('');
75
+ }
76
+ function checkNode(context, node) {
77
+ const msgs = (0, util_1.extractMessages)(node, (0, util_1.getSettings)(context));
78
+ for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
79
+ if (!defaultMessage || !messageNode) {
80
+ continue;
81
+ }
82
+ let ast;
83
+ try {
84
+ ast = (0, icu_messageformat_parser_1.parse)(defaultMessage, { captureLocation: true });
85
+ }
86
+ catch (e) {
87
+ context.report({
88
+ node: messageNode,
89
+ message: e instanceof Error ? e.message : String(e),
90
+ });
91
+ return;
92
+ }
93
+ if (!isAstValid(ast)) {
94
+ const reportObject = {
95
+ node: messageNode,
96
+ message: 'Multiple consecutive whitespaces are not allowed',
97
+ };
98
+ if (messageNode.type === 'Literal' &&
99
+ messageNode.value &&
100
+ typeof messageNode.value === 'string') {
101
+ reportObject.fix = function (fixer) {
102
+ return fixer.replaceText(messageNode, JSON.stringify(trimMultiWhitespaces(messageNode.value, ast)));
103
+ };
104
+ }
105
+ else if (messageNode.type === 'TemplateLiteral' &&
106
+ messageNode.quasis.length === 1 &&
107
+ messageNode.expressions.length === 0) {
108
+ reportObject.fix = function (fixer) {
109
+ return fixer.replaceText(messageNode, '`' +
110
+ trimMultiWhitespaces(messageNode.quasis[0].value.cooked, ast)
111
+ .replace(/\\/g, '\\\\')
112
+ .replace(/`/g, '\\`') +
113
+ '`');
114
+ };
115
+ }
116
+ context.report(reportObject);
117
+ }
118
+ }
119
+ }
120
+ const rule = {
121
+ meta: {
122
+ type: 'problem',
123
+ docs: {
124
+ description: 'Prevents usage of multiple consecutive whitespaces in message',
125
+ category: 'Errors',
126
+ recommended: false,
127
+ url: 'https://formatjs.io/docs/tooling/linter#no-multiple-whitespaces',
128
+ },
129
+ fixable: 'code',
130
+ },
131
+ create(context) {
132
+ const callExpressionVisitor = (node) => checkNode(context, node);
133
+ if (context.parserServices.defineTemplateBodyVisitor) {
134
+ return context.parserServices.defineTemplateBodyVisitor({
135
+ CallExpression: callExpressionVisitor,
136
+ }, {
137
+ CallExpression: callExpressionVisitor,
138
+ });
139
+ }
140
+ return {
141
+ JSXOpeningElement: (node) => checkNode(context, node),
142
+ CallExpression: callExpressionVisitor,
143
+ };
144
+ },
145
+ };
146
+ exports.default = rule;
@@ -0,0 +1,4 @@
1
+ import { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=no-offset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-offset.d.ts","sourceRoot":"","sources":["no-offset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAuD3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA8BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -0,0 +1,70 @@
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 NoOffsetError extends Error {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.message = 'offset are not allowed in plural rules';
9
+ }
10
+ }
11
+ function verifyAst(ast) {
12
+ for (const el of ast) {
13
+ if ((0, icu_messageformat_parser_1.isPluralElement)(el)) {
14
+ if (el.offset) {
15
+ throw new NoOffsetError();
16
+ }
17
+ const { options } = el;
18
+ for (const selector of Object.keys(options)) {
19
+ verifyAst(options[selector].value);
20
+ }
21
+ }
22
+ }
23
+ }
24
+ function checkNode(context, node) {
25
+ const settings = (0, util_1.getSettings)(context);
26
+ const msgs = (0, util_1.extractMessages)(node, settings);
27
+ for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
28
+ if (!defaultMessage || !messageNode) {
29
+ continue;
30
+ }
31
+ try {
32
+ verifyAst((0, icu_messageformat_parser_1.parse)(defaultMessage, {
33
+ ignoreTag: settings.ignoreTag,
34
+ }));
35
+ }
36
+ catch (e) {
37
+ context.report({
38
+ node: messageNode,
39
+ message: e.message,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ const rule = {
45
+ meta: {
46
+ type: 'problem',
47
+ docs: {
48
+ description: 'Disallow offset in plural rules',
49
+ category: 'Errors',
50
+ recommended: false,
51
+ url: 'https://formatjs.io/docs/tooling/linter#no-offset',
52
+ },
53
+ fixable: 'code',
54
+ },
55
+ create(context) {
56
+ const callExpressionVisitor = (node) => checkNode(context, node);
57
+ if (context.parserServices.defineTemplateBodyVisitor) {
58
+ return context.parserServices.defineTemplateBodyVisitor({
59
+ CallExpression: callExpressionVisitor,
60
+ }, {
61
+ CallExpression: callExpressionVisitor,
62
+ });
63
+ }
64
+ return {
65
+ JSXOpeningElement: (node) => checkNode(context, node),
66
+ CallExpression: callExpressionVisitor,
67
+ };
68
+ },
69
+ };
70
+ exports.default = rule;
package/util.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { Rule } from 'eslint';
2
+ import { TSESTree } from '@typescript-eslint/typescript-estree';
3
+ export interface MessageDescriptor {
4
+ id?: string;
5
+ defaultMessage?: string;
6
+ description?: string | object;
7
+ }
8
+ export interface Settings {
9
+ excludeMessageDeclCalls?: boolean;
10
+ additionalFunctionNames?: string[];
11
+ additionalComponentNames?: string[];
12
+ ignoreTag?: boolean;
13
+ }
14
+ export interface MessageDescriptorNodeInfo {
15
+ message: MessageDescriptor;
16
+ messageNode?: TSESTree.Property['value'] | TSESTree.JSXAttribute['value'];
17
+ messagePropNode?: TSESTree.Property | TSESTree.JSXAttribute;
18
+ descriptionNode?: TSESTree.Property['value'] | TSESTree.JSXAttribute['value'];
19
+ idValueNode?: TSESTree.Property['value'] | TSESTree.JSXAttribute['value'];
20
+ idPropNode?: TSESTree.Property | TSESTree.JSXAttribute;
21
+ }
22
+ export declare function getSettings({ settings }: Rule.RuleContext): Settings;
23
+ export declare function extractMessages(node: TSESTree.Node, { additionalComponentNames, additionalFunctionNames, excludeMessageDeclCalls, }?: Settings): Array<[MessageDescriptorNodeInfo, TSESTree.Expression | undefined]>;
24
+ //# sourceMappingURL=util.d.ts.map
package/util.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAC,QAAQ,EAAC,MAAM,sCAAsC,CAAA;AAE7D,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC9B;AAMD,MAAM,WAAW,QAAQ;IACvB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAA;IAClC,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AACD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,iBAAiB,CAAA;IAC1B,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IACzE,eAAe,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAA;IAC3D,eAAe,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IAC7E,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IACzE,UAAU,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAA;CACvD;AAED,wBAAgB,WAAW,CAAC,EAAC,QAAQ,EAAC,EAAE,IAAI,CAAC,WAAW,GAAG,QAAQ,CAElE;AA4ND,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,EACE,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,GACxB,GAAE,QAAa,GACf,KAAK,CAAC,CAAC,yBAAyB,EAAE,QAAQ,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,CAiDrE"}
package/util.js ADDED
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractMessages = exports.getSettings = void 0;
4
+ const FORMAT_FUNCTION_NAMES = new Set(['$formatMessage', 'formatMessage']);
5
+ const COMPONENT_NAMES = new Set(['FormattedMessage']);
6
+ const DECLARATION_FUNCTION_NAMES = new Set(['defineMessage']);
7
+ function getSettings({ settings }) {
8
+ return settings.formatjs ?? settings;
9
+ }
10
+ exports.getSettings = getSettings;
11
+ function isStringLiteral(node) {
12
+ return node.type === 'Literal' && typeof node.value === 'string';
13
+ }
14
+ function isTemplateLiteralWithoutVar(node) {
15
+ return node.type === 'TemplateLiteral' && node.quasis.length === 1;
16
+ }
17
+ function staticallyEvaluateStringConcat(node) {
18
+ if (!isStringLiteral(node.right)) {
19
+ return ['', false];
20
+ }
21
+ if (isStringLiteral(node.left)) {
22
+ return [String(node.left.value) + node.right.value, true];
23
+ }
24
+ if (node.left.type === 'BinaryExpression') {
25
+ const [result, isStaticallyEvaluatable] = staticallyEvaluateStringConcat(node.left);
26
+ return [result + node.right.value, isStaticallyEvaluatable];
27
+ }
28
+ return ['', false];
29
+ }
30
+ function isIntlFormatMessageCall(node) {
31
+ return (node.type === 'CallExpression' &&
32
+ node.callee.type === 'MemberExpression' &&
33
+ node.callee.object.type === 'Identifier' &&
34
+ node.callee.object.name === 'intl' &&
35
+ node.callee.property.type === 'Identifier' &&
36
+ node.callee.property.name === 'formatMessage' &&
37
+ node.arguments.length >= 1 &&
38
+ node.arguments[0].type === 'ObjectExpression');
39
+ }
40
+ function isSingleMessageDescriptorDeclaration(node, functionNames) {
41
+ return (node.type === 'CallExpression' &&
42
+ node.callee.type === 'Identifier' &&
43
+ functionNames.has(node.callee.name));
44
+ }
45
+ function isMultipleMessageDescriptorDeclaration(node) {
46
+ return (node.type === 'CallExpression' &&
47
+ node.callee.type === 'Identifier' &&
48
+ node.callee.name === 'defineMessages');
49
+ }
50
+ function extractMessageDescriptor(node) {
51
+ if (!node || node.type !== 'ObjectExpression') {
52
+ return;
53
+ }
54
+ const result = {
55
+ message: {},
56
+ messageNode: undefined,
57
+ messagePropNode: undefined,
58
+ descriptionNode: undefined,
59
+ idValueNode: undefined,
60
+ };
61
+ for (const prop of node.properties) {
62
+ if (prop.type !== 'Property' || prop.key.type !== 'Identifier') {
63
+ continue;
64
+ }
65
+ const valueNode = prop.value;
66
+ let value = undefined;
67
+ if (isStringLiteral(valueNode)) {
68
+ value = valueNode.value;
69
+ }
70
+ else if (isTemplateLiteralWithoutVar(valueNode)) {
71
+ value = valueNode.quasis[0].value.cooked;
72
+ }
73
+ else if (valueNode.type === 'BinaryExpression') {
74
+ const [result, isStatic] = staticallyEvaluateStringConcat(valueNode);
75
+ if (isStatic) {
76
+ value = result;
77
+ }
78
+ }
79
+ switch (prop.key.name) {
80
+ case 'defaultMessage':
81
+ result.messagePropNode = prop;
82
+ result.messageNode = valueNode;
83
+ result.message.defaultMessage = value;
84
+ break;
85
+ case 'description':
86
+ result.descriptionNode = valueNode;
87
+ result.message.description = value;
88
+ break;
89
+ case 'id':
90
+ result.message.id = value;
91
+ result.idValueNode = valueNode;
92
+ result.idPropNode = prop;
93
+ break;
94
+ }
95
+ }
96
+ return result;
97
+ }
98
+ function extractMessageDescriptorFromJSXElement(node) {
99
+ if (!node || !node.attributes) {
100
+ return;
101
+ }
102
+ let values;
103
+ const result = {
104
+ message: {},
105
+ messageNode: undefined,
106
+ messagePropNode: undefined,
107
+ descriptionNode: undefined,
108
+ idValueNode: undefined,
109
+ idPropNode: undefined,
110
+ };
111
+ let hasSpreadAttribute = false;
112
+ for (const prop of node.attributes) {
113
+ // We can't analyze spread attr
114
+ if (prop.type === 'JSXSpreadAttribute') {
115
+ hasSpreadAttribute = true;
116
+ }
117
+ if (prop.type !== 'JSXAttribute' || prop.name.type !== 'JSXIdentifier') {
118
+ continue;
119
+ }
120
+ const key = prop.name;
121
+ let valueNode = prop.value;
122
+ let value = undefined;
123
+ if (valueNode) {
124
+ if (isStringLiteral(valueNode)) {
125
+ value = valueNode.value;
126
+ }
127
+ else if (valueNode?.type === 'JSXExpressionContainer') {
128
+ const { expression } = valueNode;
129
+ if (expression.type === 'BinaryExpression') {
130
+ const [result, isStatic] = staticallyEvaluateStringConcat(expression);
131
+ if (isStatic) {
132
+ value = result;
133
+ }
134
+ }
135
+ else if (isTemplateLiteralWithoutVar(expression)) {
136
+ value = expression.quasis[0].value.cooked;
137
+ }
138
+ }
139
+ }
140
+ switch (key.name) {
141
+ case 'defaultMessage':
142
+ result.messagePropNode = prop;
143
+ result.messageNode = valueNode;
144
+ if (value) {
145
+ result.message.defaultMessage = value;
146
+ }
147
+ break;
148
+ case 'description':
149
+ result.descriptionNode = valueNode;
150
+ if (value) {
151
+ result.message.description = value;
152
+ }
153
+ break;
154
+ case 'id':
155
+ result.idValueNode = valueNode;
156
+ result.idPropNode = prop;
157
+ if (value) {
158
+ result.message.id = value;
159
+ }
160
+ break;
161
+ case 'values':
162
+ if (valueNode?.type === 'JSXExpressionContainer' &&
163
+ valueNode.expression.type === 'ObjectExpression') {
164
+ values = valueNode.expression;
165
+ }
166
+ break;
167
+ }
168
+ }
169
+ if (!result.messagePropNode &&
170
+ !result.descriptionNode &&
171
+ !result.idPropNode &&
172
+ hasSpreadAttribute) {
173
+ return;
174
+ }
175
+ return [result, values];
176
+ }
177
+ function extractMessageDescriptors(node) {
178
+ if (!node || node.type !== 'ObjectExpression' || !node.properties.length) {
179
+ return [];
180
+ }
181
+ const msgs = [];
182
+ for (const prop of node.properties) {
183
+ if (prop.type !== 'Property') {
184
+ continue;
185
+ }
186
+ const msg = prop.value;
187
+ if (msg.type !== 'ObjectExpression') {
188
+ continue;
189
+ }
190
+ const nodeInfo = extractMessageDescriptor(msg);
191
+ if (nodeInfo) {
192
+ msgs.push(nodeInfo);
193
+ }
194
+ }
195
+ return msgs;
196
+ }
197
+ function extractMessages(node, { additionalComponentNames, additionalFunctionNames, excludeMessageDeclCalls, } = {}) {
198
+ const allFormatFunctionNames = Array.isArray(additionalFunctionNames)
199
+ ? new Set([
200
+ ...Array.from(FORMAT_FUNCTION_NAMES),
201
+ ...additionalFunctionNames,
202
+ ])
203
+ : FORMAT_FUNCTION_NAMES;
204
+ const allComponentNames = Array.isArray(additionalComponentNames)
205
+ ? new Set([...Array.from(COMPONENT_NAMES), ...additionalComponentNames])
206
+ : COMPONENT_NAMES;
207
+ if (node.type === 'CallExpression') {
208
+ const expr = node;
209
+ const args0 = expr.arguments[0];
210
+ const args1 = expr.arguments[1];
211
+ // We can't really analyze spread element
212
+ if (!args0 || args0.type === 'SpreadElement') {
213
+ return [];
214
+ }
215
+ if ((!excludeMessageDeclCalls &&
216
+ isSingleMessageDescriptorDeclaration(node, DECLARATION_FUNCTION_NAMES)) ||
217
+ isIntlFormatMessageCall(node) ||
218
+ isSingleMessageDescriptorDeclaration(node, allFormatFunctionNames)) {
219
+ const msgDescriptorNodeInfo = extractMessageDescriptor(args0);
220
+ if (msgDescriptorNodeInfo && (!args1 || args1.type !== 'SpreadElement')) {
221
+ return [[msgDescriptorNodeInfo, args1]];
222
+ }
223
+ }
224
+ else if (!excludeMessageDeclCalls &&
225
+ isMultipleMessageDescriptorDeclaration(node)) {
226
+ return extractMessageDescriptors(args0).map(msg => [msg, undefined]);
227
+ }
228
+ }
229
+ else if (node.type === 'JSXOpeningElement' &&
230
+ node.name &&
231
+ node.name.type === 'JSXIdentifier' &&
232
+ allComponentNames.has(node.name.name)) {
233
+ const msgDescriptorNodeInfo = extractMessageDescriptorFromJSXElement(node);
234
+ if (msgDescriptorNodeInfo) {
235
+ return [msgDescriptorNodeInfo];
236
+ }
237
+ }
238
+ return [];
239
+ }
240
+ exports.extractMessages = extractMessages;
package/BUILD DELETED
@@ -1,89 +0,0 @@
1
- load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
2
- load("@aspect_rules_js//npm/private:npm_package.bzl", "npm_package")
3
- load("@npm//:defs.bzl", "npm_link_all_packages")
4
- load("//tools:index.bzl", "check_format", "package_json_test", "ts_compile_node")
5
- load("//tools:jest.bzl", "jest_test")
6
-
7
- npm_link_all_packages(name = "node_modules")
8
-
9
- PACKAGE_NAME = "eslint-plugin-formatjs"
10
-
11
- npm_package(
12
- name = PACKAGE_NAME,
13
- srcs = [
14
- "LICENSE.md",
15
- "README.md",
16
- "package.json",
17
- ":dist",
18
- ],
19
- package = PACKAGE_NAME,
20
- visibility = ["//visibility:public"],
21
- )
22
-
23
- SRCS = glob(["rules/*.ts"]) + [
24
- "index.ts",
25
- "util.ts",
26
- ]
27
-
28
- SRC_DEPS = [
29
- "//:node_modules/@types/eslint",
30
- "//:node_modules/@types/estree",
31
- "//:node_modules/@types/node",
32
- "//:node_modules/@types/picomatch",
33
- "//:node_modules/@typescript-eslint/typescript-estree",
34
- "//:node_modules/emoji-regex",
35
- "//:node_modules/eslint",
36
- "//:node_modules/picomatch",
37
- "//:node_modules/typescript",
38
- ":node_modules/@formatjs/icu-messageformat-parser",
39
- ":node_modules/@formatjs/ts-transformer",
40
- ]
41
-
42
- ts_compile_node(
43
- name = "dist",
44
- srcs = SRCS,
45
- package = PACKAGE_NAME,
46
- deps = SRC_DEPS,
47
- )
48
-
49
- TESTS_BASE_SRCS = SRCS + glob(
50
- [
51
- "tests/*.ts",
52
- ],
53
- exclude = ["tests/*.test.ts"],
54
- )
55
-
56
- TEST_FILES = glob([
57
- "tests/*.test.ts",
58
- ])
59
-
60
- [jest_test(
61
- name = "unit-%s" % f[6:f.index(".test.ts")],
62
- srcs = TESTS_BASE_SRCS + [f],
63
- deps = [
64
- "//:node_modules/@typescript-eslint/parser",
65
- "//:node_modules/vue-eslint-parser",
66
- ] + SRC_DEPS,
67
- ) for f in TEST_FILES]
68
-
69
- write_source_files(
70
- name = "tsconfig_json",
71
- files = {"tsconfig.json": "//tools:tsconfig.golden.json"},
72
- )
73
-
74
- check_format(
75
- name = "prettier",
76
- srcs = glob(
77
- [
78
- "**/*",
79
- ],
80
- exclude = [
81
- "CHANGELOG.md",
82
- ],
83
- ),
84
- )
85
-
86
- package_json_test(
87
- name = "package_json_test",
88
- deps = SRC_DEPS,
89
- )