eslint-plugin-formatjs 5.4.2 → 6.0.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.
Files changed (74) hide show
  1. package/context-compat.js +1 -5
  2. package/index.d.ts +15 -1
  3. package/index.js +46 -45
  4. package/package.json +5 -3
  5. package/rules/blocklist-elements.d.ts +1 -2
  6. package/rules/blocklist-elements.js +23 -26
  7. package/rules/enforce-default-message.js +8 -11
  8. package/rules/enforce-description.js +8 -11
  9. package/rules/enforce-id.d.ts +1 -0
  10. package/rules/enforce-id.js +21 -14
  11. package/rules/enforce-placeholders.js +14 -17
  12. package/rules/enforce-plural-rules.js +10 -13
  13. package/rules/no-camel-case.js +11 -14
  14. package/rules/no-complex-selectors.js +13 -16
  15. package/rules/no-emoji.js +11 -14
  16. package/rules/no-id.js +6 -9
  17. package/rules/no-invalid-icu.js +9 -12
  18. package/rules/no-literal-string-in-jsx.js +33 -13
  19. package/rules/no-literal-string-in-object.js +8 -11
  20. package/rules/no-missing-icu-plural-one-placeholders.js +15 -19
  21. package/rules/no-multiple-plurals.js +10 -13
  22. package/rules/no-multiple-whitespaces.js +27 -32
  23. package/rules/no-offset.js +10 -13
  24. package/rules/no-useless-message.js +13 -16
  25. package/rules/prefer-formatted-message.js +4 -7
  26. package/rules/prefer-pound-in-plural.js +25 -29
  27. package/util.js +5 -12
  28. package/lib_esnext/context-compat.d.ts +0 -3
  29. package/lib_esnext/context-compat.js +0 -6
  30. package/lib_esnext/index.d.ts +0 -1
  31. package/lib_esnext/index.js +0 -146
  32. package/lib_esnext/package.json +0 -31
  33. package/lib_esnext/rules/blocklist-elements.d.ts +0 -15
  34. package/lib_esnext/rules/blocklist-elements.js +0 -132
  35. package/lib_esnext/rules/enforce-default-message.d.ts +0 -10
  36. package/lib_esnext/rules/enforce-default-message.js +0 -68
  37. package/lib_esnext/rules/enforce-description.d.ts +0 -10
  38. package/lib_esnext/rules/enforce-description.js +0 -66
  39. package/lib_esnext/rules/enforce-id.d.ts +0 -10
  40. package/lib_esnext/rules/enforce-id.js +0 -153
  41. package/lib_esnext/rules/enforce-placeholders.d.ts +0 -8
  42. package/lib_esnext/rules/enforce-placeholders.js +0 -147
  43. package/lib_esnext/rules/enforce-plural-rules.d.ts +0 -17
  44. package/lib_esnext/rules/enforce-plural-rules.js +0 -103
  45. package/lib_esnext/rules/no-camel-case.d.ts +0 -5
  46. package/lib_esnext/rules/no-camel-case.js +0 -76
  47. package/lib_esnext/rules/no-complex-selectors.d.ts +0 -9
  48. package/lib_esnext/rules/no-complex-selectors.js +0 -136
  49. package/lib_esnext/rules/no-emoji.d.ts +0 -9
  50. package/lib_esnext/rules/no-emoji.js +0 -99
  51. package/lib_esnext/rules/no-id.d.ts +0 -5
  52. package/lib_esnext/rules/no-id.js +0 -58
  53. package/lib_esnext/rules/no-invalid-icu.d.ts +0 -5
  54. package/lib_esnext/rules/no-invalid-icu.js +0 -60
  55. package/lib_esnext/rules/no-literal-string-in-jsx.d.ts +0 -13
  56. package/lib_esnext/rules/no-literal-string-in-jsx.js +0 -179
  57. package/lib_esnext/rules/no-literal-string-in-object.d.ts +0 -9
  58. package/lib_esnext/rules/no-literal-string-in-object.js +0 -90
  59. package/lib_esnext/rules/no-missing-icu-plural-one-placeholders.d.ts +0 -6
  60. package/lib_esnext/rules/no-missing-icu-plural-one-placeholders.js +0 -99
  61. package/lib_esnext/rules/no-multiple-plurals.d.ts +0 -5
  62. package/lib_esnext/rules/no-multiple-plurals.js +0 -70
  63. package/lib_esnext/rules/no-multiple-whitespaces.d.ts +0 -5
  64. package/lib_esnext/rules/no-multiple-whitespaces.js +0 -141
  65. package/lib_esnext/rules/no-offset.d.ts +0 -5
  66. package/lib_esnext/rules/no-offset.js +0 -69
  67. package/lib_esnext/rules/no-useless-message.d.ts +0 -5
  68. package/lib_esnext/rules/no-useless-message.js +0 -71
  69. package/lib_esnext/rules/prefer-formatted-message.d.ts +0 -5
  70. package/lib_esnext/rules/prefer-formatted-message.js +0 -33
  71. package/lib_esnext/rules/prefer-pound-in-plural.d.ts +0 -5
  72. package/lib_esnext/rules/prefer-pound-in-plural.js +0 -191
  73. package/lib_esnext/util.d.ts +0 -32
  74. package/lib_esnext/util.js +0 -280
