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.
Files changed (88) hide show
  1. package/index.d.ts +22 -0
  2. package/index.d.ts.map +1 -0
  3. package/index.js +38 -0
  4. package/package.json +3 -3
  5. package/rules/blocklist-elements.d.ts +4 -0
  6. package/rules/blocklist-elements.d.ts.map +1 -0
  7. package/rules/blocklist-elements.js +127 -0
  8. package/rules/enforce-default-message.d.ts +4 -0
  9. package/rules/enforce-default-message.d.ts.map +1 -0
  10. package/rules/enforce-default-message.js +57 -0
  11. package/rules/enforce-description.d.ts +4 -0
  12. package/rules/enforce-description.d.ts.map +1 -0
  13. package/rules/enforce-description.js +54 -0
  14. package/rules/enforce-id.d.ts +4 -0
  15. package/rules/enforce-id.d.ts.map +1 -0
  16. package/rules/enforce-id.js +125 -0
  17. package/rules/enforce-placeholders.d.ts +4 -0
  18. package/rules/enforce-placeholders.d.ts.map +1 -0
  19. package/rules/enforce-placeholders.js +118 -0
  20. package/rules/enforce-plural-rules.d.ts +4 -0
  21. package/rules/enforce-plural-rules.d.ts.map +1 -0
  22. package/rules/enforce-plural-rules.js +104 -0
  23. package/rules/no-camel-case.d.ts +4 -0
  24. package/rules/no-camel-case.d.ts.map +1 -0
  25. package/rules/no-camel-case.js +77 -0
  26. package/rules/no-complex-selectors.d.ts +4 -0
  27. package/rules/no-complex-selectors.d.ts.map +1 -0
  28. package/rules/no-complex-selectors.js +99 -0
  29. package/rules/no-emoji.d.ts +4 -0
  30. package/rules/no-emoji.d.ts.map +1 -0
  31. package/rules/no-emoji.js +47 -0
  32. package/rules/no-id.d.ts +4 -0
  33. package/rules/no-id.d.ts.map +1 -0
  34. package/rules/no-id.js +52 -0
  35. package/rules/no-invalid-icu.d.ts +4 -0
  36. package/rules/no-invalid-icu.d.ts.map +1 -0
  37. package/rules/no-invalid-icu.js +54 -0
  38. package/rules/no-literal-string-in-jsx.d.ts +4 -0
  39. package/rules/no-literal-string-in-jsx.d.ts.map +1 -0
  40. package/rules/no-literal-string-in-jsx.js +166 -0
  41. package/rules/no-multiple-plurals.d.ts +4 -0
  42. package/rules/no-multiple-plurals.d.ts.map +1 -0
  43. package/rules/no-multiple-plurals.js +71 -0
  44. package/rules/no-multiple-whitespaces.d.ts +4 -0
  45. package/rules/no-multiple-whitespaces.d.ts.map +1 -0
  46. package/rules/no-multiple-whitespaces.js +146 -0
  47. package/rules/no-offset.d.ts +4 -0
  48. package/rules/no-offset.d.ts.map +1 -0
  49. package/rules/no-offset.js +70 -0
  50. package/util.d.ts +24 -0
  51. package/util.d.ts.map +1 -0
  52. package/util.js +240 -0
  53. package/BUILD +0 -89
  54. package/CHANGELOG.md +0 -892
  55. package/index.ts +0 -38
  56. package/rules/blocklist-elements.ts +0 -159
  57. package/rules/enforce-default-message.ts +0 -71
  58. package/rules/enforce-description.ts +0 -68
  59. package/rules/enforce-id.ts +0 -171
  60. package/rules/enforce-placeholders.ts +0 -161
  61. package/rules/enforce-plural-rules.ts +0 -134
  62. package/rules/no-camel-case.ts +0 -97
  63. package/rules/no-complex-selectors.ts +0 -125
  64. package/rules/no-emoji.ts +0 -60
  65. package/rules/no-id.ts +0 -63
  66. package/rules/no-invalid-icu.ts +0 -69
  67. package/rules/no-literal-string-in-jsx.ts +0 -213
  68. package/rules/no-multiple-plurals.ts +0 -89
  69. package/rules/no-multiple-whitespaces.ts +0 -194
  70. package/rules/no-offset.ts +0 -88
  71. package/tests/blocklist-elements.test.ts +0 -120
  72. package/tests/enforce-default-message.test.ts +0 -260
  73. package/tests/enforce-description.test.ts +0 -117
  74. package/tests/enforce-id.test.ts +0 -209
  75. package/tests/enforce-placeholders.test.ts +0 -170
  76. package/tests/enforce-plural-rules.test.ts +0 -86
  77. package/tests/fixtures.ts +0 -15
  78. package/tests/no-camel-case.test.ts +0 -31
  79. package/tests/no-complex-selectors.test.ts +0 -125
  80. package/tests/no-id.test.ts +0 -151
  81. package/tests/no-invalid-icu.test.ts +0 -106
  82. package/tests/no-literal-string-in-jsx.test.ts +0 -213
  83. package/tests/no-multiple-plurals.test.ts +0 -42
  84. package/tests/no-multiple-whitespaces.test.ts +0 -100
  85. package/tests/no-offset.test.ts +0 -41
  86. package/tests/util.ts +0 -26
  87. package/tsconfig.json +0 -5
  88. 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
@@ -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
@@ -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