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