eslint-plugin-formatjs 6.4.4 → 6.4.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/index.d.ts +15 -12
- package/index.js +4603 -54
- package/index.js.map +1 -0
- package/package.json +3 -3
- package/util.d.ts +654 -29
- package/util.js +62 -134
- package/util.js.map +1 -0
- package/emoji-data.generated.d.ts +0 -27
- package/emoji-data.generated.js +0 -2564
- package/emoji-utils.d.ts +0 -43
- package/emoji-utils.js +0 -145
- package/messages.d.ts +0 -2
- package/messages.js +0 -1
- package/rules/blocklist-elements.d.ts +0 -14
- package/rules/blocklist-elements.js +0 -129
- package/rules/enforce-default-message.d.ts +0 -7
- package/rules/enforce-default-message.js +0 -57
- package/rules/enforce-description.d.ts +0 -11
- package/rules/enforce-description.js +0 -97
- package/rules/enforce-id.d.ts +0 -8
- package/rules/enforce-id.js +0 -135
- package/rules/enforce-placeholders.d.ts +0 -3
- package/rules/enforce-placeholders.js +0 -128
- package/rules/enforce-plural-rules.d.ts +0 -14
- package/rules/enforce-plural-rules.js +0 -108
- package/rules/no-camel-case.d.ts +0 -3
- package/rules/no-camel-case.js +0 -85
- package/rules/no-complex-selectors.d.ts +0 -3
- package/rules/no-complex-selectors.js +0 -119
- package/rules/no-emoji.d.ts +0 -8
- package/rules/no-emoji.js +0 -88
- package/rules/no-id.d.ts +0 -3
- package/rules/no-id.js +0 -48
- package/rules/no-invalid-icu.d.ts +0 -3
- package/rules/no-invalid-icu.js +0 -56
- package/rules/no-literal-string-in-jsx.d.ts +0 -3
- package/rules/no-literal-string-in-jsx.js +0 -161
- package/rules/no-literal-string-in-object.d.ts +0 -3
- package/rules/no-literal-string-in-object.js +0 -59
- package/rules/no-missing-icu-plural-one-placeholders.d.ts +0 -5
- package/rules/no-missing-icu-plural-one-placeholders.js +0 -94
- package/rules/no-multiple-plurals.d.ts +0 -3
- package/rules/no-multiple-plurals.js +0 -76
- package/rules/no-multiple-whitespaces.d.ts +0 -3
- package/rules/no-multiple-whitespaces.js +0 -126
- package/rules/no-offset.d.ts +0 -3
- package/rules/no-offset.js +0 -75
- package/rules/no-useless-message.d.ts +0 -3
- package/rules/no-useless-message.js +0 -69
- package/rules/prefer-formatted-message.d.ts +0 -3
- package/rules/prefer-formatted-message.js +0 -26
- package/rules/prefer-full-sentence.d.ts +0 -3
- package/rules/prefer-full-sentence.js +0 -111
- package/rules/prefer-pound-in-plural.d.ts +0 -3
- package/rules/prefer-pound-in-plural.js +0 -163
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { TYPE, parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
-
import { extractMessages, getSettings } from "../util.js";
|
|
3
|
-
import { CORE_MESSAGES } from "../messages.js";
|
|
4
|
-
function collectPlaceholderNames(ast) {
|
|
5
|
-
const placeholderNames = new Set();
|
|
6
|
-
_traverse(ast);
|
|
7
|
-
return placeholderNames;
|
|
8
|
-
function _traverse(ast) {
|
|
9
|
-
for (const element of ast) {
|
|
10
|
-
switch (element.type) {
|
|
11
|
-
case TYPE.literal:
|
|
12
|
-
case TYPE.pound: break;
|
|
13
|
-
case TYPE.tag:
|
|
14
|
-
placeholderNames.add(element.value);
|
|
15
|
-
_traverse(element.children);
|
|
16
|
-
break;
|
|
17
|
-
case TYPE.plural:
|
|
18
|
-
case TYPE.select:
|
|
19
|
-
placeholderNames.add(element.value);
|
|
20
|
-
for (const { value } of Object.values(element.options)) {
|
|
21
|
-
_traverse(value);
|
|
22
|
-
}
|
|
23
|
-
break;
|
|
24
|
-
default:
|
|
25
|
-
placeholderNames.add(element.value);
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function checkNode(context, node) {
|
|
32
|
-
const settings = getSettings(context);
|
|
33
|
-
const msgs = extractMessages(node, {
|
|
34
|
-
excludeMessageDeclCalls: true,
|
|
35
|
-
...settings
|
|
36
|
-
});
|
|
37
|
-
const { options: [opt] } = context;
|
|
38
|
-
const ignoreList = new Set(opt?.ignoreList || []);
|
|
39
|
-
for (const [{ message: { defaultMessage }, messageNode }, values] of msgs) {
|
|
40
|
-
if (!defaultMessage || !messageNode) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (values && values.type !== "ObjectExpression") {
|
|
44
|
-
// cannot evaluate this
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (values?.properties.find((prop) => prop.type === "SpreadElement")) {
|
|
48
|
-
// cannot evaluate the spread element
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const literalElementByLiteralKey = new Map();
|
|
52
|
-
if (values) {
|
|
53
|
-
for (const prop of values.properties) {
|
|
54
|
-
if (prop.type === "Property" && !prop.computed) {
|
|
55
|
-
const name = prop.key.type === "Identifier" ? prop.key.name : String(prop.key.value);
|
|
56
|
-
literalElementByLiteralKey.set(name, prop);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
let ast;
|
|
61
|
-
try {
|
|
62
|
-
ast = parse(defaultMessage, { ignoreTag: settings.ignoreTag });
|
|
63
|
-
} catch (e) {
|
|
64
|
-
context.report({
|
|
65
|
-
node: messageNode,
|
|
66
|
-
messageId: "parseError",
|
|
67
|
-
data: { error: e instanceof Error ? e.message : String(e) }
|
|
68
|
-
});
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const placeholderNames = collectPlaceholderNames(ast);
|
|
72
|
-
const missingPlaceholders = [];
|
|
73
|
-
placeholderNames.forEach((name) => {
|
|
74
|
-
if (!ignoreList.has(name) && !literalElementByLiteralKey.has(name)) {
|
|
75
|
-
missingPlaceholders.push(name);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
if (missingPlaceholders.length > 0) {
|
|
79
|
-
context.report({
|
|
80
|
-
node: messageNode,
|
|
81
|
-
messageId: "missingValue",
|
|
82
|
-
data: { list: missingPlaceholders.join(", ") }
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
literalElementByLiteralKey.forEach((element, key) => {
|
|
86
|
-
if (!ignoreList.has(key) && !placeholderNames.has(key)) {
|
|
87
|
-
context.report({
|
|
88
|
-
node: element,
|
|
89
|
-
messageId: "unusedValue"
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
export const name = "enforce-placeholders";
|
|
96
|
-
export const rule = {
|
|
97
|
-
meta: {
|
|
98
|
-
type: "problem",
|
|
99
|
-
docs: {
|
|
100
|
-
description: "Enforce that all messages with placeholders have enough passed-in values",
|
|
101
|
-
url: "https://formatjs.github.io/docs/tooling/linter#enforce-placeholders"
|
|
102
|
-
},
|
|
103
|
-
schema: [{
|
|
104
|
-
type: "object",
|
|
105
|
-
properties: { ignoreList: {
|
|
106
|
-
type: "array",
|
|
107
|
-
items: { type: "string" }
|
|
108
|
-
} },
|
|
109
|
-
additionalProperties: false
|
|
110
|
-
}],
|
|
111
|
-
messages: {
|
|
112
|
-
...CORE_MESSAGES,
|
|
113
|
-
missingValue: "Missing value(s) for the following placeholder(s): {{list}}.",
|
|
114
|
-
unusedValue: "Value not used by the message."
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
create(context) {
|
|
118
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
119
|
-
const parserServices = context.sourceCode.parserServices;
|
|
120
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
121
|
-
return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
125
|
-
CallExpression: callExpressionVisitor
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Rule } from "eslint";
|
|
2
|
-
declare enum LDML {
|
|
3
|
-
zero = "zero",
|
|
4
|
-
one = "one",
|
|
5
|
-
two = "two",
|
|
6
|
-
few = "few",
|
|
7
|
-
many = "many",
|
|
8
|
-
other = "other"
|
|
9
|
-
}
|
|
10
|
-
type PluralConfig = { [key in LDML]? : boolean };
|
|
11
|
-
export type Options = [PluralConfig?];
|
|
12
|
-
export declare const name = "enforce-plural-rules";
|
|
13
|
-
export declare const rule: Rule.RuleModule;
|
|
14
|
-
export {};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { isPluralElement, parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
-
import { extractMessages, getSettings } from "../util.js";
|
|
3
|
-
import { CORE_MESSAGES } from "../messages.js";
|
|
4
|
-
var LDML = /* @__PURE__ */ function(LDML) {
|
|
5
|
-
LDML["zero"] = "zero";
|
|
6
|
-
LDML["one"] = "one";
|
|
7
|
-
LDML["two"] = "two";
|
|
8
|
-
LDML["few"] = "few";
|
|
9
|
-
LDML["many"] = "many";
|
|
10
|
-
LDML["other"] = "other";
|
|
11
|
-
return LDML;
|
|
12
|
-
}(LDML || {});
|
|
13
|
-
function verifyAst(plConfig, ast) {
|
|
14
|
-
const errors = [];
|
|
15
|
-
for (const el of ast) {
|
|
16
|
-
if (isPluralElement(el)) {
|
|
17
|
-
const rules = Object.keys(plConfig);
|
|
18
|
-
for (const rule of rules) {
|
|
19
|
-
if (plConfig[rule] && !el.options[rule]) {
|
|
20
|
-
errors.push({
|
|
21
|
-
messageId: "missingPlural",
|
|
22
|
-
data: { rule }
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
if (!plConfig[rule] && el.options[rule]) {
|
|
26
|
-
errors.push({
|
|
27
|
-
messageId: "forbidden",
|
|
28
|
-
data: { rule }
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
const { options } = el;
|
|
33
|
-
for (const selector of Object.keys(options)) {
|
|
34
|
-
errors.push(...verifyAst(plConfig, options[selector].value));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return errors;
|
|
39
|
-
}
|
|
40
|
-
function checkNode(context, node) {
|
|
41
|
-
const settings = getSettings(context);
|
|
42
|
-
const msgs = extractMessages(node, settings);
|
|
43
|
-
if (!msgs.length) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const plConfig = context.options[0];
|
|
47
|
-
if (!plConfig) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
|
|
51
|
-
if (!defaultMessage || !messageNode) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
let ast;
|
|
55
|
-
try {
|
|
56
|
-
ast = parse(defaultMessage, { ignoreTag: settings.ignoreTag });
|
|
57
|
-
} catch (e) {
|
|
58
|
-
context.report({
|
|
59
|
-
node: messageNode,
|
|
60
|
-
messageId: "parseError",
|
|
61
|
-
data: { error: e.message }
|
|
62
|
-
});
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const errors = verifyAst(plConfig, ast);
|
|
66
|
-
for (const error of errors) {
|
|
67
|
-
context.report({
|
|
68
|
-
node: messageNode,
|
|
69
|
-
...error
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export const name = "enforce-plural-rules";
|
|
75
|
-
export const rule = {
|
|
76
|
-
meta: {
|
|
77
|
-
type: "problem",
|
|
78
|
-
docs: {
|
|
79
|
-
description: "Enforce plural rules to always specify certain categories like `one`/`other`",
|
|
80
|
-
url: "https://formatjs.github.io/docs/tooling/linter#enforce-plural-rules"
|
|
81
|
-
},
|
|
82
|
-
fixable: "code",
|
|
83
|
-
schema: [{
|
|
84
|
-
type: "object",
|
|
85
|
-
properties: Object.keys(LDML).reduce((schema, k) => {
|
|
86
|
-
schema[k] = { type: "boolean" };
|
|
87
|
-
return schema;
|
|
88
|
-
}, {}),
|
|
89
|
-
additionalProperties: false
|
|
90
|
-
}],
|
|
91
|
-
messages: {
|
|
92
|
-
...CORE_MESSAGES,
|
|
93
|
-
missingPlural: `Missing plural rule "{{rule}}"`,
|
|
94
|
-
forbidden: `Plural rule "{{rule}}" is forbidden`
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
create(context) {
|
|
98
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
99
|
-
const parserServices = context.sourceCode.parserServices;
|
|
100
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
101
|
-
return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
105
|
-
CallExpression: callExpressionVisitor
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
};
|
package/rules/no-camel-case.d.ts
DELETED
package/rules/no-camel-case.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { isArgumentElement, isPluralElement, parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
-
import { extractMessages, getSettings } from "../util.js";
|
|
3
|
-
import { CORE_MESSAGES } from "../messages.js";
|
|
4
|
-
export const name = "no-camel-case";
|
|
5
|
-
const CAMEL_CASE_REGEX = /[A-Z]/;
|
|
6
|
-
function verifyAst(ast) {
|
|
7
|
-
const errors = [];
|
|
8
|
-
for (const el of ast) {
|
|
9
|
-
if (isArgumentElement(el)) {
|
|
10
|
-
if (CAMEL_CASE_REGEX.test(el.value)) {
|
|
11
|
-
errors.push({
|
|
12
|
-
messageId: "camelcase",
|
|
13
|
-
data: {}
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
if (isPluralElement(el)) {
|
|
19
|
-
if (CAMEL_CASE_REGEX.test(el.value)) {
|
|
20
|
-
errors.push({
|
|
21
|
-
messageId: "camelcase",
|
|
22
|
-
data: {}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
const { options } = el;
|
|
26
|
-
for (const selector of Object.keys(options)) {
|
|
27
|
-
errors.push(...verifyAst(options[selector].value));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return errors;
|
|
32
|
-
}
|
|
33
|
-
function checkNode(context, node) {
|
|
34
|
-
const settings = getSettings(context);
|
|
35
|
-
const msgs = extractMessages(node, settings);
|
|
36
|
-
for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
|
|
37
|
-
if (!defaultMessage || !messageNode) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
let ast;
|
|
41
|
-
try {
|
|
42
|
-
ast = parse(defaultMessage, { ignoreTag: settings.ignoreTag });
|
|
43
|
-
} catch (e) {
|
|
44
|
-
context.report({
|
|
45
|
-
node: messageNode,
|
|
46
|
-
messageId: "parseError",
|
|
47
|
-
data: { error: e.message }
|
|
48
|
-
});
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const errors = verifyAst(ast);
|
|
52
|
-
for (const error of errors) {
|
|
53
|
-
context.report({
|
|
54
|
-
node: messageNode,
|
|
55
|
-
...error
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
export const rule = {
|
|
61
|
-
meta: {
|
|
62
|
-
type: "problem",
|
|
63
|
-
docs: {
|
|
64
|
-
description: "Disallow camel case placeholders in message",
|
|
65
|
-
url: "https://formatjs.github.io/docs/tooling/linter#no-camel-case"
|
|
66
|
-
},
|
|
67
|
-
fixable: "code",
|
|
68
|
-
schema: [],
|
|
69
|
-
messages: {
|
|
70
|
-
...CORE_MESSAGES,
|
|
71
|
-
camelcase: "Camel case arguments are not allowed"
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
create(context) {
|
|
75
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
76
|
-
const parserServices = context.sourceCode.parserServices;
|
|
77
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
78
|
-
return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
82
|
-
CallExpression: callExpressionVisitor
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
};
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { TYPE, parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
-
import { extractMessages, getSettings } from "../util.js";
|
|
3
|
-
import { CORE_MESSAGES } from "../messages.js";
|
|
4
|
-
function calculateComplexity(ast) {
|
|
5
|
-
// Dynamic programming: define a complexity function f, where:
|
|
6
|
-
// f(plural | select) = sum(f(option) for each option) * f(next element),
|
|
7
|
-
// f(tag) = f(first element of children) * f(next element),
|
|
8
|
-
// f(other) = f(next element),
|
|
9
|
-
// f(out of bound) = 1.
|
|
10
|
-
const complexityByNode = new Map();
|
|
11
|
-
return _calculate(ast, 0);
|
|
12
|
-
function _calculate(ast, index) {
|
|
13
|
-
const element = ast[index];
|
|
14
|
-
if (!element) {
|
|
15
|
-
return 1;
|
|
16
|
-
}
|
|
17
|
-
const cachedComplexity = complexityByNode.get(element);
|
|
18
|
-
if (cachedComplexity !== undefined) {
|
|
19
|
-
return cachedComplexity;
|
|
20
|
-
}
|
|
21
|
-
let complexity;
|
|
22
|
-
switch (element.type) {
|
|
23
|
-
case TYPE.plural:
|
|
24
|
-
case TYPE.select: {
|
|
25
|
-
let sumOfOptions = 0;
|
|
26
|
-
for (const { value } of Object.values(element.options)) {
|
|
27
|
-
sumOfOptions += _calculate(value, 0);
|
|
28
|
-
}
|
|
29
|
-
complexity = sumOfOptions * _calculate(ast, index + 1);
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
case TYPE.tag:
|
|
33
|
-
complexity = _calculate(element.children, 0) * _calculate(ast, index + 1);
|
|
34
|
-
break;
|
|
35
|
-
default:
|
|
36
|
-
complexity = _calculate(ast, index + 1);
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
complexityByNode.set(element, complexity);
|
|
40
|
-
return complexity;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function checkNode(context, node) {
|
|
44
|
-
const settings = getSettings(context);
|
|
45
|
-
const msgs = extractMessages(node, settings);
|
|
46
|
-
if (!msgs.length) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const config = {
|
|
50
|
-
limit: 20,
|
|
51
|
-
...context.options[0]
|
|
52
|
-
};
|
|
53
|
-
for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
|
|
54
|
-
if (!defaultMessage || !messageNode) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
let ast;
|
|
58
|
-
try {
|
|
59
|
-
ast = parse(defaultMessage, { ignoreTag: settings.ignoreTag });
|
|
60
|
-
} catch (e) {
|
|
61
|
-
context.report({
|
|
62
|
-
node: messageNode,
|
|
63
|
-
messageId: "parseError",
|
|
64
|
-
data: { error: e instanceof Error ? e.message : String(e) }
|
|
65
|
-
});
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const complexity = calculateComplexity(ast);
|
|
69
|
-
if (complexity > config.limit) {
|
|
70
|
-
context.report({
|
|
71
|
-
node: messageNode,
|
|
72
|
-
messageId: "tooComplex",
|
|
73
|
-
data: {
|
|
74
|
-
complexity,
|
|
75
|
-
limit: config.limit
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
export const name = "no-complex-selectors";
|
|
82
|
-
export const rule = {
|
|
83
|
-
meta: {
|
|
84
|
-
type: "problem",
|
|
85
|
-
docs: {
|
|
86
|
-
description: `Make sure a sentence is not too complex.
|
|
87
|
-
Complexity is determined by how many strings are produced when we try to
|
|
88
|
-
flatten the sentence given its selectors. For example:
|
|
89
|
-
"I have {count, plural, one{a dog} other{many dogs}}"
|
|
90
|
-
has the complexity of 2 because flattening the plural selector
|
|
91
|
-
results in 2 sentences: "I have a dog" & "I have many dogs".
|
|
92
|
-
Default complexity limit is 20
|
|
93
|
-
(using Smartling as a reference: https://help.smartling.com/hc/en-us/articles/360008030994-ICU-MessageFormat)
|
|
94
|
-
`,
|
|
95
|
-
url: "https://formatjs.github.io/docs/tooling/linter#no-complex-selectors"
|
|
96
|
-
},
|
|
97
|
-
schema: [{
|
|
98
|
-
type: "object",
|
|
99
|
-
properties: { limit: { type: "number" } },
|
|
100
|
-
additionalProperties: false
|
|
101
|
-
}],
|
|
102
|
-
fixable: "code",
|
|
103
|
-
messages: {
|
|
104
|
-
...CORE_MESSAGES,
|
|
105
|
-
tooComplex: `Message complexity is too high ({{complexity}} vs limit at {{limit}})`
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
create(context) {
|
|
109
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
110
|
-
const parserServices = context.sourceCode.parserServices;
|
|
111
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
112
|
-
return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
116
|
-
CallExpression: callExpressionVisitor
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
};
|
package/rules/no-emoji.d.ts
DELETED
package/rules/no-emoji.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { extractEmojis, filterEmojis, hasEmoji, isValidEmojiVersion } from "../emoji-utils.js";
|
|
2
|
-
import { extractMessages, getSettings } from "../util.js";
|
|
3
|
-
export const name = "no-emoji";
|
|
4
|
-
function checkNode(context, node) {
|
|
5
|
-
const msgs = extractMessages(node, getSettings(context));
|
|
6
|
-
let versionAbove;
|
|
7
|
-
const [emojiConfig] = context.options;
|
|
8
|
-
if (emojiConfig?.versionAbove && isValidEmojiVersion(emojiConfig.versionAbove)) {
|
|
9
|
-
versionAbove = emojiConfig.versionAbove;
|
|
10
|
-
}
|
|
11
|
-
for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
|
|
12
|
-
if (!defaultMessage || !messageNode) {
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
if (hasEmoji(defaultMessage)) {
|
|
16
|
-
if (versionAbove) {
|
|
17
|
-
const filter = filterEmojis(versionAbove);
|
|
18
|
-
for (const emoji of extractEmojis(defaultMessage)) {
|
|
19
|
-
// Check if the emoji's version is allowed (filterEmojis returns true for allowed emojis)
|
|
20
|
-
if (!filter(emoji)) {
|
|
21
|
-
context.report({
|
|
22
|
-
node: messageNode,
|
|
23
|
-
messageId: "notAllowedAboveVersion",
|
|
24
|
-
data: {
|
|
25
|
-
versionAbove,
|
|
26
|
-
emoji
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
context.report({
|
|
33
|
-
node: messageNode,
|
|
34
|
-
messageId: "notAllowed"
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const versionAboveEnums = [
|
|
41
|
-
"0.6",
|
|
42
|
-
"0.7",
|
|
43
|
-
"1.0",
|
|
44
|
-
"2.0",
|
|
45
|
-
"3.0",
|
|
46
|
-
"4.0",
|
|
47
|
-
"5.0",
|
|
48
|
-
"11.0",
|
|
49
|
-
"12.0",
|
|
50
|
-
"13.0",
|
|
51
|
-
"14.0",
|
|
52
|
-
"15.0",
|
|
53
|
-
"16.0",
|
|
54
|
-
"17.0"
|
|
55
|
-
];
|
|
56
|
-
export const rule = {
|
|
57
|
-
meta: {
|
|
58
|
-
type: "problem",
|
|
59
|
-
docs: {
|
|
60
|
-
description: "Disallow emojis in message",
|
|
61
|
-
url: "https://formatjs.github.io/docs/tooling/linter#no-emoji"
|
|
62
|
-
},
|
|
63
|
-
fixable: "code",
|
|
64
|
-
schema: [{
|
|
65
|
-
type: "object",
|
|
66
|
-
properties: { versionAbove: {
|
|
67
|
-
type: "string",
|
|
68
|
-
enum: versionAboveEnums
|
|
69
|
-
} },
|
|
70
|
-
additionalProperties: false
|
|
71
|
-
}],
|
|
72
|
-
messages: {
|
|
73
|
-
notAllowed: "Emojis are not allowed",
|
|
74
|
-
notAllowedAboveVersion: "Emojis above version {{versionAbove}} are not allowed - Emoji: {{emoji}}"
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
create(context) {
|
|
78
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
79
|
-
const parserServices = context.sourceCode.parserServices;
|
|
80
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
81
|
-
return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
85
|
-
CallExpression: callExpressionVisitor
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
};
|
package/rules/no-id.d.ts
DELETED
package/rules/no-id.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { extractMessages, getSettings } from "../util.js";
|
|
2
|
-
function isComment(token) {
|
|
3
|
-
return !!token && (token.type === "Block" || token.type === "Line");
|
|
4
|
-
}
|
|
5
|
-
export const name = "no-id";
|
|
6
|
-
function checkNode(context, node) {
|
|
7
|
-
const msgs = extractMessages(node, getSettings(context));
|
|
8
|
-
for (const [{ idPropNode }] of msgs) {
|
|
9
|
-
if (idPropNode) {
|
|
10
|
-
context.report({
|
|
11
|
-
node: idPropNode,
|
|
12
|
-
messageId: "noId",
|
|
13
|
-
fix(fixer) {
|
|
14
|
-
const src = context.sourceCode;
|
|
15
|
-
const token = src.getTokenAfter(idPropNode);
|
|
16
|
-
const fixes = [fixer.remove(idPropNode)];
|
|
17
|
-
if (token && !isComment(token) && token?.value === ",") {
|
|
18
|
-
fixes.push(fixer.remove(token));
|
|
19
|
-
}
|
|
20
|
-
return fixes;
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
export const rule = {
|
|
27
|
-
meta: {
|
|
28
|
-
type: "problem",
|
|
29
|
-
docs: {
|
|
30
|
-
description: "Ban explicit ID from MessageDescriptor",
|
|
31
|
-
url: "https://formatjs.github.io/docs/tooling/linter#no-id"
|
|
32
|
-
},
|
|
33
|
-
fixable: "code",
|
|
34
|
-
schema: [],
|
|
35
|
-
messages: { noId: "Manual `id` are not allowed in message descriptor" }
|
|
36
|
-
},
|
|
37
|
-
create(context) {
|
|
38
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
39
|
-
const parserServices = context.sourceCode.parserServices;
|
|
40
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
41
|
-
return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
45
|
-
CallExpression: callExpressionVisitor
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
};
|
package/rules/no-invalid-icu.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
-
import { extractMessages, getSettings } from "../util.js";
|
|
3
|
-
import { CORE_MESSAGES } from "../messages.js";
|
|
4
|
-
export const name = "no-invalid-icu";
|
|
5
|
-
function checkNode(context, node) {
|
|
6
|
-
const settings = getSettings(context);
|
|
7
|
-
let msgs;
|
|
8
|
-
try {
|
|
9
|
-
msgs = extractMessages(node, settings);
|
|
10
|
-
} catch (e) {
|
|
11
|
-
// GH #5069: Handle errors from extractMessages (e.g., tagged templates with substitutions)
|
|
12
|
-
context.report({
|
|
13
|
-
node,
|
|
14
|
-
messageId: "parseError",
|
|
15
|
-
data: { error: e.message }
|
|
16
|
-
});
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (!msgs.length) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
|
|
23
|
-
if (!defaultMessage || !messageNode) {
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
parse(defaultMessage, { ignoreTag: settings.ignoreTag });
|
|
28
|
-
} catch (e) {
|
|
29
|
-
context.report({
|
|
30
|
-
node: messageNode,
|
|
31
|
-
messageId: "parseError",
|
|
32
|
-
data: { error: e.message }
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export const rule = {
|
|
38
|
-
meta: {
|
|
39
|
-
type: "problem",
|
|
40
|
-
docs: { description: `Make sure ICU messages are formatted correctly with no bad select statements, plurals, etc.` },
|
|
41
|
-
fixable: "code",
|
|
42
|
-
schema: [],
|
|
43
|
-
messages: CORE_MESSAGES
|
|
44
|
-
},
|
|
45
|
-
create(context) {
|
|
46
|
-
const callExpressionVisitor = (node) => checkNode(context, node);
|
|
47
|
-
const parserServices = context.sourceCode.parserServices;
|
|
48
|
-
if (parserServices?.defineTemplateBodyVisitor) {
|
|
49
|
-
return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
JSXOpeningElement: (node) => checkNode(context, node),
|
|
53
|
-
CallExpression: callExpressionVisitor
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
};
|