@@ -1,179 +0,0 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
- import picomatch from 'picomatch';
3
- const propMatcherSchema = {
4
- type: 'array',
5
- items: {
6
- type: 'array',
7
- items: [{ type: 'string' }, { type: 'string' }],
8
- },
9
- };
10
- const defaultPropIncludePattern = [
11
- ['*', 'aria-{label,description,details,errormessage}'],
12
- ['[a-z]*([a-z0-9])', '(placeholder|title)'],
13
- ['img', 'alt'],
14
- ];
15
- const defaultPropExcludePattern = [];
16
- function stringifyJsxTagName(tagName) {
17
- switch (tagName.type) {
18
- case TSESTree.AST_NODE_TYPES.JSXIdentifier:
19
- return tagName.name;
20
- case TSESTree.AST_NODE_TYPES.JSXMemberExpression:
21
- return `${stringifyJsxTagName(tagName.object)}.${tagName.property.name}`;
22
- case TSESTree.AST_NODE_TYPES.JSXNamespacedName:
23
- return `${tagName.namespace.name}:${tagName.name.name}`;
24
- }
25
- }
26
- function compilePropMatcher(propMatcher) {
27
- return propMatcher.map(([tagNamePattern, propNamePattern]) => {
28
- return [
29
- picomatch.makeRe(tagNamePattern, { contains: false }),
30
- picomatch.makeRe(propNamePattern, { contains: false }),
31
- ];
32
- });
33
- }
34
- export const name = 'no-literal-string-in-jsx';
35
- export const rule = {
36
- meta: {
37
- type: 'problem',
38
- docs: {
39
- description: 'Disallow untranslated literal strings without translation.',
40
- url: 'https://formatjs.github.io/docs/tooling/linter#no-literal-string-in-jsx',
41
- },
42
- schema: [
43
- {
44
- type: 'object',
45
- properties: {
46
- props: {
47
- type: 'object',
48
- properties: {
49
- include: {
50
- ...propMatcherSchema,
51
- },
52
- exclude: {
53
- ...propMatcherSchema,
54
- },
55
- },
56
- },
57
- },
58
- },
59
- ],
60
- messages: {
61
- noLiteralStringInJsx: 'Cannot have untranslated text in JSX',
62
- },
63
- },
64
- defaultOptions: [],
65
- // TODO: Vue support
66
- create(context) {
67
- const userConfig = context.options[0] || {};
68
- const propIncludePattern = compilePropMatcher([
69
- ...defaultPropIncludePattern,
70
- ...(userConfig.props?.include ?? []),
71
- ]);
72
- const propExcludePattern = compilePropMatcher([
73
- ...defaultPropExcludePattern,
74
- ...(userConfig.props?.exclude ?? []),
75
- ]);
76
- const lexicalJsxStack = [];
77
- const shouldSkipCurrentJsxAttribute = (node) => {
78
- const currentJsxNode = lexicalJsxStack[lexicalJsxStack.length - 1];
79
- if (currentJsxNode.type === 'JSXFragment') {
80
- return false;
81
- }
82
- const nameString = stringifyJsxTagName(currentJsxNode.openingElement.name);
83
- const attributeName = typeof node.name.name === 'string'
84
- ? node.name.name
85
- : node.name.name.name;
86
- // match exclude
87
- for (const [tagNamePattern, propNamePattern] of propExcludePattern) {
88
- if (tagNamePattern.test(nameString) &&
89
- propNamePattern.test(attributeName)) {
90
- return true;
91
- }
92
- }
93
- // match include
94
- for (const [tagNamePattern, propNamePattern] of propIncludePattern) {
95
- if (tagNamePattern.test(nameString) &&
96
- propNamePattern.test(attributeName)) {
97
- return false;
98
- }
99
- }
100
- return true;
101
- };
102
- const checkJSXExpression = (node) => {
103
- // Check if this is either a string literal / template literal, or the concat of them.
104
- // It also ignores the empty string.
105
- if ((node.type === 'Literal' &&
106
- typeof node.value === 'string' &&
107
- node.value.length > 0) ||
108
- (node.type === 'TemplateLiteral' &&
109
- (node.quasis.length > 1 || node.quasis[0].value.raw.length > 0))) {
110
- context.report({
111
- node: node,
112
- messageId: 'noLiteralStringInJsx',
113
- });
114
- }
115
- else if (node.type === 'BinaryExpression' && node.operator === '+') {
116
- checkJSXExpression(node.left);
117
- checkJSXExpression(node.right);
118
- }
119
- else if (node.type === 'ConditionalExpression') {
120
- checkJSXExpression(node.consequent);
121
- checkJSXExpression(node.alternate);
122
- }
123
- else if (node.type === 'LogicalExpression') {
124
- checkJSXExpression(node.left);
125
- checkJSXExpression(node.right);
126
- }
127
- };
128
- return {
129
- JSXElement: (node) => {
130
- lexicalJsxStack.push(node);
131
- },
132
- 'JSXElement:exit': () => {
133
- lexicalJsxStack.pop();
134
- },
135
- JSXFragment: (node) => {
136
- lexicalJsxStack.push(node);
137
- },
138
- 'JSXFragment:exit': () => {
139
- lexicalJsxStack.pop();
140
- },
141
- JSXAttribute: (node) => {
142
- if (shouldSkipCurrentJsxAttribute(node)) {
143
- return;
144
- }
145
- if (!node.value) {
146
- return;
147
- }
148
- if (node.value.type === 'Literal' &&
149
- typeof node.value.value === 'string' &&
150
- node.value.value.length > 0) {
151
- context.report({
152
- node: node,
153
- messageId: 'noLiteralStringInJsx',
154
- });
155
- }
156
- else if (node.value.type === 'JSXExpressionContainer' &&
157
- node.value.expression.type !== 'JSXEmptyExpression') {
158
- checkJSXExpression(node.value.expression);
159
- }
160
- },
161
- JSXText: (node) => {
162
- // Ignore purely spacing fragments
163
- if (!node.value.replace(/\s*/gm, '')) {
164
- return;
165
- }
166
- context.report({
167
- node: node,
168
- messageId: 'noLiteralStringInJsx',
169
- });
170
- },
171
- // Children expression container
172
- 'JSXElement > JSXExpressionContainer': (node) => {
173
- if (node.expression.type !== 'JSXEmptyExpression') {
174
- checkJSXExpression(node.expression);
175
- }
176
- },
177
- };
178
- },
179
- };
@@ -1,9 +0,0 @@
1
- import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
2
- type MessageIds = 'untranslatedProperty';
3
- type PropertyConfig = {
4
- include: string[];
5
- };
6
- type Options = [PropertyConfig?];
7
- export declare const name = "no-literal-string-in-object";
8
- export declare const rule: RuleModule<MessageIds, Options>;
9
- export {};
@@ -1,90 +0,0 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
- import { getParserServices } from '../context-compat';
3
- export const name = 'no-literal-string-in-object';
4
- export const rule = {
5
- meta: {
6
- type: 'problem',
7
- docs: {
8
- description: 'Enforce translation of specific object properties',
9
- url: 'https://formatjs.github.io/docs/tooling/linter#no-literal-string-in-object',
10
- },
11
- schema: [
12
- {
13
- type: 'object',
14
- properties: {
15
- include: {
16
- type: 'array',
17
- items: { type: 'string' },
18
- default: ['label'],
19
- },
20
- },
21
- additionalProperties: false,
22
- },
23
- ],
24
- messages: {
25
- untranslatedProperty: 'Object property: `{{propertyKey}}` might contain an untranslated literal string',
26
- },
27
- },
28
- defaultOptions: [],
29
- create(context) {
30
- const propertyVisitor = (node) => {
31
- checkProperty(context, node);
32
- };
33
- const parserServices = getParserServices(context);
34
- //@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
35
- if (parserServices?.defineTemplateBodyVisitor) {
36
- //@ts-expect-error
37
- return parserServices.defineTemplateBodyVisitor({
38
- Property: propertyVisitor,
39
- }, {
40
- Property: propertyVisitor,
41
- });
42
- }
43
- return {
44
- Property: propertyVisitor,
45
- };
46
- },
47
- };
48
- function checkProperty(context, node) {
49
- const config = {
50
- include: ['label'],
51
- ...(context.options[0] || {}),
52
- };
53
- const propertyKey = node.key.type === TSESTree.AST_NODE_TYPES.Identifier
54
- ? node.key.name
55
- : node.key.type === TSESTree.AST_NODE_TYPES.Literal &&
56
- typeof node.key.value === 'string'
57
- ? node.key.value
58
- : null;
59
- if (!propertyKey || !config.include.includes(propertyKey)) {
60
- return;
61
- }
62
- checkPropertyValue(context, node.value, propertyKey);
63
- }
64
- function checkPropertyValue(context, node, propertyKey) {
65
- if ((node.type === 'Literal' &&
66
- typeof node.value === 'string' &&
67
- node.value.length > 0) ||
68
- (node.type === 'TemplateLiteral' &&
69
- (node.quasis.length > 1 || node.quasis[0].value.raw.length > 0))) {
70
- context.report({
71
- node: node,
72
- messageId: 'untranslatedProperty',
73
- data: {
74
- propertyKey: propertyKey,
75
- },
76
- });
77
- }
78
- else if (node.type === 'BinaryExpression' && node.operator === '+') {
79
- checkPropertyValue(context, node.left, propertyKey);
80
- checkPropertyValue(context, node.right, propertyKey);
81
- }
82
- else if (node.type === 'ConditionalExpression') {
83
- checkPropertyValue(context, node.consequent, propertyKey);
84
- checkPropertyValue(context, node.alternate, propertyKey);
85
- }
86
- else if (node.type === 'LogicalExpression') {
87
- checkPropertyValue(context, node.left, propertyKey);
88
- checkPropertyValue(context, node.right, propertyKey);
89
- }
90
- }
@@ -1,6 +0,0 @@
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 {};
@@ -1,99 +0,0 @@
1
- import { isLiteralElement, isPluralElement, isSelectElement, isTagElement, parse, } from '@formatjs/icu-messageformat-parser';
2
- import MagicString from 'magic-string';
3
- import { getParserServices } from '../context-compat';
4
- import { extractMessages, patchMessage } from '../util';
5
- export const name = 'no-missing-icu-plural-one-placeholders';
6
- function verifyAst(context, messageNode, ast) {
7
- const patches = [];
8
- _verifyAstAndReplace(ast);
9
- if (patches.length > 0) {
10
- const patchedMessage = patchMessage(messageNode, ast, content => {
11
- return patches
12
- .reduce((magicString, patch) => {
13
- switch (patch.type) {
14
- case 'prependLeft':
15
- return magicString.prependLeft(patch.index, patch.content);
16
- case 'remove':
17
- return magicString.remove(patch.start, patch.end);
18
- case 'update':
19
- return magicString.update(patch.start, patch.end, patch.content);
20
- }
21
- }, new MagicString(content))
22
- .toString();
23
- });
24
- context.report({
25
- node: messageNode,
26
- messageId: 'noMissingIcuPluralOnePlaceholders',
27
- fix: patchedMessage !== null
28
- ? fixer => fixer.replaceText(messageNode, patchedMessage)
29
- : null,
30
- });
31
- }
32
- function _verifyAstAndReplace(ast, root = true) {
33
- for (const el of ast) {
34
- if (isPluralElement(el) && el.options['one']) {
35
- _verifyAstAndReplace(el.options['one'].value, false);
36
- }
37
- else if (isSelectElement(el)) {
38
- for (const { value } of Object.values(el.options)) {
39
- _verifyAstAndReplace(value, root);
40
- }
41
- }
42
- else if (isTagElement(el)) {
43
- _verifyAstAndReplace(el.children, root);
44
- }
45
- else if (!root && isLiteralElement(el)) {
46
- const match = el.value.match(/\b1\b/);
47
- if (match && el.location) {
48
- patches.push({
49
- type: 'update',
50
- start: el.location.start.offset,
51
- end: el.location.end.offset,
52
- content: el.value.replace(match[0], '#'),
53
- });
54
- }
55
- }
56
- }
57
- }
58
- }
59
- function checkNode(context, node) {
60
- const msgs = extractMessages(node);
61
- for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
62
- if (!defaultMessage || !messageNode) {
63
- continue;
64
- }
65
- verifyAst(context, messageNode, parse(defaultMessage, { captureLocation: true }));
66
- }
67
- }
68
- export const rule = {
69
- meta: {
70
- type: 'problem',
71
- docs: {
72
- description: 'We use `one {# item}` instead of `one {1 item}` in ICU messages as some locales use the `one` formatting for other similar numbers.',
73
- url: 'https://formatjs.github.io/docs/tooling/linter#no-explicit-icu-plural',
74
- },
75
- fixable: 'code',
76
- messages: {
77
- noMissingIcuPluralOnePlaceholders: 'Use `one {# item}` instead of `one {1 item}` in ICU messages.',
78
- },
79
- schema: [],
80
- },
81
- defaultOptions: [],
82
- create(context) {
83
- const callExpressionVisitor = (node) => checkNode(context, node);
84
- const parserServices = getParserServices(context);
85
- //@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
86
- if (parserServices?.defineTemplateBodyVisitor) {
87
- //@ts-expect-error
88
- return parserServices.defineTemplateBodyVisitor({
89
- CallExpression: callExpressionVisitor,
90
- }, {
91
- CallExpression: callExpressionVisitor,
92
- });
93
- }
94
- return {
95
- JSXOpeningElement: (node) => checkNode(context, node),
96
- CallExpression: callExpressionVisitor,
97
- };
98
- },
99
- };
@@ -1,5 +0,0 @@
1
- import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
2
- type MessageIds = 'noMultiplePlurals';
3
- export declare const name = "no-multiple-plurals";
4
- export declare const rule: RuleModule<MessageIds>;
5
- export {};
@@ -1,70 +0,0 @@
1
- import { isPluralElement, parse, } from '@formatjs/icu-messageformat-parser';
2
- import { getParserServices } from '../context-compat';
3
- import { extractMessages, getSettings } from '../util';
4
- function verifyAst(ast, pluralCount = { count: 0 }) {
5
- const errors = [];
6
- for (const el of ast) {
7
- if (isPluralElement(el)) {
8
- pluralCount.count++;
9
- if (pluralCount.count > 1) {
10
- errors.push({ messageId: 'noMultiplePlurals', data: {} });
11
- }
12
- const { options } = el;
13
- for (const selector of Object.keys(options)) {
14
- errors.push(...verifyAst(options[selector].value, pluralCount));
15
- }
16
- }
17
- }
18
- return errors;
19
- }
20
- function checkNode(context, node) {
21
- const settings = getSettings(context);
22
- const msgs = extractMessages(node, settings);
23
- for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
24
- if (!defaultMessage || !messageNode) {
25
- continue;
26
- }
27
- const errors = verifyAst(parse(defaultMessage, {
28
- ignoreTag: settings.ignoreTag,
29
- }));
30
- for (const error of errors) {
31
- context.report({
32
- node,
33
- ...error,
34
- });
35
- }
36
- }
37
- }
38
- export const name = 'no-multiple-plurals';
39
- export const rule = {
40
- meta: {
41
- type: 'problem',
42
- docs: {
43
- description: 'Disallow multiple plural rules in the same message',
44
- url: 'https://formatjs.github.io/docs/tooling/linter#no-multiple-plurals',
45
- },
46
- fixable: 'code',
47
- schema: [],
48
- messages: {
49
- noMultiplePlurals: 'Multiple plural rules in the same message',
50
- },
51
- },
52
- defaultOptions: [],
53
- create(context) {
54
- const callExpressionVisitor = (node) => checkNode(context, node);
55
- const parserServices = getParserServices(context);
56
- //@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
57
- if (parserServices?.defineTemplateBodyVisitor) {
58
- //@ts-expect-error
59
- return 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
- };
@@ -1,5 +0,0 @@
1
- import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
2
- type MessageIds = 'noMultipleWhitespaces' | 'parserError';
3
- export declare const name = "no-multiple-whitespaces";
4
- export declare const rule: RuleModule<MessageIds>;
5
- export {};
@@ -1,141 +0,0 @@
1
- import { parse, TYPE, } from '@formatjs/icu-messageformat-parser';
2
- import { getParserServices } from '../context-compat';
3
- import { extractMessages, getSettings, patchMessage } from '../util';
4
- function isAstValid(ast) {
5
- for (const element of ast) {
6
- switch (element.type) {
7
- case TYPE.literal:
8
- if (/\s{2,}/gm.test(element.value)) {
9
- return false;
10
- }
11
- break;
12
- case TYPE.argument:
13
- case TYPE.date:
14
- case TYPE.literal:
15
- case TYPE.number:
16
- case TYPE.pound:
17
- case TYPE.time:
18
- break;
19
- case TYPE.plural:
20
- case TYPE.select: {
21
- for (const option of Object.values(element.options)) {
22
- if (!isAstValid(option.value)) {
23
- return false;
24
- }
25
- }
26
- break;
27
- }
28
- case TYPE.tag:
29
- return isAstValid(element.children);
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 TYPE.literal:
40
- literalElements.push(element);
41
- break;
42
- case TYPE.argument:
43
- case TYPE.date:
44
- case TYPE.literal:
45
- case TYPE.number:
46
- case TYPE.pound:
47
- case TYPE.time:
48
- break;
49
- case TYPE.plural:
50
- case TYPE.select: {
51
- for (const option of Object.values(element.options)) {
52
- collectLiteralElements(option.value);
53
- }
54
- break;
55
- }
56
- case TYPE.tag:
57
- collectLiteralElements(element.children);
58
- break;
59
- }
60
- }
61
- };
62
- collectLiteralElements(ast);
63
- // Surgically trim whitespaces in the literal element ranges.
64
- // This is to preserve the original whitespaces and newlines info that are lost to parsing.
65
- let trimmedFragments = [];
66
- let currentOffset = 0;
67
- for (const literal of literalElements) {
68
- const { start, end } = literal.location;
69
- const startOffset = start.offset;
70
- const endOffset = end.offset;
71
- trimmedFragments.push(message.slice(currentOffset, startOffset));
72
- trimmedFragments.push(message.slice(startOffset, endOffset).replace(/\s{2,}/gm, ' '));
73
- currentOffset = endOffset;
74
- }
75
- trimmedFragments.push(message.slice(currentOffset));
76
- return trimmedFragments.join('');
77
- }
78
- function checkNode(context, node) {
79
- const msgs = extractMessages(node, getSettings(context));
80
- for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
81
- if (!defaultMessage || !messageNode) {
82
- continue;
83
- }
84
- let ast;
85
- try {
86
- ast = parse(defaultMessage, { captureLocation: true });
87
- }
88
- catch (e) {
89
- context.report({
90
- node: messageNode,
91
- messageId: 'parserError',
92
- data: { message: e instanceof Error ? e.message : String(e) },
93
- });
94
- return;
95
- }
96
- if (!isAstValid(ast)) {
97
- const newMessage = patchMessage(messageNode, ast, trimMultiWhitespaces);
98
- context.report({
99
- node: messageNode,
100
- messageId: 'noMultipleWhitespaces',
101
- fix: newMessage !== null
102
- ? fixer => fixer.replaceText(messageNode, newMessage)
103
- : null,
104
- });
105
- }
106
- }
107
- }
108
- export const name = 'no-multiple-whitespaces';
109
- export const rule = {
110
- meta: {
111
- type: 'problem',
112
- docs: {
113
- description: 'Prevents usage of multiple consecutive whitespaces in message',
114
- url: 'https://formatjs.github.io/docs/tooling/linter#no-multiple-whitespaces',
115
- },
116
- messages: {
117
- noMultipleWhitespaces: 'Multiple consecutive whitespaces are not allowed',
118
- parserError: '{{message}}',
119
- },
120
- fixable: 'code',
121
- schema: [],
122
- },
123
- defaultOptions: [],
124
- create(context) {
125
- const callExpressionVisitor = (node) => checkNode(context, node);
126
- const parserServices = getParserServices(context);
127
- //@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
128
- if (parserServices?.defineTemplateBodyVisitor) {
129
- //@ts-expect-error
130
- return parserServices.defineTemplateBodyVisitor({
131
- CallExpression: callExpressionVisitor,
132
- }, {
133
- CallExpression: callExpressionVisitor,
134
- });
135
- }
136
- return {
137
- JSXOpeningElement: (node) => checkNode(context, node),
138
- CallExpression: callExpressionVisitor,
139
- };
140
- },
141
- };
@@ -1,5 +0,0 @@
1
- import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
2
- type MessageIds = 'noOffset';
3
- export declare const name = "no-offset";
4
- export declare const rule: RuleModule<MessageIds>;
5
- export {};
@@ -1,69 +0,0 @@
1
- import { isPluralElement, parse, } from '@formatjs/icu-messageformat-parser';
2
- import { getParserServices } from '../context-compat';
3
- import { extractMessages, getSettings } from '../util';
4
- function verifyAst(ast) {
5
- const errors = [];
6
- for (const el of ast) {
7
- if (isPluralElement(el)) {
8
- if (el.offset) {
9
- errors.push({ messageId: 'noOffset', data: {} });
10
- }
11
- const { options } = el;
12
- for (const selector of Object.keys(options)) {
13
- errors.push(...verifyAst(options[selector].value));
14
- }
15
- }
16
- }
17
- return errors;
18
- }
19
- function checkNode(context, node) {
20
- const settings = getSettings(context);
21
- const msgs = extractMessages(node, settings);
22
- for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
23
- if (!defaultMessage || !messageNode) {
24
- continue;
25
- }
26
- const errors = verifyAst(parse(defaultMessage, {
27
- ignoreTag: settings.ignoreTag,
28
- }));
29
- for (const error of errors) {
30
- context.report({
31
- node: messageNode,
32
- ...error,
33
- });
34
- }
35
- }
36
- }
37
- export const name = 'no-offset';
38
- export const rule = {
39
- meta: {
40
- type: 'problem',
41
- docs: {
42
- description: 'Disallow offset in plural rules',
43
- url: 'https://formatjs.github.io/docs/tooling/linter#no-offset',
44
- },
45
- fixable: 'code',
46
- messages: {
47
- noOffset: 'offset is not allowed',
48
- },
49
- schema: [],
50
- },
51
- defaultOptions: [],
52
- create(context) {
53
- const callExpressionVisitor = (node) => checkNode(context, node);
54
- const parserServices = getParserServices(context);
55
- //@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
56
- if (parserServices?.defineTemplateBodyVisitor) {
57
- //@ts-expect-error
58
- return 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
- };