eslint-plugin-formatjs 5.4.2 → 6.0.0
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/context-compat.js +1 -5
- package/index.d.ts +15 -1
- package/index.js +46 -45
- package/package.json +12 -10
- package/rules/blocklist-elements.d.ts +1 -2
- package/rules/blocklist-elements.js +23 -26
- package/rules/enforce-default-message.js +8 -11
- package/rules/enforce-description.js +8 -11
- package/rules/enforce-id.d.ts +1 -0
- package/rules/enforce-id.js +21 -14
- package/rules/enforce-placeholders.js +14 -17
- package/rules/enforce-plural-rules.js +10 -13
- package/rules/no-camel-case.js +11 -14
- package/rules/no-complex-selectors.js +13 -16
- package/rules/no-emoji.js +11 -14
- package/rules/no-id.js +6 -9
- package/rules/no-invalid-icu.js +9 -12
- package/rules/no-literal-string-in-jsx.js +33 -13
- package/rules/no-literal-string-in-object.js +8 -11
- package/rules/no-missing-icu-plural-one-placeholders.js +15 -19
- package/rules/no-multiple-plurals.js +10 -13
- package/rules/no-multiple-whitespaces.js +27 -32
- package/rules/no-offset.js +10 -13
- package/rules/no-useless-message.js +13 -16
- package/rules/prefer-formatted-message.js +4 -7
- package/rules/prefer-pound-in-plural.js +25 -29
- package/util.js +5 -12
- package/lib_esnext/context-compat.d.ts +0 -3
- package/lib_esnext/context-compat.js +0 -6
- package/lib_esnext/index.d.ts +0 -1
- package/lib_esnext/index.js +0 -146
- package/lib_esnext/package.json +0 -31
- package/lib_esnext/rules/blocklist-elements.d.ts +0 -15
- package/lib_esnext/rules/blocklist-elements.js +0 -132
- package/lib_esnext/rules/enforce-default-message.d.ts +0 -10
- package/lib_esnext/rules/enforce-default-message.js +0 -68
- package/lib_esnext/rules/enforce-description.d.ts +0 -10
- package/lib_esnext/rules/enforce-description.js +0 -66
- package/lib_esnext/rules/enforce-id.d.ts +0 -10
- package/lib_esnext/rules/enforce-id.js +0 -153
- package/lib_esnext/rules/enforce-placeholders.d.ts +0 -8
- package/lib_esnext/rules/enforce-placeholders.js +0 -147
- package/lib_esnext/rules/enforce-plural-rules.d.ts +0 -17
- package/lib_esnext/rules/enforce-plural-rules.js +0 -103
- package/lib_esnext/rules/no-camel-case.d.ts +0 -5
- package/lib_esnext/rules/no-camel-case.js +0 -76
- package/lib_esnext/rules/no-complex-selectors.d.ts +0 -9
- package/lib_esnext/rules/no-complex-selectors.js +0 -136
- package/lib_esnext/rules/no-emoji.d.ts +0 -9
- package/lib_esnext/rules/no-emoji.js +0 -99
- package/lib_esnext/rules/no-id.d.ts +0 -5
- package/lib_esnext/rules/no-id.js +0 -58
- package/lib_esnext/rules/no-invalid-icu.d.ts +0 -5
- package/lib_esnext/rules/no-invalid-icu.js +0 -60
- package/lib_esnext/rules/no-literal-string-in-jsx.d.ts +0 -13
- package/lib_esnext/rules/no-literal-string-in-jsx.js +0 -179
- package/lib_esnext/rules/no-literal-string-in-object.d.ts +0 -9
- package/lib_esnext/rules/no-literal-string-in-object.js +0 -90
- package/lib_esnext/rules/no-missing-icu-plural-one-placeholders.d.ts +0 -6
- package/lib_esnext/rules/no-missing-icu-plural-one-placeholders.js +0 -99
- package/lib_esnext/rules/no-multiple-plurals.d.ts +0 -5
- package/lib_esnext/rules/no-multiple-plurals.js +0 -70
- package/lib_esnext/rules/no-multiple-whitespaces.d.ts +0 -5
- package/lib_esnext/rules/no-multiple-whitespaces.js +0 -141
- package/lib_esnext/rules/no-offset.d.ts +0 -5
- package/lib_esnext/rules/no-offset.js +0 -69
- package/lib_esnext/rules/no-useless-message.d.ts +0 -5
- package/lib_esnext/rules/no-useless-message.js +0 -71
- package/lib_esnext/rules/prefer-formatted-message.d.ts +0 -5
- package/lib_esnext/rules/prefer-formatted-message.js +0 -33
- package/lib_esnext/rules/prefer-pound-in-plural.d.ts +0 -5
- package/lib_esnext/rules/prefer-pound-in-plural.js +0 -191
- package/lib_esnext/util.d.ts +0 -32
- package/lib_esnext/util.js +0 -280
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
export declare const name = "blocklist-elements";
|
|
3
|
-
export declare enum Element {
|
|
4
|
-
literal = "literal",
|
|
5
|
-
argument = "argument",
|
|
6
|
-
number = "number",
|
|
7
|
-
date = "date",
|
|
8
|
-
time = "time",
|
|
9
|
-
select = "select",
|
|
10
|
-
selectordinal = "selectordinal",
|
|
11
|
-
plural = "plural",
|
|
12
|
-
tag = "tag"
|
|
13
|
-
}
|
|
14
|
-
export declare const rule: ESLintUtils.RuleModule<'blocklist', [
|
|
15
|
-
], unknown, ESLintUtils.RuleListener>;
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { isArgumentElement, isDateElement, isLiteralElement, isNumberElement, isPluralElement, isSelectElement, isTagElement, isTimeElement, parse, } from '@formatjs/icu-messageformat-parser';
|
|
2
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
3
|
-
import { getParserServices } from '../context-compat';
|
|
4
|
-
import { extractMessages, getSettings } from '../util';
|
|
5
|
-
export const name = 'blocklist-elements';
|
|
6
|
-
function getMessage(type) {
|
|
7
|
-
return {
|
|
8
|
-
messageId: 'blocklist',
|
|
9
|
-
data: { type },
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
export var Element;
|
|
13
|
-
(function (Element) {
|
|
14
|
-
Element["literal"] = "literal";
|
|
15
|
-
Element["argument"] = "argument";
|
|
16
|
-
Element["number"] = "number";
|
|
17
|
-
Element["date"] = "date";
|
|
18
|
-
Element["time"] = "time";
|
|
19
|
-
Element["select"] = "select";
|
|
20
|
-
Element["selectordinal"] = "selectordinal";
|
|
21
|
-
Element["plural"] = "plural";
|
|
22
|
-
Element["tag"] = "tag";
|
|
23
|
-
})(Element || (Element = {}));
|
|
24
|
-
function verifyAst(blocklist, ast) {
|
|
25
|
-
const errors = [];
|
|
26
|
-
for (const el of ast) {
|
|
27
|
-
if (isLiteralElement(el) && blocklist.includes(Element.literal)) {
|
|
28
|
-
errors.push(getMessage(Element.literal));
|
|
29
|
-
}
|
|
30
|
-
if (isArgumentElement(el) && blocklist.includes(Element.argument)) {
|
|
31
|
-
errors.push(getMessage(Element.argument));
|
|
32
|
-
}
|
|
33
|
-
if (isNumberElement(el) && blocklist.includes(Element.number)) {
|
|
34
|
-
errors.push(getMessage(Element.number));
|
|
35
|
-
}
|
|
36
|
-
if (isDateElement(el) && blocklist.includes(Element.date)) {
|
|
37
|
-
errors.push(getMessage(Element.date));
|
|
38
|
-
}
|
|
39
|
-
if (isTimeElement(el) && blocklist.includes(Element.time)) {
|
|
40
|
-
errors.push(getMessage(Element.time));
|
|
41
|
-
}
|
|
42
|
-
if (isSelectElement(el) && blocklist.includes(Element.select)) {
|
|
43
|
-
errors.push(getMessage(Element.select));
|
|
44
|
-
}
|
|
45
|
-
if (isTagElement(el) && blocklist.includes(Element.tag)) {
|
|
46
|
-
errors.push(getMessage(Element.tag));
|
|
47
|
-
}
|
|
48
|
-
if (isPluralElement(el)) {
|
|
49
|
-
if (blocklist.includes(Element.plural)) {
|
|
50
|
-
errors.push(getMessage(Element.argument));
|
|
51
|
-
}
|
|
52
|
-
if (el.pluralType === 'ordinal' &&
|
|
53
|
-
blocklist.includes(Element.selectordinal)) {
|
|
54
|
-
errors.push(getMessage(Element.selectordinal));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (isSelectElement(el) || isPluralElement(el)) {
|
|
58
|
-
const { options } = el;
|
|
59
|
-
for (const selector of Object.keys(options)) {
|
|
60
|
-
verifyAst(blocklist, options[selector].value);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return errors;
|
|
65
|
-
}
|
|
66
|
-
function checkNode(context, node) {
|
|
67
|
-
const settings = getSettings(context);
|
|
68
|
-
const msgs = extractMessages(node, settings);
|
|
69
|
-
if (!msgs.length) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const blocklist = context.options[0];
|
|
73
|
-
if (!Array.isArray(blocklist) || !blocklist.length) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
77
|
-
if (!defaultMessage || !messageNode) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const errors = verifyAst(blocklist, parse(defaultMessage, {
|
|
81
|
-
ignoreTag: settings.ignoreTag,
|
|
82
|
-
}));
|
|
83
|
-
for (const error of errors) {
|
|
84
|
-
context.report({
|
|
85
|
-
node,
|
|
86
|
-
...error,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const createRule = ESLintUtils.RuleCreator(_ => 'https://formatjs.github.io/docs/tooling/linter#blocklist-elements');
|
|
92
|
-
export const rule = createRule({
|
|
93
|
-
name,
|
|
94
|
-
meta: {
|
|
95
|
-
type: 'problem',
|
|
96
|
-
docs: {
|
|
97
|
-
description: 'Disallow specific elements in ICU message format',
|
|
98
|
-
url: 'https://formatjs.github.io/docs/tooling/linter#blocklist-elements',
|
|
99
|
-
},
|
|
100
|
-
fixable: 'code',
|
|
101
|
-
schema: [
|
|
102
|
-
{
|
|
103
|
-
type: 'array',
|
|
104
|
-
items: {
|
|
105
|
-
type: 'string',
|
|
106
|
-
enum: Object.keys(Element),
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
messages: {
|
|
111
|
-
blocklist: `{{type}} element is blocklisted`,
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
defaultOptions: [],
|
|
115
|
-
create(context) {
|
|
116
|
-
const callExpressionVisitor = node => checkNode(context, node);
|
|
117
|
-
const parserServices = getParserServices(context);
|
|
118
|
-
//@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
|
|
119
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
120
|
-
//@ts-expect-error
|
|
121
|
-
return parserServices.defineTemplateBodyVisitor({
|
|
122
|
-
CallExpression: callExpressionVisitor,
|
|
123
|
-
}, {
|
|
124
|
-
CallExpression: callExpressionVisitor,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
JSXOpeningElement: node => checkNode(context, node),
|
|
129
|
-
CallExpression: callExpressionVisitor,
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
-
export declare enum Option {
|
|
3
|
-
literal = "literal",
|
|
4
|
-
anything = "anything"
|
|
5
|
-
}
|
|
6
|
-
type MessageIds = 'defaultMessage' | 'defaultMessageLiteral';
|
|
7
|
-
type Options = [`${Option}`?];
|
|
8
|
-
export declare const name = "enforce-default-message";
|
|
9
|
-
export declare const rule: RuleModule<MessageIds, Options>;
|
|
10
|
-
export {};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { getParserServices } from '../context-compat';
|
|
2
|
-
import { extractMessages, getSettings } from '../util';
|
|
3
|
-
export var Option;
|
|
4
|
-
(function (Option) {
|
|
5
|
-
Option["literal"] = "literal";
|
|
6
|
-
Option["anything"] = "anything";
|
|
7
|
-
})(Option || (Option = {}));
|
|
8
|
-
export const name = 'enforce-default-message';
|
|
9
|
-
function checkNode(context, node) {
|
|
10
|
-
const msgs = extractMessages(node, getSettings(context));
|
|
11
|
-
const { options: [type], } = context;
|
|
12
|
-
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
13
|
-
if (!defaultMessage) {
|
|
14
|
-
if (type === 'literal' && messageNode) {
|
|
15
|
-
context.report({
|
|
16
|
-
node: messageNode,
|
|
17
|
-
messageId: 'defaultMessageLiteral',
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
else if (!messageNode) {
|
|
21
|
-
context.report({
|
|
22
|
-
node: node,
|
|
23
|
-
messageId: 'defaultMessage',
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export const rule = {
|
|
30
|
-
meta: {
|
|
31
|
-
type: 'problem',
|
|
32
|
-
docs: {
|
|
33
|
-
description: 'Enforce defaultMessage in message descriptor',
|
|
34
|
-
url: 'https://formatjs.github.io/docs/tooling/linter#enforce-default-message',
|
|
35
|
-
},
|
|
36
|
-
fixable: 'code',
|
|
37
|
-
schema: [
|
|
38
|
-
{
|
|
39
|
-
type: 'string',
|
|
40
|
-
enum: Object.keys(Option),
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
messages: {
|
|
44
|
-
defaultMessageLiteral: `"defaultMessage" must be:
|
|
45
|
-
- a string literal or
|
|
46
|
-
- template literal without variable`,
|
|
47
|
-
defaultMessage: '`defaultMessage` has to be specified in message descriptor',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
defaultOptions: [],
|
|
51
|
-
create(context) {
|
|
52
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
53
|
-
const parserServices = getParserServices(context);
|
|
54
|
-
//@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
|
|
55
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
56
|
-
//@ts-expect-error
|
|
57
|
-
return parserServices.defineTemplateBodyVisitor({
|
|
58
|
-
CallExpression: callExpressionVisitor,
|
|
59
|
-
}, {
|
|
60
|
-
CallExpression: callExpressionVisitor,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
65
|
-
CallExpression: callExpressionVisitor,
|
|
66
|
-
};
|
|
67
|
-
},
|
|
68
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
-
export declare enum Option {
|
|
3
|
-
literal = "literal",
|
|
4
|
-
anything = "anything"
|
|
5
|
-
}
|
|
6
|
-
type MessageIds = 'enforceDescription' | 'enforceDescriptionLiteral';
|
|
7
|
-
type Options = [`${Option}`?];
|
|
8
|
-
export declare const name = "enforce-description";
|
|
9
|
-
export declare const rule: RuleModule<MessageIds, Options>;
|
|
10
|
-
export {};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { getParserServices } from '../context-compat';
|
|
2
|
-
import { extractMessages, getSettings } from '../util';
|
|
3
|
-
export var Option;
|
|
4
|
-
(function (Option) {
|
|
5
|
-
Option["literal"] = "literal";
|
|
6
|
-
Option["anything"] = "anything";
|
|
7
|
-
})(Option || (Option = {}));
|
|
8
|
-
function checkNode(context, node) {
|
|
9
|
-
const msgs = extractMessages(node, getSettings(context));
|
|
10
|
-
const { options: [type], } = context;
|
|
11
|
-
for (const [{ message: { description }, descriptionNode, },] of msgs) {
|
|
12
|
-
if (!description) {
|
|
13
|
-
if (type === 'literal' && descriptionNode) {
|
|
14
|
-
context.report({
|
|
15
|
-
node: descriptionNode,
|
|
16
|
-
messageId: 'enforceDescriptionLiteral',
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
else if (!descriptionNode) {
|
|
20
|
-
context.report({
|
|
21
|
-
node: node,
|
|
22
|
-
messageId: 'enforceDescription',
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
export const name = 'enforce-description';
|
|
29
|
-
export const rule = {
|
|
30
|
-
meta: {
|
|
31
|
-
type: 'problem',
|
|
32
|
-
docs: {
|
|
33
|
-
description: 'Enforce description in message descriptor',
|
|
34
|
-
url: 'https://formatjs.github.io/docs/tooling/linter#enforce-description',
|
|
35
|
-
},
|
|
36
|
-
fixable: 'code',
|
|
37
|
-
schema: [
|
|
38
|
-
{
|
|
39
|
-
type: 'string',
|
|
40
|
-
enum: Object.keys(Option),
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
messages: {
|
|
44
|
-
enforceDescription: '`description` has to be specified in message descriptor',
|
|
45
|
-
enforceDescriptionLiteral: '`description` has to be a string literal (not function call or variable)',
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
defaultOptions: [],
|
|
49
|
-
create(context) {
|
|
50
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
51
|
-
const parserServices = getParserServices(context);
|
|
52
|
-
//@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
|
|
53
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
54
|
-
//@ts-expect-error
|
|
55
|
-
return parserServices.defineTemplateBodyVisitor({
|
|
56
|
-
CallExpression: callExpressionVisitor,
|
|
57
|
-
}, {
|
|
58
|
-
CallExpression: callExpressionVisitor,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
63
|
-
CallExpression: callExpressionVisitor,
|
|
64
|
-
};
|
|
65
|
-
},
|
|
66
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
-
export type Option = {
|
|
3
|
-
idInterpolationPattern: string;
|
|
4
|
-
idWhitelist?: string[];
|
|
5
|
-
};
|
|
6
|
-
type MessageIds = 'enforceId' | 'enforceIdDefaultMessage' | 'enforceIdDescription' | 'enforceIdMatching' | 'enforceIdMatchingAllowlisted';
|
|
7
|
-
type Options = [Option];
|
|
8
|
-
export declare const name = "enforce-id";
|
|
9
|
-
export declare const rule: RuleModule<MessageIds, Options>;
|
|
10
|
-
export {};
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { interpolateName } from '@formatjs/ts-transformer';
|
|
2
|
-
import { getParserServices } from '../context-compat';
|
|
3
|
-
import { extractMessages, getSettings } from '../util';
|
|
4
|
-
function checkNode(context, node, { idInterpolationPattern, idWhitelistRegexps, }) {
|
|
5
|
-
const msgs = extractMessages(node, getSettings(context));
|
|
6
|
-
for (const [{ message: { defaultMessage, description, id }, idPropNode, descriptionNode, messagePropNode, },] of msgs) {
|
|
7
|
-
if (!idInterpolationPattern && !idPropNode) {
|
|
8
|
-
context.report({
|
|
9
|
-
node,
|
|
10
|
-
messageId: 'enforceId',
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
else if (idInterpolationPattern) {
|
|
14
|
-
if (!defaultMessage) {
|
|
15
|
-
context.report({
|
|
16
|
-
node,
|
|
17
|
-
messageId: 'enforceIdDefaultMessage',
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
else if (!description && descriptionNode) {
|
|
21
|
-
context.report({
|
|
22
|
-
node,
|
|
23
|
-
messageId: 'enforceIdDescription',
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
if (idWhitelistRegexps &&
|
|
28
|
-
id &&
|
|
29
|
-
idWhitelistRegexps.some((r) => r.test(id))) {
|
|
30
|
-
// messageId is allowlisted so skip interpolation id check
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
const correctId = interpolateName({
|
|
34
|
-
resourcePath: context.getFilename(),
|
|
35
|
-
}, idInterpolationPattern, {
|
|
36
|
-
content: description
|
|
37
|
-
? `${defaultMessage}#${description}`
|
|
38
|
-
: defaultMessage,
|
|
39
|
-
});
|
|
40
|
-
if (id !== correctId) {
|
|
41
|
-
let messageId = 'enforceIdMatching';
|
|
42
|
-
let messageData = {
|
|
43
|
-
idInterpolationPattern,
|
|
44
|
-
expected: correctId,
|
|
45
|
-
actual: id,
|
|
46
|
-
};
|
|
47
|
-
if (idWhitelistRegexps) {
|
|
48
|
-
messageId = 'enforceIdMatchingAllowlisted';
|
|
49
|
-
messageData = {
|
|
50
|
-
...messageData,
|
|
51
|
-
idWhitelist: idWhitelistRegexps
|
|
52
|
-
.map(r => `"${r.toString()}"`)
|
|
53
|
-
.join(', '),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
context.report({
|
|
57
|
-
node,
|
|
58
|
-
messageId,
|
|
59
|
-
data: messageData,
|
|
60
|
-
fix(fixer) {
|
|
61
|
-
if (idPropNode) {
|
|
62
|
-
if (idPropNode.type === 'JSXAttribute') {
|
|
63
|
-
return fixer.replaceText(idPropNode, `id="${correctId}"`);
|
|
64
|
-
}
|
|
65
|
-
return fixer.replaceText(idPropNode, `id: '${correctId}'`);
|
|
66
|
-
}
|
|
67
|
-
if (messagePropNode) {
|
|
68
|
-
// Insert after default message node
|
|
69
|
-
if (messagePropNode.type === 'JSXAttribute') {
|
|
70
|
-
return fixer.insertTextAfter(messagePropNode, ` id="${correctId}"`);
|
|
71
|
-
}
|
|
72
|
-
return fixer.insertTextAfter(messagePropNode, `, id: '${correctId}'`);
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
export const name = 'enforce-id';
|
|
83
|
-
export const rule = {
|
|
84
|
-
meta: {
|
|
85
|
-
type: 'problem',
|
|
86
|
-
docs: {
|
|
87
|
-
description: 'Enforce (generated) ID in message descriptor',
|
|
88
|
-
url: 'https://formatjs.github.io/docs/tooling/linter#enforce-id',
|
|
89
|
-
},
|
|
90
|
-
fixable: 'code',
|
|
91
|
-
schema: [
|
|
92
|
-
{
|
|
93
|
-
type: 'object',
|
|
94
|
-
properties: {
|
|
95
|
-
idInterpolationPattern: {
|
|
96
|
-
type: 'string',
|
|
97
|
-
description: 'Pattern to verify ID against. Recommended value: [sha512:contenthash:base64:6]',
|
|
98
|
-
},
|
|
99
|
-
idWhitelist: {
|
|
100
|
-
type: 'array',
|
|
101
|
-
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.",
|
|
102
|
-
items: {
|
|
103
|
-
type: 'string',
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
required: ['idInterpolationPattern'],
|
|
108
|
-
additionalProperties: false,
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
messages: {
|
|
112
|
-
enforceId: `id must be specified`,
|
|
113
|
-
enforceIdDefaultMessage: `defaultMessage must be a string literal to calculate generated IDs`,
|
|
114
|
-
enforceIdDescription: `description must be a string literal to calculate generated IDs`,
|
|
115
|
-
enforceIdMatching: `"id" does not match with hash pattern {{idInterpolationPattern}}.
|
|
116
|
-
Expected: {{expected}}
|
|
117
|
-
Actual: {{actual}}`,
|
|
118
|
-
enforceIdMatchingAllowlisted: `"id" does not match with hash pattern {{idInterpolationPattern}} or allowlisted patterns {{idWhitelist}}.
|
|
119
|
-
Expected: {{expected}}
|
|
120
|
-
Actual: {{actual}}`,
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
defaultOptions: [
|
|
124
|
-
{
|
|
125
|
-
idInterpolationPattern: '[sha512:contenthash:base64:6]',
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
create(context) {
|
|
129
|
-
const tmp = context.options[0];
|
|
130
|
-
let opts = {
|
|
131
|
-
idInterpolationPattern: tmp?.idInterpolationPattern,
|
|
132
|
-
};
|
|
133
|
-
if (Array.isArray(tmp?.idWhitelist)) {
|
|
134
|
-
const { idWhitelist } = tmp;
|
|
135
|
-
opts.idWhitelistRegexps = idWhitelist.map((str) => new RegExp(str, 'i'));
|
|
136
|
-
}
|
|
137
|
-
const callExpressionVisitor = (node) => checkNode(context, node, opts);
|
|
138
|
-
const parserServices = getParserServices(context);
|
|
139
|
-
//@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
|
|
140
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
141
|
-
//@ts-expect-error
|
|
142
|
-
return parserServices.defineTemplateBodyVisitor({
|
|
143
|
-
CallExpression: callExpressionVisitor,
|
|
144
|
-
}, {
|
|
145
|
-
CallExpression: callExpressionVisitor,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
JSXOpeningElement: (node) => checkNode(context, node, opts),
|
|
150
|
-
CallExpression: callExpressionVisitor,
|
|
151
|
-
};
|
|
152
|
-
},
|
|
153
|
-
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
-
type MessageIds = 'parserError' | 'missingValue' | 'unusedValue';
|
|
3
|
-
type Options = [{
|
|
4
|
-
ignoreList: string[];
|
|
5
|
-
}?];
|
|
6
|
-
export declare const name = "enforce-placeholders";
|
|
7
|
-
export declare const rule: RuleModule<MessageIds, Options>;
|
|
8
|
-
export {};
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { TYPE, parse, } from '@formatjs/icu-messageformat-parser';
|
|
2
|
-
import { getParserServices } from '../context-compat';
|
|
3
|
-
import { extractMessages, getSettings } from '../util';
|
|
4
|
-
function collectPlaceholderNames(ast) {
|
|
5
|
-
const placeholderNames = new Set();
|
|
6
|
-
_traverse(ast);
|
|
7
|
-
return placeholderNames;
|
|
8
|
-
function _traverse(ast) {
|
|
9
|
-
for (const element of ast) {
|
|
10
|
-
switch (element.type) {
|
|
11
|
-
case TYPE.literal:
|
|
12
|
-
case TYPE.pound:
|
|
13
|
-
break;
|
|
14
|
-
case TYPE.tag:
|
|
15
|
-
placeholderNames.add(element.value);
|
|
16
|
-
_traverse(element.children);
|
|
17
|
-
break;
|
|
18
|
-
case TYPE.plural:
|
|
19
|
-
case TYPE.select:
|
|
20
|
-
placeholderNames.add(element.value);
|
|
21
|
-
for (const { value } of Object.values(element.options)) {
|
|
22
|
-
_traverse(value);
|
|
23
|
-
}
|
|
24
|
-
break;
|
|
25
|
-
default:
|
|
26
|
-
placeholderNames.add(element.value);
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function checkNode(context, node) {
|
|
33
|
-
const settings = getSettings(context);
|
|
34
|
-
const msgs = extractMessages(node, {
|
|
35
|
-
excludeMessageDeclCalls: true,
|
|
36
|
-
...settings,
|
|
37
|
-
});
|
|
38
|
-
const { options: [opt], } = context;
|
|
39
|
-
const ignoreList = new Set(opt?.ignoreList || []);
|
|
40
|
-
for (const [{ message: { defaultMessage }, messageNode, }, values,] of msgs) {
|
|
41
|
-
if (!defaultMessage || !messageNode) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (values && values.type !== 'ObjectExpression') {
|
|
45
|
-
// cannot evaluate this
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (values?.properties.find(prop => prop.type === 'SpreadElement')) {
|
|
49
|
-
// cannot evaluate the spread element
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
const literalElementByLiteralKey = new Map();
|
|
53
|
-
if (values) {
|
|
54
|
-
for (const prop of values.properties) {
|
|
55
|
-
if (prop.type === 'Property' && !prop.computed) {
|
|
56
|
-
const name = prop.key.type === 'Identifier'
|
|
57
|
-
? prop.key.name
|
|
58
|
-
: String(prop.key.value);
|
|
59
|
-
literalElementByLiteralKey.set(name, prop);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
let ast;
|
|
64
|
-
try {
|
|
65
|
-
ast = parse(defaultMessage, { ignoreTag: settings.ignoreTag });
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
context.report({
|
|
69
|
-
node: messageNode,
|
|
70
|
-
messageId: 'parserError',
|
|
71
|
-
data: { message: e instanceof Error ? e.message : String(e) },
|
|
72
|
-
});
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const placeholderNames = collectPlaceholderNames(ast);
|
|
76
|
-
const missingPlaceholders = [];
|
|
77
|
-
placeholderNames.forEach(name => {
|
|
78
|
-
if (!ignoreList.has(name) && !literalElementByLiteralKey.has(name)) {
|
|
79
|
-
missingPlaceholders.push(name);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
if (missingPlaceholders.length > 0) {
|
|
83
|
-
context.report({
|
|
84
|
-
node: messageNode,
|
|
85
|
-
messageId: 'missingValue',
|
|
86
|
-
data: {
|
|
87
|
-
list: missingPlaceholders.join(', '),
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
literalElementByLiteralKey.forEach((element, key) => {
|
|
92
|
-
if (!ignoreList.has(key) && !placeholderNames.has(key)) {
|
|
93
|
-
context.report({
|
|
94
|
-
node: element,
|
|
95
|
-
messageId: 'unusedValue',
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
export const name = 'enforce-placeholders';
|
|
102
|
-
export const rule = {
|
|
103
|
-
meta: {
|
|
104
|
-
type: 'problem',
|
|
105
|
-
docs: {
|
|
106
|
-
description: 'Enforce that all messages with placeholders have enough passed-in values',
|
|
107
|
-
url: 'https://formatjs.github.io/docs/tooling/linter#enforce-placeholders',
|
|
108
|
-
},
|
|
109
|
-
schema: [
|
|
110
|
-
{
|
|
111
|
-
type: 'object',
|
|
112
|
-
properties: {
|
|
113
|
-
ignoreList: {
|
|
114
|
-
type: 'array',
|
|
115
|
-
items: {
|
|
116
|
-
type: 'string',
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
additionalProperties: false,
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
messages: {
|
|
124
|
-
parserError: '{{message}}',
|
|
125
|
-
missingValue: 'Missing value(s) for the following placeholder(s): {{list}}.',
|
|
126
|
-
unusedValue: 'Value not used by the message.',
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
defaultOptions: [],
|
|
130
|
-
create(context) {
|
|
131
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
132
|
-
const parserServices = getParserServices(context);
|
|
133
|
-
//@ts-expect-error defineTemplateBodyVisitor exists in Vue parser
|
|
134
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
135
|
-
//@ts-expect-error
|
|
136
|
-
return parserServices.defineTemplateBodyVisitor({
|
|
137
|
-
CallExpression: callExpressionVisitor,
|
|
138
|
-
}, {
|
|
139
|
-
CallExpression: callExpressionVisitor,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
144
|
-
CallExpression: callExpressionVisitor,
|
|
145
|
-
};
|
|
146
|
-
},
|
|
147
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
-
declare enum LDML {
|
|
3
|
-
zero = "zero",
|
|
4
|
-
one = "one",
|
|
5
|
-
two = "two",
|
|
6
|
-
few = "few",
|
|
7
|
-
many = "many",
|
|
8
|
-
other = "other"
|
|
9
|
-
}
|
|
10
|
-
type PluralConfig = {
|
|
11
|
-
[key in LDML]?: boolean;
|
|
12
|
-
};
|
|
13
|
-
export type Options = [PluralConfig?];
|
|
14
|
-
type MessageIds = 'missingPlural' | 'forbidden';
|
|
15
|
-
export declare const name = "enforce-plural-rules";
|
|
16
|
-
export declare const rule: RuleModule<MessageIds, Options>;
|
|
17
|
-
export {};
|