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
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import {Rule} from 'eslint'
|
|
2
|
-
import {TSESTree} from '@typescript-eslint/typescript-estree'
|
|
3
|
-
import {extractMessages, getSettings} from '../util'
|
|
4
|
-
import {
|
|
5
|
-
parse,
|
|
6
|
-
isPluralElement,
|
|
7
|
-
MessageFormatElement,
|
|
8
|
-
} from '@formatjs/icu-messageformat-parser'
|
|
9
|
-
|
|
10
|
-
class PluralRulesEnforcement extends Error {
|
|
11
|
-
public message: string
|
|
12
|
-
constructor(message: string) {
|
|
13
|
-
super()
|
|
14
|
-
this.message = message
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
enum LDML {
|
|
19
|
-
zero = 'zero',
|
|
20
|
-
one = 'one',
|
|
21
|
-
two = 'two',
|
|
22
|
-
few = 'few',
|
|
23
|
-
many = 'many',
|
|
24
|
-
other = 'other',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function verifyAst(
|
|
28
|
-
plConfig: Record<LDML, boolean>,
|
|
29
|
-
ast: MessageFormatElement[]
|
|
30
|
-
) {
|
|
31
|
-
for (const el of ast) {
|
|
32
|
-
if (isPluralElement(el)) {
|
|
33
|
-
const rules = Object.keys(plConfig) as Array<LDML>
|
|
34
|
-
for (const rule of rules) {
|
|
35
|
-
if (plConfig[rule] && !el.options[rule]) {
|
|
36
|
-
throw new PluralRulesEnforcement(`Missing plural rule "${rule}"`)
|
|
37
|
-
}
|
|
38
|
-
if (!plConfig[rule] && el.options[rule]) {
|
|
39
|
-
throw new PluralRulesEnforcement(`Plural rule "${rule}" is forbidden`)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const {options} = el
|
|
43
|
-
for (const selector of Object.keys(options)) {
|
|
44
|
-
verifyAst(plConfig, options[selector].value)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function checkNode(context: Rule.RuleContext, node: TSESTree.Node) {
|
|
51
|
-
const settings = getSettings(context)
|
|
52
|
-
const msgs = extractMessages(node, settings)
|
|
53
|
-
if (!msgs.length) {
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const plConfig: Record<keyof LDML, boolean> = context.options[0]
|
|
58
|
-
if (!plConfig) {
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
for (const [
|
|
62
|
-
{
|
|
63
|
-
message: {defaultMessage},
|
|
64
|
-
messageNode,
|
|
65
|
-
},
|
|
66
|
-
] of msgs) {
|
|
67
|
-
if (!defaultMessage || !messageNode) {
|
|
68
|
-
continue
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
verifyAst(
|
|
72
|
-
context.options[0],
|
|
73
|
-
parse(defaultMessage, {
|
|
74
|
-
ignoreTag: settings.ignoreTag,
|
|
75
|
-
})
|
|
76
|
-
)
|
|
77
|
-
} catch (e) {
|
|
78
|
-
context.report({
|
|
79
|
-
node: messageNode as any,
|
|
80
|
-
message: e instanceof Error ? e.message : String(e),
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const rule: Rule.RuleModule = {
|
|
87
|
-
meta: {
|
|
88
|
-
type: 'problem',
|
|
89
|
-
docs: {
|
|
90
|
-
description:
|
|
91
|
-
'Enforce plural rules to always specify certain categories like `one`/`other`',
|
|
92
|
-
category: 'Errors',
|
|
93
|
-
recommended: false,
|
|
94
|
-
url: 'https://formatjs.io/docs/tooling/linter#enforce-plural-rules',
|
|
95
|
-
},
|
|
96
|
-
fixable: 'code',
|
|
97
|
-
schema: [
|
|
98
|
-
{
|
|
99
|
-
type: 'object',
|
|
100
|
-
properties: Object.keys(LDML).reduce(
|
|
101
|
-
(schema: Record<string, {type: 'boolean'}>, k) => {
|
|
102
|
-
schema[k] = {
|
|
103
|
-
type: 'boolean',
|
|
104
|
-
}
|
|
105
|
-
return schema
|
|
106
|
-
},
|
|
107
|
-
{}
|
|
108
|
-
),
|
|
109
|
-
additionalProperties: false,
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
create(context) {
|
|
114
|
-
const callExpressionVisitor = (node: TSESTree.Node) =>
|
|
115
|
-
checkNode(context, node)
|
|
116
|
-
|
|
117
|
-
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
118
|
-
return context.parserServices.defineTemplateBodyVisitor(
|
|
119
|
-
{
|
|
120
|
-
CallExpression: callExpressionVisitor,
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
CallExpression: callExpressionVisitor,
|
|
124
|
-
}
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
JSXOpeningElement: (node: TSESTree.Node) => checkNode(context, node),
|
|
129
|
-
CallExpression: callExpressionVisitor,
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export default rule
|
package/rules/no-camel-case.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import {Rule} from 'eslint'
|
|
2
|
-
import {TSESTree} from '@typescript-eslint/typescript-estree'
|
|
3
|
-
import {
|
|
4
|
-
parse,
|
|
5
|
-
isPluralElement,
|
|
6
|
-
MessageFormatElement,
|
|
7
|
-
isArgumentElement,
|
|
8
|
-
} from '@formatjs/icu-messageformat-parser'
|
|
9
|
-
import {extractMessages, getSettings} from '../util'
|
|
10
|
-
|
|
11
|
-
const CAMEL_CASE_REGEX = /[A-Z]/
|
|
12
|
-
|
|
13
|
-
class CamelCase extends Error {
|
|
14
|
-
public message = 'Camel case arguments are not allowed'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function verifyAst(ast: MessageFormatElement[]) {
|
|
18
|
-
for (const el of ast) {
|
|
19
|
-
if (isArgumentElement(el)) {
|
|
20
|
-
if (CAMEL_CASE_REGEX.test(el.value)) {
|
|
21
|
-
throw new CamelCase()
|
|
22
|
-
}
|
|
23
|
-
continue
|
|
24
|
-
}
|
|
25
|
-
if (isPluralElement(el)) {
|
|
26
|
-
if (CAMEL_CASE_REGEX.test(el.value)) {
|
|
27
|
-
throw new CamelCase()
|
|
28
|
-
}
|
|
29
|
-
const {options} = el
|
|
30
|
-
for (const selector of Object.keys(options)) {
|
|
31
|
-
verifyAst(options[selector].value)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function checkNode(context: Rule.RuleContext, node: TSESTree.Node) {
|
|
38
|
-
const settings = getSettings(context)
|
|
39
|
-
const msgs = extractMessages(node, settings)
|
|
40
|
-
|
|
41
|
-
for (const [
|
|
42
|
-
{
|
|
43
|
-
message: {defaultMessage},
|
|
44
|
-
messageNode,
|
|
45
|
-
},
|
|
46
|
-
] of msgs) {
|
|
47
|
-
if (!defaultMessage || !messageNode) {
|
|
48
|
-
continue
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
verifyAst(
|
|
52
|
-
parse(defaultMessage, {
|
|
53
|
-
ignoreTag: settings.ignoreTag,
|
|
54
|
-
})
|
|
55
|
-
)
|
|
56
|
-
} catch (e) {
|
|
57
|
-
context.report({
|
|
58
|
-
node: messageNode as any,
|
|
59
|
-
message: e instanceof Error ? e.message : String(e),
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const rule: Rule.RuleModule = {
|
|
66
|
-
meta: {
|
|
67
|
-
type: 'problem',
|
|
68
|
-
docs: {
|
|
69
|
-
description: 'Disallow camel case placeholders in message',
|
|
70
|
-
category: 'Errors',
|
|
71
|
-
recommended: false,
|
|
72
|
-
url: 'https://formatjs.io/docs/tooling/linter#no-camel-case',
|
|
73
|
-
},
|
|
74
|
-
fixable: 'code',
|
|
75
|
-
},
|
|
76
|
-
create(context) {
|
|
77
|
-
const callExpressionVisitor = (node: TSESTree.Node) =>
|
|
78
|
-
checkNode(context, node)
|
|
79
|
-
|
|
80
|
-
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
81
|
-
return context.parserServices.defineTemplateBodyVisitor(
|
|
82
|
-
{
|
|
83
|
-
CallExpression: callExpressionVisitor,
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
CallExpression: callExpressionVisitor,
|
|
87
|
-
}
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
JSXOpeningElement: (node: TSESTree.Node) => checkNode(context, node),
|
|
92
|
-
CallExpression: callExpressionVisitor,
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export default rule
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import {Rule} from 'eslint'
|
|
2
|
-
import {TSESTree} from '@typescript-eslint/typescript-estree'
|
|
3
|
-
import {extractMessages, getSettings} from '../util'
|
|
4
|
-
import {
|
|
5
|
-
parse,
|
|
6
|
-
isPluralElement,
|
|
7
|
-
MessageFormatElement,
|
|
8
|
-
isSelectElement,
|
|
9
|
-
} from '@formatjs/icu-messageformat-parser'
|
|
10
|
-
import {hoistSelectors} from '@formatjs/icu-messageformat-parser/manipulator'
|
|
11
|
-
|
|
12
|
-
interface Config {
|
|
13
|
-
limit: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function calculateComplexity(ast: MessageFormatElement[]): number {
|
|
17
|
-
if (ast.length === 1) {
|
|
18
|
-
const el = ast[0]
|
|
19
|
-
if (isPluralElement(el) || isSelectElement(el)) {
|
|
20
|
-
return Object.keys(el.options).reduce((complexity, k) => {
|
|
21
|
-
return complexity + calculateComplexity(el.options[k].value)
|
|
22
|
-
}, 0)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return 1
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function checkNode(context: Rule.RuleContext, node: TSESTree.Node) {
|
|
29
|
-
const settings = getSettings(context)
|
|
30
|
-
const msgs = extractMessages(node, settings)
|
|
31
|
-
if (!msgs.length) {
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const config: Config = {
|
|
36
|
-
limit: 20,
|
|
37
|
-
...(context.options[0] || {}),
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
for (const [
|
|
41
|
-
{
|
|
42
|
-
message: {defaultMessage},
|
|
43
|
-
messageNode,
|
|
44
|
-
},
|
|
45
|
-
] of msgs) {
|
|
46
|
-
if (!defaultMessage || !messageNode) {
|
|
47
|
-
continue
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let ast: MessageFormatElement[]
|
|
51
|
-
try {
|
|
52
|
-
ast = parse(defaultMessage, {
|
|
53
|
-
ignoreTag: settings.ignoreTag,
|
|
54
|
-
})
|
|
55
|
-
} catch (e) {
|
|
56
|
-
context.report({
|
|
57
|
-
node: messageNode as any,
|
|
58
|
-
message: e instanceof Error ? e.message : String(e),
|
|
59
|
-
})
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const hoistedAst = hoistSelectors(ast)
|
|
64
|
-
const complexity = calculateComplexity(hoistedAst)
|
|
65
|
-
if (complexity > config.limit) {
|
|
66
|
-
context.report({
|
|
67
|
-
node: messageNode as any,
|
|
68
|
-
message: `Message complexity is too high (${complexity} vs limit at ${config.limit})`,
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const rule: Rule.RuleModule = {
|
|
75
|
-
meta: {
|
|
76
|
-
type: 'problem',
|
|
77
|
-
docs: {
|
|
78
|
-
description: `Make sure a sentence is not too complex.
|
|
79
|
-
Complexity is determined by how many strings are produced when we try to
|
|
80
|
-
flatten the sentence given its selectors. For example:
|
|
81
|
-
"I have {count, plural, one{a dog} other{many dogs}}"
|
|
82
|
-
has the complexity of 2 because flattening the plural selector
|
|
83
|
-
results in 2 sentences: "I have a dog" & "I have many dogs".
|
|
84
|
-
Default complexity limit is 20
|
|
85
|
-
(using Smartling as a reference: https://help.smartling.com/hc/en-us/articles/360008030994-ICU-MessageFormat)
|
|
86
|
-
`,
|
|
87
|
-
category: 'Errors',
|
|
88
|
-
recommended: false,
|
|
89
|
-
url: 'https://formatjs.io/docs/tooling/linter#no-complex-selectors',
|
|
90
|
-
},
|
|
91
|
-
schema: [
|
|
92
|
-
{
|
|
93
|
-
type: 'object',
|
|
94
|
-
properties: {
|
|
95
|
-
limit: {
|
|
96
|
-
type: 'number',
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
additionalProperties: false,
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
fixable: 'code',
|
|
103
|
-
},
|
|
104
|
-
create(context) {
|
|
105
|
-
const callExpressionVisitor = (node: TSESTree.Node) =>
|
|
106
|
-
checkNode(context, node)
|
|
107
|
-
|
|
108
|
-
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
109
|
-
return context.parserServices.defineTemplateBodyVisitor(
|
|
110
|
-
{
|
|
111
|
-
CallExpression: callExpressionVisitor,
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
CallExpression: callExpressionVisitor,
|
|
115
|
-
}
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
JSXOpeningElement: (node: TSESTree.Node) => checkNode(context, node),
|
|
120
|
-
CallExpression: callExpressionVisitor,
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export default rule
|
package/rules/no-emoji.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import {Rule} from 'eslint'
|
|
2
|
-
import {TSESTree} from '@typescript-eslint/typescript-estree'
|
|
3
|
-
import {extractMessages, getSettings} from '../util'
|
|
4
|
-
import emojiRegex from 'emoji-regex'
|
|
5
|
-
const EMOJI_REGEX: RegExp = (emojiRegex as any)()
|
|
6
|
-
|
|
7
|
-
function checkNode(context: Rule.RuleContext, node: TSESTree.Node) {
|
|
8
|
-
const msgs = extractMessages(node, getSettings(context))
|
|
9
|
-
|
|
10
|
-
for (const [
|
|
11
|
-
{
|
|
12
|
-
message: {defaultMessage},
|
|
13
|
-
messageNode,
|
|
14
|
-
},
|
|
15
|
-
] of msgs) {
|
|
16
|
-
if (!defaultMessage || !messageNode) {
|
|
17
|
-
continue
|
|
18
|
-
}
|
|
19
|
-
if (EMOJI_REGEX.test(defaultMessage)) {
|
|
20
|
-
context.report({
|
|
21
|
-
node: messageNode as any,
|
|
22
|
-
message: 'Emojis are not allowed',
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const rule: Rule.RuleModule = {
|
|
29
|
-
meta: {
|
|
30
|
-
type: 'problem',
|
|
31
|
-
docs: {
|
|
32
|
-
description: 'Disallow emojis in message',
|
|
33
|
-
category: 'Errors',
|
|
34
|
-
recommended: false,
|
|
35
|
-
url: 'https://formatjs.io/docs/tooling/linter#no-emoji',
|
|
36
|
-
},
|
|
37
|
-
fixable: 'code',
|
|
38
|
-
},
|
|
39
|
-
create(context) {
|
|
40
|
-
const callExpressionVisitor = (node: TSESTree.Node) =>
|
|
41
|
-
checkNode(context, node)
|
|
42
|
-
|
|
43
|
-
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
44
|
-
return context.parserServices.defineTemplateBodyVisitor(
|
|
45
|
-
{
|
|
46
|
-
CallExpression: callExpressionVisitor,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
CallExpression: callExpressionVisitor,
|
|
50
|
-
}
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
JSXOpeningElement: (node: TSESTree.Node) => checkNode(context, node),
|
|
55
|
-
CallExpression: callExpressionVisitor,
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export default rule
|
package/rules/no-id.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import {Rule, SourceCode} from 'eslint'
|
|
2
|
-
import {extractMessages, getSettings} from '../util'
|
|
3
|
-
import {TSESTree} from '@typescript-eslint/typescript-estree'
|
|
4
|
-
import * as ESTree from 'estree'
|
|
5
|
-
|
|
6
|
-
function isComment(
|
|
7
|
-
token: ReturnType<SourceCode['getTokenAfter']>
|
|
8
|
-
): token is ESTree.Comment {
|
|
9
|
-
return !!token && (token.type === 'Block' || token.type === 'Line')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function checkNode(context: Rule.RuleContext, node: TSESTree.Node) {
|
|
13
|
-
const msgs = extractMessages(node, getSettings(context))
|
|
14
|
-
for (const [{idPropNode}] of msgs) {
|
|
15
|
-
if (idPropNode) {
|
|
16
|
-
context.report({
|
|
17
|
-
node: idPropNode as any,
|
|
18
|
-
message: 'Manual `id` are not allowed in message descriptor',
|
|
19
|
-
fix(fixer) {
|
|
20
|
-
const src = context.getSourceCode()
|
|
21
|
-
const token = src.getTokenAfter(idPropNode as any)
|
|
22
|
-
const fixes = [fixer.remove(idPropNode as any)]
|
|
23
|
-
if (token && !isComment(token) && token?.value === ',') {
|
|
24
|
-
fixes.push(fixer.remove(token))
|
|
25
|
-
}
|
|
26
|
-
return fixes
|
|
27
|
-
},
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default {
|
|
34
|
-
meta: {
|
|
35
|
-
type: 'problem',
|
|
36
|
-
docs: {
|
|
37
|
-
description: 'Ban explicit ID from MessageDescriptor',
|
|
38
|
-
category: 'Errors',
|
|
39
|
-
recommended: false,
|
|
40
|
-
url: 'https://formatjs.io/docs/tooling/linter#no-id',
|
|
41
|
-
},
|
|
42
|
-
fixable: 'code',
|
|
43
|
-
},
|
|
44
|
-
create(context) {
|
|
45
|
-
const callExpressionVisitor = (node: TSESTree.Node) =>
|
|
46
|
-
checkNode(context, node)
|
|
47
|
-
|
|
48
|
-
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
49
|
-
return context.parserServices.defineTemplateBodyVisitor(
|
|
50
|
-
{
|
|
51
|
-
CallExpression: callExpressionVisitor,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
CallExpression: callExpressionVisitor,
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
JSXOpeningElement: (node: TSESTree.Node) => checkNode(context, node),
|
|
60
|
-
CallExpression: callExpressionVisitor,
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
} as Rule.RuleModule
|
package/rules/no-invalid-icu.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import {Rule} from 'eslint'
|
|
2
|
-
import {TSESTree} from '@typescript-eslint/typescript-estree'
|
|
3
|
-
import {parse} from '@formatjs/icu-messageformat-parser'
|
|
4
|
-
import {extractMessages, getSettings} from '../util'
|
|
5
|
-
|
|
6
|
-
function checkNode(context: Rule.RuleContext, node: TSESTree.Node) {
|
|
7
|
-
const settings = getSettings(context)
|
|
8
|
-
const msgs = extractMessages(node, settings)
|
|
9
|
-
|
|
10
|
-
if (!msgs.length) {
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
for (const [
|
|
15
|
-
{
|
|
16
|
-
message: {defaultMessage},
|
|
17
|
-
messageNode,
|
|
18
|
-
},
|
|
19
|
-
] of msgs) {
|
|
20
|
-
if (!defaultMessage || !messageNode) {
|
|
21
|
-
continue
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
parse(defaultMessage, {
|
|
26
|
-
ignoreTag: settings.ignoreTag,
|
|
27
|
-
})
|
|
28
|
-
} catch (e) {
|
|
29
|
-
const msg = e instanceof Error ? e.message : e
|
|
30
|
-
context.report({
|
|
31
|
-
node: messageNode as any,
|
|
32
|
-
message: `Error parsing ICU string: ${msg}`,
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const rule: Rule.RuleModule = {
|
|
39
|
-
meta: {
|
|
40
|
-
type: 'problem',
|
|
41
|
-
docs: {
|
|
42
|
-
description: `Make sure ICU messages are formatted correctly with no bad select statements, plurals, etc.`,
|
|
43
|
-
category: 'Errors',
|
|
44
|
-
recommended: true,
|
|
45
|
-
},
|
|
46
|
-
fixable: 'code',
|
|
47
|
-
},
|
|
48
|
-
create(context) {
|
|
49
|
-
const callExpressionVisitor = (node: TSESTree.Node) =>
|
|
50
|
-
checkNode(context, node)
|
|
51
|
-
|
|
52
|
-
if (context.parserServices.defineTemplateBodyVisitor) {
|
|
53
|
-
return context.parserServices.defineTemplateBodyVisitor(
|
|
54
|
-
{
|
|
55
|
-
CallExpression: callExpressionVisitor,
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
CallExpression: callExpressionVisitor,
|
|
59
|
-
}
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
JSXOpeningElement: (node: TSESTree.Node) => checkNode(context, node),
|
|
64
|
-
CallExpression: callExpressionVisitor,
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default rule
|