eslint-plugin-formatjs 5.2.5 → 5.2.6
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/lib_esnext/context-compat.d.ts +2 -0
- package/lib_esnext/context-compat.js +6 -0
- package/lib_esnext/index.d.ts +1 -0
- package/lib_esnext/index.js +143 -0
- package/lib_esnext/package.json +37 -0
- package/lib_esnext/rules/blocklist-elements.d.ts +14 -0
- package/lib_esnext/rules/blocklist-elements.js +132 -0
- package/lib_esnext/rules/enforce-default-message.d.ts +10 -0
- package/lib_esnext/rules/enforce-default-message.js +68 -0
- package/lib_esnext/rules/enforce-description.d.ts +10 -0
- package/lib_esnext/rules/enforce-description.js +66 -0
- package/lib_esnext/rules/enforce-id.d.ts +10 -0
- package/lib_esnext/rules/enforce-id.js +153 -0
- package/lib_esnext/rules/enforce-placeholders.d.ts +8 -0
- package/lib_esnext/rules/enforce-placeholders.js +147 -0
- package/lib_esnext/rules/enforce-plural-rules.d.ts +17 -0
- package/lib_esnext/rules/enforce-plural-rules.js +103 -0
- package/lib_esnext/rules/no-camel-case.d.ts +5 -0
- package/lib_esnext/rules/no-camel-case.js +76 -0
- package/lib_esnext/rules/no-complex-selectors.d.ts +9 -0
- package/lib_esnext/rules/no-complex-selectors.js +136 -0
- package/lib_esnext/rules/no-emoji.d.ts +9 -0
- package/lib_esnext/rules/no-emoji.js +99 -0
- package/lib_esnext/rules/no-id.d.ts +5 -0
- package/lib_esnext/rules/no-id.js +58 -0
- package/lib_esnext/rules/no-invalid-icu.d.ts +5 -0
- package/lib_esnext/rules/no-invalid-icu.js +60 -0
- package/lib_esnext/rules/no-literal-string-in-jsx.d.ts +13 -0
- package/lib_esnext/rules/no-literal-string-in-jsx.js +179 -0
- package/lib_esnext/rules/no-missing-icu-plural-one-placeholders.d.ts +6 -0
- package/lib_esnext/rules/no-missing-icu-plural-one-placeholders.js +99 -0
- package/lib_esnext/rules/no-multiple-plurals.d.ts +5 -0
- package/lib_esnext/rules/no-multiple-plurals.js +70 -0
- package/lib_esnext/rules/no-multiple-whitespaces.d.ts +5 -0
- package/lib_esnext/rules/no-multiple-whitespaces.js +141 -0
- package/lib_esnext/rules/no-offset.d.ts +5 -0
- package/lib_esnext/rules/no-offset.js +69 -0
- package/lib_esnext/rules/no-useless-message.d.ts +5 -0
- package/lib_esnext/rules/no-useless-message.js +71 -0
- package/lib_esnext/rules/prefer-formatted-message.d.ts +5 -0
- package/lib_esnext/rules/prefer-formatted-message.js +33 -0
- package/lib_esnext/rules/prefer-pound-in-plural.d.ts +5 -0
- package/lib_esnext/rules/prefer-pound-in-plural.js +191 -0
- package/lib_esnext/util.d.ts +32 -0
- package/lib_esnext/util.js +261 -0
- package/package.json +4 -4
- package/context-compat.js.map +0 -1
- package/index.js.map +0 -1
- package/rules/blocklist-elements.js.map +0 -1
- package/rules/enforce-default-message.js.map +0 -1
- package/rules/enforce-description.js.map +0 -1
- package/rules/enforce-id.js.map +0 -1
- package/rules/enforce-placeholders.js.map +0 -1
- package/rules/enforce-plural-rules.js.map +0 -1
- package/rules/no-camel-case.js.map +0 -1
- package/rules/no-complex-selectors.js.map +0 -1
- package/rules/no-emoji.js.map +0 -1
- package/rules/no-id.js.map +0 -1
- package/rules/no-invalid-icu.js.map +0 -1
- package/rules/no-literal-string-in-jsx.js.map +0 -1
- package/rules/no-missing-icu-plural-one-placeholders.js.map +0 -1
- package/rules/no-multiple-plurals.js.map +0 -1
- package/rules/no-multiple-whitespaces.js.map +0 -1
- package/rules/no-offset.js.map +0 -1
- package/rules/no-useless-message.js.map +0 -1
- package/rules/prefer-formatted-message.js.map +0 -1
- package/rules/prefer-pound-in-plural.js.map +0 -1
- package/util.js.map +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
+
export declare const getParserServices: <TRuleContext extends RuleContext<string, unknown[]>>(context: TRuleContext) => Partial<import("@typescript-eslint/utils").ParserServicesWithTypeInformation> | Partial<import("@typescript-eslint/utils").ParserServicesWithoutTypeInformation> | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { name as blocklistElementRuleName, rule as blocklistElements, } from './rules/blocklist-elements';
|
|
2
|
+
import { rule as enforceDefaultMessage, name as enforceDefaultMessageName, } from './rules/enforce-default-message';
|
|
3
|
+
import { rule as enforceDescription, name as enforceDescriptionName, } from './rules/enforce-description';
|
|
4
|
+
import { rule as enforceId, name as enforceIdName } from './rules/enforce-id';
|
|
5
|
+
import { rule as enforcePlaceholders, name as enforcePlaceholdersName, } from './rules/enforce-placeholders';
|
|
6
|
+
import { rule as enforcePluralRules, name as enforcePluralRulesName, } from './rules/enforce-plural-rules';
|
|
7
|
+
import { rule as noCamelCase, name as noCamelCaseName, } from './rules/no-camel-case';
|
|
8
|
+
import { rule as noComplexSelectors, name as noComplexSelectorsName, } from './rules/no-complex-selectors';
|
|
9
|
+
import { rule as noEmoji, name as noEmojiName } from './rules/no-emoji';
|
|
10
|
+
import { rule as noId, name as noIdName } from './rules/no-id';
|
|
11
|
+
import { rule as noInvalidICU, name as noInvalidICUName, } from './rules/no-invalid-icu';
|
|
12
|
+
import { rule as noLiteralStringInJsx, name as noLiteralStringInJsxName, } from './rules/no-literal-string-in-jsx';
|
|
13
|
+
import { rule as noMissingIcuPluralOnePlaceholders, name as noMissingIcuPluralOnePlaceholdersName, } from './rules/no-missing-icu-plural-one-placeholders';
|
|
14
|
+
import { rule as noMultiplePlurals, name as noMultiplePluralsName, } from './rules/no-multiple-plurals';
|
|
15
|
+
import { rule as noMultipleWhitespaces, name as noMultipleWhitespacesName, } from './rules/no-multiple-whitespaces';
|
|
16
|
+
import { rule as noOffset, name as noOffsetName } from './rules/no-offset';
|
|
17
|
+
import { rule as noUselessMessage, name as noUselessMessageName, } from './rules/no-useless-message';
|
|
18
|
+
import { rule as preferFormattedMessage, name as preferFormattedMessageName, } from './rules/prefer-formatted-message';
|
|
19
|
+
import { rule as preferPoundInPlural, name as preferPoundInPluralName, } from './rules/prefer-pound-in-plural';
|
|
20
|
+
import { name, version } from './package.json';
|
|
21
|
+
// All rules
|
|
22
|
+
const rules = {
|
|
23
|
+
// @ts-expect-error
|
|
24
|
+
[blocklistElementRuleName]: blocklistElements,
|
|
25
|
+
// @ts-expect-error
|
|
26
|
+
[enforceDefaultMessageName]: enforceDefaultMessage,
|
|
27
|
+
// @ts-expect-error
|
|
28
|
+
[enforceDescriptionName]: enforceDescription,
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
[enforceIdName]: enforceId,
|
|
31
|
+
// @ts-expect-error
|
|
32
|
+
[enforcePlaceholdersName]: enforcePlaceholders,
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
[enforcePluralRulesName]: enforcePluralRules,
|
|
35
|
+
// @ts-expect-error
|
|
36
|
+
[noCamelCaseName]: noCamelCase,
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
[noComplexSelectorsName]: noComplexSelectors,
|
|
39
|
+
// @ts-expect-error
|
|
40
|
+
[noEmojiName]: noEmoji,
|
|
41
|
+
// @ts-expect-error
|
|
42
|
+
[noIdName]: noId,
|
|
43
|
+
// @ts-expect-error
|
|
44
|
+
[noInvalidICUName]: noInvalidICU,
|
|
45
|
+
// @ts-expect-error
|
|
46
|
+
[noLiteralStringInJsxName]: noLiteralStringInJsx,
|
|
47
|
+
// @ts-expect-error
|
|
48
|
+
[noMultiplePluralsName]: noMultiplePlurals,
|
|
49
|
+
// @ts-expect-error
|
|
50
|
+
[noMultipleWhitespacesName]: noMultipleWhitespaces,
|
|
51
|
+
// @ts-expect-error
|
|
52
|
+
[noOffsetName]: noOffset,
|
|
53
|
+
// @ts-expect-error
|
|
54
|
+
[noUselessMessageName]: noUselessMessage,
|
|
55
|
+
// @ts-expect-error
|
|
56
|
+
[preferFormattedMessageName]: preferFormattedMessage,
|
|
57
|
+
// @ts-expect-error
|
|
58
|
+
[preferPoundInPluralName]: preferPoundInPlural,
|
|
59
|
+
// @ts-expect-error
|
|
60
|
+
[noMissingIcuPluralOnePlaceholdersName]: noMissingIcuPluralOnePlaceholders,
|
|
61
|
+
};
|
|
62
|
+
// Base plugin
|
|
63
|
+
const plugin = {
|
|
64
|
+
meta: { name, version },
|
|
65
|
+
rules,
|
|
66
|
+
};
|
|
67
|
+
// Configs
|
|
68
|
+
const configs = {
|
|
69
|
+
strict: {
|
|
70
|
+
name: 'formatjs/strict',
|
|
71
|
+
plugins: { formatjs: plugin },
|
|
72
|
+
rules: {
|
|
73
|
+
'formatjs/no-offset': 'error',
|
|
74
|
+
'formatjs/enforce-default-message': ['error', 'literal'],
|
|
75
|
+
'formatjs/enforce-description': ['error', 'literal'],
|
|
76
|
+
'formatjs/enforce-placeholders': 'error',
|
|
77
|
+
'formatjs/no-emoji': 'error',
|
|
78
|
+
'formatjs/no-multiple-whitespaces': 'error',
|
|
79
|
+
'formatjs/no-multiple-plurals': 'error',
|
|
80
|
+
'formatjs/no-complex-selectors': ['error', { limit: 20 }],
|
|
81
|
+
'formatjs/no-useless-message': 'error',
|
|
82
|
+
'formatjs/prefer-pound-in-plural': 'error',
|
|
83
|
+
'formatjs/no-missing-icu-plural-one-placeholders': 'error',
|
|
84
|
+
'formatjs/enforce-id': [
|
|
85
|
+
'error',
|
|
86
|
+
{
|
|
87
|
+
idInterpolationPattern: '[sha512:contenthash:base64:10]',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
'formatjs/enforce-plural-rules': [
|
|
91
|
+
'error',
|
|
92
|
+
{
|
|
93
|
+
one: true,
|
|
94
|
+
other: true,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
'formatjs/no-literal-string-in-jsx': [
|
|
98
|
+
'error',
|
|
99
|
+
{
|
|
100
|
+
props: {
|
|
101
|
+
include: [['*', '{label,placeholder,title}']],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
'formatjs/blocklist-elements': ['error', ['selectordinal']],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
recommended: {
|
|
109
|
+
name: 'formatjs/recommended',
|
|
110
|
+
plugins: { formatjs: plugin },
|
|
111
|
+
rules: {
|
|
112
|
+
'formatjs/no-offset': 'error',
|
|
113
|
+
'formatjs/enforce-default-message': ['error', 'literal'],
|
|
114
|
+
'formatjs/enforce-description': ['error', 'literal'],
|
|
115
|
+
'formatjs/enforce-placeholders': 'error',
|
|
116
|
+
'formatjs/no-emoji': 'error',
|
|
117
|
+
'formatjs/no-multiple-whitespaces': 'error',
|
|
118
|
+
'formatjs/no-multiple-plurals': 'error',
|
|
119
|
+
'formatjs/no-complex-selectors': ['error', { limit: 20 }],
|
|
120
|
+
'formatjs/no-useless-message': 'error',
|
|
121
|
+
'formatjs/prefer-pound-in-plural': 'error',
|
|
122
|
+
'formatjs/no-missing-icu-plural-one-placeholders': 'error',
|
|
123
|
+
'formatjs/enforce-plural-rules': [
|
|
124
|
+
'error',
|
|
125
|
+
{
|
|
126
|
+
one: true,
|
|
127
|
+
other: true,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
'formatjs/no-literal-string-in-jsx': [
|
|
131
|
+
'warn',
|
|
132
|
+
{
|
|
133
|
+
props: {
|
|
134
|
+
include: [['*', '{label,placeholder,title}']],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
'formatjs/blocklist-elements': ['error', ['selectordinal']],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
plugin.configs = configs;
|
|
143
|
+
module.exports = plugin;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-formatjs",
|
|
3
|
+
"version": "5.2.6",
|
|
4
|
+
"description": "ESLint plugin for formatjs",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+ssh://git@github.com/formatjs/formatjs.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"eslint",
|
|
12
|
+
"eslintplugin",
|
|
13
|
+
"i18n",
|
|
14
|
+
"formatjs"
|
|
15
|
+
],
|
|
16
|
+
"author": "Long Ho <holevietlong@gmail.com>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/formatjs/formatjs/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/formatjs/formatjs#readme",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@formatjs/icu-messageformat-parser": "workspace:*",
|
|
24
|
+
"@formatjs/ts-transformer": "workspace:*",
|
|
25
|
+
"@types/eslint": "9",
|
|
26
|
+
"@types/picomatch": "3",
|
|
27
|
+
"@typescript-eslint/utils": "8.17.0",
|
|
28
|
+
"emoji-regex": "10",
|
|
29
|
+
"magic-string": "^0.30.0",
|
|
30
|
+
"picomatch": "2 || 3 || 4",
|
|
31
|
+
"tslib": "2",
|
|
32
|
+
"unicode-emoji-utils": "^1.2.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"eslint": "9"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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", [], unknown, ESLintUtils.RuleListener>;
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,153 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
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 {};
|