eslint-plugin-formatjs 4.2.0 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +22 -0
- package/index.d.ts.map +1 -0
- package/index.js +38 -0
- package/package.json +4 -4
- package/rules/blocklist-elements.d.ts +4 -0
- package/rules/blocklist-elements.d.ts.map +1 -0
- package/rules/blocklist-elements.js +127 -0
- package/rules/enforce-default-message.d.ts +4 -0
- package/rules/enforce-default-message.d.ts.map +1 -0
- package/rules/enforce-default-message.js +57 -0
- package/rules/enforce-description.d.ts +4 -0
- package/rules/enforce-description.d.ts.map +1 -0
- package/rules/enforce-description.js +54 -0
- package/rules/enforce-id.d.ts +4 -0
- package/rules/enforce-id.d.ts.map +1 -0
- package/rules/enforce-id.js +125 -0
- package/rules/enforce-placeholders.d.ts +4 -0
- package/rules/enforce-placeholders.d.ts.map +1 -0
- package/rules/enforce-placeholders.js +118 -0
- package/rules/enforce-plural-rules.d.ts +4 -0
- package/rules/enforce-plural-rules.d.ts.map +1 -0
- package/rules/enforce-plural-rules.js +104 -0
- package/rules/no-camel-case.d.ts +4 -0
- package/rules/no-camel-case.d.ts.map +1 -0
- package/rules/no-camel-case.js +77 -0
- package/rules/no-complex-selectors.d.ts +4 -0
- package/rules/no-complex-selectors.d.ts.map +1 -0
- package/rules/no-complex-selectors.js +99 -0
- package/rules/no-emoji.d.ts +4 -0
- package/rules/no-emoji.d.ts.map +1 -0
- package/rules/no-emoji.js +47 -0
- package/rules/no-id.d.ts +4 -0
- package/rules/no-id.d.ts.map +1 -0
- package/rules/no-id.js +52 -0
- package/rules/no-invalid-icu.d.ts +4 -0
- package/rules/no-invalid-icu.d.ts.map +1 -0
- package/rules/no-invalid-icu.js +54 -0
- package/rules/no-literal-string-in-jsx.d.ts +4 -0
- package/rules/no-literal-string-in-jsx.d.ts.map +1 -0
- package/rules/no-literal-string-in-jsx.js +166 -0
- package/rules/no-multiple-plurals.d.ts +4 -0
- package/rules/no-multiple-plurals.d.ts.map +1 -0
- package/rules/no-multiple-plurals.js +71 -0
- package/rules/no-multiple-whitespaces.d.ts +4 -0
- package/rules/no-multiple-whitespaces.d.ts.map +1 -0
- package/rules/no-multiple-whitespaces.js +146 -0
- package/rules/no-offset.d.ts +4 -0
- package/rules/no-offset.d.ts.map +1 -0
- package/rules/no-offset.js +70 -0
- package/util.d.ts +24 -0
- package/util.d.ts.map +1 -0
- package/util.js +240 -0
- package/BUILD +0 -89
- package/CHANGELOG.md +0 -892
- package/index.ts +0 -38
- package/rules/blocklist-elements.ts +0 -159
- package/rules/enforce-default-message.ts +0 -71
- package/rules/enforce-description.ts +0 -68
- package/rules/enforce-id.ts +0 -171
- package/rules/enforce-placeholders.ts +0 -161
- package/rules/enforce-plural-rules.ts +0 -134
- package/rules/no-camel-case.ts +0 -97
- package/rules/no-complex-selectors.ts +0 -125
- package/rules/no-emoji.ts +0 -60
- package/rules/no-id.ts +0 -63
- package/rules/no-invalid-icu.ts +0 -69
- package/rules/no-literal-string-in-jsx.ts +0 -213
- package/rules/no-multiple-plurals.ts +0 -89
- package/rules/no-multiple-whitespaces.ts +0 -194
- package/rules/no-offset.ts +0 -88
- package/tests/blocklist-elements.test.ts +0 -120
- package/tests/enforce-default-message.test.ts +0 -260
- package/tests/enforce-description.test.ts +0 -117
- package/tests/enforce-id.test.ts +0 -209
- package/tests/enforce-placeholders.test.ts +0 -170
- package/tests/enforce-plural-rules.test.ts +0 -86
- package/tests/fixtures.ts +0 -15
- package/tests/no-camel-case.test.ts +0 -31
- package/tests/no-complex-selectors.test.ts +0 -125
- package/tests/no-id.test.ts +0 -151
- package/tests/no-invalid-icu.test.ts +0 -106
- package/tests/no-literal-string-in-jsx.test.ts +0 -213
- package/tests/no-multiple-plurals.test.ts +0 -42
- package/tests/no-multiple-whitespaces.test.ts +0 -100
- package/tests/no-offset.test.ts +0 -41
- package/tests/util.ts +0 -26
- package/tsconfig.json +0 -5
- package/util.ts +0 -307
|
@@ -0,0 +1,104 @@
|
|
|
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 PluralRulesEnforcement extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super();
|
|
8
|
+
this.message = message;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
var LDML;
|
|
12
|
+
(function (LDML) {
|
|
13
|
+
LDML["zero"] = "zero";
|
|
14
|
+
LDML["one"] = "one";
|
|
15
|
+
LDML["two"] = "two";
|
|
16
|
+
LDML["few"] = "few";
|
|
17
|
+
LDML["many"] = "many";
|
|
18
|
+
LDML["other"] = "other";
|
|
19
|
+
})(LDML || (LDML = {}));
|
|
20
|
+
function verifyAst(plConfig, ast) {
|
|
21
|
+
for (const el of ast) {
|
|
22
|
+
if ((0, icu_messageformat_parser_1.isPluralElement)(el)) {
|
|
23
|
+
const rules = Object.keys(plConfig);
|
|
24
|
+
for (const rule of rules) {
|
|
25
|
+
if (plConfig[rule] && !el.options[rule]) {
|
|
26
|
+
throw new PluralRulesEnforcement(`Missing plural rule "${rule}"`);
|
|
27
|
+
}
|
|
28
|
+
if (!plConfig[rule] && el.options[rule]) {
|
|
29
|
+
throw new PluralRulesEnforcement(`Plural rule "${rule}" is forbidden`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const { options } = el;
|
|
33
|
+
for (const selector of Object.keys(options)) {
|
|
34
|
+
verifyAst(plConfig, options[selector].value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function checkNode(context, node) {
|
|
40
|
+
const settings = (0, util_1.getSettings)(context);
|
|
41
|
+
const msgs = (0, util_1.extractMessages)(node, settings);
|
|
42
|
+
if (!msgs.length) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const plConfig = context.options[0];
|
|
46
|
+
if (!plConfig) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
50
|
+
if (!defaultMessage || !messageNode) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
verifyAst(context.options[0], (0, icu_messageformat_parser_1.parse)(defaultMessage, {
|
|
55
|
+
ignoreTag: settings.ignoreTag,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: messageNode,
|
|
61
|
+
message: e instanceof Error ? e.message : String(e),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const rule = {
|
|
67
|
+
meta: {
|
|
68
|
+
type: 'problem',
|
|
69
|
+
docs: {
|
|
70
|
+
description: 'Enforce plural rules to always specify certain categories like `one`/`other`',
|
|
71
|
+
category: 'Errors',
|
|
72
|
+
recommended: false,
|
|
73
|
+
url: 'https://formatjs.io/docs/tooling/linter#enforce-plural-rules',
|
|
74
|
+
},
|
|
75
|
+
fixable: 'code',
|
|
76
|
+
schema: [
|
|
77
|
+
{
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: Object.keys(LDML).reduce((schema, k) => {
|
|
80
|
+
schema[k] = {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
};
|
|
83
|
+
return schema;
|
|
84
|
+
}, {}),
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
create(context) {
|
|
90
|
+
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
91
|
+
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
92
|
+
return context.parserServices.defineTemplateBodyVisitor({
|
|
93
|
+
CallExpression: callExpressionVisitor,
|
|
94
|
+
}, {
|
|
95
|
+
CallExpression: callExpressionVisitor,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
JSXOpeningElement: (node) => checkNode(context, node),
|
|
100
|
+
CallExpression: callExpressionVisitor,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
exports.default = rule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-camel-case.d.ts","sourceRoot":"","sources":["no-camel-case.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAgE3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA8BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
const CAMEL_CASE_REGEX = /[A-Z]/;
|
|
6
|
+
class CamelCase extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.message = 'Camel case arguments are not allowed';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function verifyAst(ast) {
|
|
13
|
+
for (const el of ast) {
|
|
14
|
+
if ((0, icu_messageformat_parser_1.isArgumentElement)(el)) {
|
|
15
|
+
if (CAMEL_CASE_REGEX.test(el.value)) {
|
|
16
|
+
throw new CamelCase();
|
|
17
|
+
}
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if ((0, icu_messageformat_parser_1.isPluralElement)(el)) {
|
|
21
|
+
if (CAMEL_CASE_REGEX.test(el.value)) {
|
|
22
|
+
throw new CamelCase();
|
|
23
|
+
}
|
|
24
|
+
const { options } = el;
|
|
25
|
+
for (const selector of Object.keys(options)) {
|
|
26
|
+
verifyAst(options[selector].value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function checkNode(context, node) {
|
|
32
|
+
const settings = (0, util_1.getSettings)(context);
|
|
33
|
+
const msgs = (0, util_1.extractMessages)(node, settings);
|
|
34
|
+
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
35
|
+
if (!defaultMessage || !messageNode) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
verifyAst((0, icu_messageformat_parser_1.parse)(defaultMessage, {
|
|
40
|
+
ignoreTag: settings.ignoreTag,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
context.report({
|
|
45
|
+
node: messageNode,
|
|
46
|
+
message: e instanceof Error ? e.message : String(e),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const rule = {
|
|
52
|
+
meta: {
|
|
53
|
+
type: 'problem',
|
|
54
|
+
docs: {
|
|
55
|
+
description: 'Disallow camel case placeholders in message',
|
|
56
|
+
category: 'Errors',
|
|
57
|
+
recommended: false,
|
|
58
|
+
url: 'https://formatjs.io/docs/tooling/linter#no-camel-case',
|
|
59
|
+
},
|
|
60
|
+
fixable: 'code',
|
|
61
|
+
},
|
|
62
|
+
create(context) {
|
|
63
|
+
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
64
|
+
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
65
|
+
return context.parserServices.defineTemplateBodyVisitor({
|
|
66
|
+
CallExpression: callExpressionVisitor,
|
|
67
|
+
}, {
|
|
68
|
+
CallExpression: callExpressionVisitor,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
JSXOpeningElement: (node) => checkNode(context, node),
|
|
73
|
+
CallExpression: callExpressionVisitor,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
exports.default = rule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-complex-selectors.d.ts","sourceRoot":"","sources":["no-complex-selectors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAyE3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAiDhB,CAAA;AAED,eAAe,IAAI,CAAA"}
|
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
const manipulator_1 = require("@formatjs/icu-messageformat-parser/manipulator");
|
|
6
|
+
function calculateComplexity(ast) {
|
|
7
|
+
if (ast.length === 1) {
|
|
8
|
+
const el = ast[0];
|
|
9
|
+
if ((0, icu_messageformat_parser_1.isPluralElement)(el) || (0, icu_messageformat_parser_1.isSelectElement)(el)) {
|
|
10
|
+
return Object.keys(el.options).reduce((complexity, k) => {
|
|
11
|
+
return complexity + calculateComplexity(el.options[k].value);
|
|
12
|
+
}, 0);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
function checkNode(context, node) {
|
|
18
|
+
const settings = (0, util_1.getSettings)(context);
|
|
19
|
+
const msgs = (0, util_1.extractMessages)(node, settings);
|
|
20
|
+
if (!msgs.length) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const config = {
|
|
24
|
+
limit: 20,
|
|
25
|
+
...(context.options[0] || {}),
|
|
26
|
+
};
|
|
27
|
+
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
28
|
+
if (!defaultMessage || !messageNode) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
let ast;
|
|
32
|
+
try {
|
|
33
|
+
ast = (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
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const hoistedAst = (0, manipulator_1.hoistSelectors)(ast);
|
|
45
|
+
const complexity = calculateComplexity(hoistedAst);
|
|
46
|
+
if (complexity > config.limit) {
|
|
47
|
+
context.report({
|
|
48
|
+
node: messageNode,
|
|
49
|
+
message: `Message complexity is too high (${complexity} vs limit at ${config.limit})`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const rule = {
|
|
55
|
+
meta: {
|
|
56
|
+
type: 'problem',
|
|
57
|
+
docs: {
|
|
58
|
+
description: `Make sure a sentence is not too complex.
|
|
59
|
+
Complexity is determined by how many strings are produced when we try to
|
|
60
|
+
flatten the sentence given its selectors. For example:
|
|
61
|
+
"I have {count, plural, one{a dog} other{many dogs}}"
|
|
62
|
+
has the complexity of 2 because flattening the plural selector
|
|
63
|
+
results in 2 sentences: "I have a dog" & "I have many dogs".
|
|
64
|
+
Default complexity limit is 20
|
|
65
|
+
(using Smartling as a reference: https://help.smartling.com/hc/en-us/articles/360008030994-ICU-MessageFormat)
|
|
66
|
+
`,
|
|
67
|
+
category: 'Errors',
|
|
68
|
+
recommended: false,
|
|
69
|
+
url: 'https://formatjs.io/docs/tooling/linter#no-complex-selectors',
|
|
70
|
+
},
|
|
71
|
+
schema: [
|
|
72
|
+
{
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
limit: {
|
|
76
|
+
type: 'number',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
fixable: 'code',
|
|
83
|
+
},
|
|
84
|
+
create(context) {
|
|
85
|
+
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
86
|
+
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
87
|
+
return context.parserServices.defineTemplateBodyVisitor({
|
|
88
|
+
CallExpression: callExpressionVisitor,
|
|
89
|
+
}, {
|
|
90
|
+
CallExpression: callExpressionVisitor,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
JSXOpeningElement: (node) => checkNode(context, node),
|
|
95
|
+
CallExpression: callExpressionVisitor,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
exports.default = rule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-emoji.d.ts","sourceRoot":"","sources":["no-emoji.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AA2B3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA8BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const util_1 = require("../util");
|
|
5
|
+
const emoji_regex_1 = tslib_1.__importDefault(require("emoji-regex"));
|
|
6
|
+
const EMOJI_REGEX = emoji_regex_1.default();
|
|
7
|
+
function checkNode(context, node) {
|
|
8
|
+
const msgs = (0, util_1.extractMessages)(node, (0, util_1.getSettings)(context));
|
|
9
|
+
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
10
|
+
if (!defaultMessage || !messageNode) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (EMOJI_REGEX.test(defaultMessage)) {
|
|
14
|
+
context.report({
|
|
15
|
+
node: messageNode,
|
|
16
|
+
message: 'Emojis are not allowed',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const rule = {
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'problem',
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Disallow emojis in message',
|
|
26
|
+
category: 'Errors',
|
|
27
|
+
recommended: false,
|
|
28
|
+
url: 'https://formatjs.io/docs/tooling/linter#no-emoji',
|
|
29
|
+
},
|
|
30
|
+
fixable: 'code',
|
|
31
|
+
},
|
|
32
|
+
create(context) {
|
|
33
|
+
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
34
|
+
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
35
|
+
return context.parserServices.defineTemplateBodyVisitor({
|
|
36
|
+
CallExpression: callExpressionVisitor,
|
|
37
|
+
}, {
|
|
38
|
+
CallExpression: callExpressionVisitor,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
JSXOpeningElement: (node) => checkNode(context, node),
|
|
43
|
+
CallExpression: callExpressionVisitor,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
exports.default = rule;
|
package/rules/no-id.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-id.d.ts","sourceRoot":"","sources":["no-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAa,MAAM,QAAQ,CAAA;;AAgCvC,wBA8BoB"}
|
package/rules/no-id.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const util_1 = require("../util");
|
|
4
|
+
function isComment(token) {
|
|
5
|
+
return !!token && (token.type === 'Block' || token.type === 'Line');
|
|
6
|
+
}
|
|
7
|
+
function checkNode(context, node) {
|
|
8
|
+
const msgs = (0, util_1.extractMessages)(node, (0, util_1.getSettings)(context));
|
|
9
|
+
for (const [{ idPropNode }] of msgs) {
|
|
10
|
+
if (idPropNode) {
|
|
11
|
+
context.report({
|
|
12
|
+
node: idPropNode,
|
|
13
|
+
message: 'Manual `id` are not allowed in message descriptor',
|
|
14
|
+
fix(fixer) {
|
|
15
|
+
const src = context.getSourceCode();
|
|
16
|
+
const token = src.getTokenAfter(idPropNode);
|
|
17
|
+
const fixes = [fixer.remove(idPropNode)];
|
|
18
|
+
if (token && !isComment(token) && token?.value === ',') {
|
|
19
|
+
fixes.push(fixer.remove(token));
|
|
20
|
+
}
|
|
21
|
+
return fixes;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.default = {
|
|
28
|
+
meta: {
|
|
29
|
+
type: 'problem',
|
|
30
|
+
docs: {
|
|
31
|
+
description: 'Ban explicit ID from MessageDescriptor',
|
|
32
|
+
category: 'Errors',
|
|
33
|
+
recommended: false,
|
|
34
|
+
url: 'https://formatjs.io/docs/tooling/linter#no-id',
|
|
35
|
+
},
|
|
36
|
+
fixable: 'code',
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
40
|
+
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
41
|
+
return context.parserServices.defineTemplateBodyVisitor({
|
|
42
|
+
CallExpression: callExpressionVisitor,
|
|
43
|
+
}, {
|
|
44
|
+
CallExpression: callExpressionVisitor,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
JSXOpeningElement: (node) => checkNode(context, node),
|
|
49
|
+
CallExpression: callExpressionVisitor,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-invalid-icu.d.ts","sourceRoot":"","sources":["no-invalid-icu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAqC3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA6BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
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 checkNode(context, node) {
|
|
6
|
+
const settings = (0, util_1.getSettings)(context);
|
|
7
|
+
const msgs = (0, util_1.extractMessages)(node, settings);
|
|
8
|
+
if (!msgs.length) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
for (const [{ message: { defaultMessage }, messageNode, },] of msgs) {
|
|
12
|
+
if (!defaultMessage || !messageNode) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
(0, icu_messageformat_parser_1.parse)(defaultMessage, {
|
|
17
|
+
ignoreTag: settings.ignoreTag,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
const msg = e instanceof Error ? e.message : e;
|
|
22
|
+
context.report({
|
|
23
|
+
node: messageNode,
|
|
24
|
+
message: `Error parsing ICU string: ${msg}`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const rule = {
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'problem',
|
|
32
|
+
docs: {
|
|
33
|
+
description: `Make sure ICU messages are formatted correctly with no bad select statements, plurals, etc.`,
|
|
34
|
+
category: 'Errors',
|
|
35
|
+
recommended: true,
|
|
36
|
+
},
|
|
37
|
+
fixable: 'code',
|
|
38
|
+
},
|
|
39
|
+
create(context) {
|
|
40
|
+
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
41
|
+
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
42
|
+
return context.parserServices.defineTemplateBodyVisitor({
|
|
43
|
+
CallExpression: callExpressionVisitor,
|
|
44
|
+
}, {
|
|
45
|
+
CallExpression: callExpressionVisitor,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
JSXOpeningElement: (node) => checkNode(context, node),
|
|
50
|
+
CallExpression: callExpressionVisitor,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
exports.default = rule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-literal-string-in-jsx.d.ts","sourceRoot":"","sources":["no-literal-string-in-jsx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAqDhC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA4JhB,CAAA;AAED,eAAe,IAAI,CAAA"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const picomatch_1 = tslib_1.__importDefault(require("picomatch"));
|
|
5
|
+
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
|
6
|
+
const propMatcherSchema = {
|
|
7
|
+
type: 'array',
|
|
8
|
+
items: {
|
|
9
|
+
type: 'array',
|
|
10
|
+
items: [{ type: 'string' }, { type: 'string' }],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
const defaultPropIncludePattern = [
|
|
14
|
+
['*', 'aria-{label,description,details,errormessage}'],
|
|
15
|
+
['[a-z]*([a-z0-9])', '(placeholder|title)'],
|
|
16
|
+
['img', 'alt'],
|
|
17
|
+
];
|
|
18
|
+
const defaultPropExcludePattern = [];
|
|
19
|
+
function stringifyJsxTagName(tagName) {
|
|
20
|
+
switch (tagName.type) {
|
|
21
|
+
case typescript_estree_1.TSESTree.AST_NODE_TYPES.JSXIdentifier:
|
|
22
|
+
return tagName.name;
|
|
23
|
+
case typescript_estree_1.TSESTree.AST_NODE_TYPES.JSXMemberExpression:
|
|
24
|
+
return `${stringifyJsxTagName(tagName.object)}.${tagName.property.name}`;
|
|
25
|
+
case typescript_estree_1.TSESTree.AST_NODE_TYPES.JSXNamespacedName:
|
|
26
|
+
return `${tagName.namespace.name}:${tagName.name.name}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function compilePropMatcher(propMatcher) {
|
|
30
|
+
return propMatcher.map(([tagNamePattern, propNamePattern]) => {
|
|
31
|
+
return [
|
|
32
|
+
picomatch_1.default.makeRe(tagNamePattern, { contains: false }),
|
|
33
|
+
picomatch_1.default.makeRe(propNamePattern, { contains: false }),
|
|
34
|
+
];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const rule = {
|
|
38
|
+
meta: {
|
|
39
|
+
type: 'problem',
|
|
40
|
+
docs: {
|
|
41
|
+
description: 'Disallow untranslated literal strings without translation.',
|
|
42
|
+
category: 'Errors',
|
|
43
|
+
recommended: false,
|
|
44
|
+
url: 'https://formatjs.io/docs/tooling/linter#no-literal-string-in-jsx',
|
|
45
|
+
},
|
|
46
|
+
schema: [
|
|
47
|
+
{
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
props: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
include: {
|
|
54
|
+
...propMatcherSchema,
|
|
55
|
+
},
|
|
56
|
+
exclude: {
|
|
57
|
+
...propMatcherSchema,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
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
|
+
if ((node.type === 'Literal' && typeof node.value === 'string') ||
|
|
105
|
+
node.type === 'TemplateLiteral') {
|
|
106
|
+
context.report({
|
|
107
|
+
node: node,
|
|
108
|
+
message: 'Cannot have untranslated text in JSX',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else if (node.type === 'BinaryExpression' && node.operator === '+') {
|
|
112
|
+
checkJSXExpression(node.left);
|
|
113
|
+
checkJSXExpression(node.right);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
JSXElement: (node) => {
|
|
118
|
+
lexicalJsxStack.push(node);
|
|
119
|
+
},
|
|
120
|
+
'JSXElement:exit': () => {
|
|
121
|
+
lexicalJsxStack.pop();
|
|
122
|
+
},
|
|
123
|
+
JSXFragment: (node) => {
|
|
124
|
+
lexicalJsxStack.push(node);
|
|
125
|
+
},
|
|
126
|
+
'JSXFragment:exit': () => {
|
|
127
|
+
lexicalJsxStack.pop();
|
|
128
|
+
},
|
|
129
|
+
JSXAttribute: (node) => {
|
|
130
|
+
if (shouldSkipCurrentJsxAttribute(node)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!node.value) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (node.value.type === 'Literal') {
|
|
137
|
+
context.report({
|
|
138
|
+
node: node,
|
|
139
|
+
message: 'Cannot have untranslated text in JSX',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else if (node.value.type === 'JSXExpressionContainer' &&
|
|
143
|
+
node.value.expression.type !== 'JSXEmptyExpression') {
|
|
144
|
+
checkJSXExpression(node.value.expression);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
JSXText: (node) => {
|
|
148
|
+
// Ignore purely spacing fragments
|
|
149
|
+
if (!node.value.replace(/\s*/gm, '')) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
context.report({
|
|
153
|
+
node: node,
|
|
154
|
+
message: 'Cannot have untranslated text in JSX',
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
// Children expression container
|
|
158
|
+
'JSXElement > JSXExpressionContainer': (node) => {
|
|
159
|
+
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
160
|
+
checkJSXExpression(node.expression);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
exports.default = rule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-multiple-plurals.d.ts","sourceRoot":"","sources":["no-multiple-plurals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAwD3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA8BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
|