@docusaurus/eslint-plugin 0.0.0-4933 → 0.0.0-4940
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/index.d.ts +28 -0
- package/lib/index.js +16 -16
- package/lib/rules/index.d.ts +13 -0
- package/lib/rules/index.js +15 -0
- package/lib/rules/no-untranslated-text.d.ts +13 -0
- package/lib/rules/no-untranslated-text.js +48 -61
- package/lib/rules/string-literal-i18n-messages.d.ts +9 -0
- package/lib/rules/string-literal-i18n-messages.js +49 -54
- package/lib/util.d.ts +15 -0
- package/lib/util.js +35 -136
- package/package.json +7 -3
- package/src/index.ts +25 -0
- package/src/rules/index.ts +14 -0
- package/src/rules/no-untranslated-text.ts +79 -0
- package/src/rules/string-literal-i18n-messages.ts +76 -0
- package/src/util.ts +67 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: {
|
|
8
|
+
rules: {
|
|
9
|
+
'no-untranslated-text': import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"translateChildren", [{
|
|
10
|
+
ignoredStrings: string[];
|
|
11
|
+
}], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
12
|
+
'string-literal-i18n-messages': import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"translateChildren" | "translateArg", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
13
|
+
};
|
|
14
|
+
configs: {
|
|
15
|
+
recommended: {
|
|
16
|
+
rules: {
|
|
17
|
+
'@docusaurus/string-literal-i18n-messages': string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
all: {
|
|
21
|
+
rules: {
|
|
22
|
+
'@docusaurus/string-literal-i18n-messages': string;
|
|
23
|
+
'@docusaurus/no-untranslated-text': string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export = _default;
|
package/lib/index.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
4
|
*
|
|
4
5
|
* This source code is licensed under the MIT license found in the
|
|
5
6
|
* LICENSE file in the root directory of this source tree.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const tslib_1 = require("tslib");
|
|
9
|
+
const rules_1 = tslib_1.__importDefault(require("./rules"));
|
|
10
10
|
module.exports = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
rules: rules_1.default,
|
|
12
|
+
configs: {
|
|
13
|
+
recommended: {
|
|
14
|
+
rules: {
|
|
15
|
+
'@docusaurus/string-literal-i18n-messages': 'error',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
all: {
|
|
19
|
+
rules: {
|
|
20
|
+
'@docusaurus/string-literal-i18n-messages': 'error',
|
|
21
|
+
'@docusaurus/no-untranslated-text': 'warn',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
17
24
|
},
|
|
18
|
-
all: {
|
|
19
|
-
rules: {
|
|
20
|
-
'@docusaurus/string-literal-i18n-messages': 'error',
|
|
21
|
-
'@docusaurus/no-untranslated-text': 'warn',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
25
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: {
|
|
8
|
+
'no-untranslated-text': import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"translateChildren", [{
|
|
9
|
+
ignoredStrings: string[];
|
|
10
|
+
}], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
11
|
+
'string-literal-i18n-messages': import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"translateChildren" | "translateArg", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
const tslib_1 = require("tslib");
|
|
10
|
+
const no_untranslated_text_1 = tslib_1.__importDefault(require("./no-untranslated-text"));
|
|
11
|
+
const string_literal_i18n_messages_1 = tslib_1.__importDefault(require("./string-literal-i18n-messages"));
|
|
12
|
+
exports.default = {
|
|
13
|
+
'no-untranslated-text': no_untranslated_text_1.default,
|
|
14
|
+
'string-literal-i18n-messages': string_literal_i18n_messages_1.default,
|
|
15
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
declare type Options = [
|
|
8
|
+
{
|
|
9
|
+
ignoredStrings: string[];
|
|
10
|
+
}
|
|
11
|
+
];
|
|
12
|
+
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"translateChildren", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
13
|
+
export default _default;
|
|
@@ -1,71 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
4
|
*
|
|
4
5
|
* This source code is licensed under the MIT license found in the
|
|
5
6
|
* LICENSE file in the root directory of this source tree.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
const util_1 = require("../util");
|
|
10
|
+
exports.default = (0, util_1.createRule)({
|
|
11
|
+
name: 'no-untranslated-text',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'enforce text labels in JSX to be wrapped by translate calls',
|
|
16
|
+
recommended: false,
|
|
17
|
+
},
|
|
18
|
+
schema: [
|
|
19
|
+
{
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
ignoredStrings: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
messages: {
|
|
30
|
+
translateChildren: 'All text labels in JSX should be wrapped by translate calls',
|
|
31
|
+
},
|
|
21
32
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
properties: {
|
|
26
|
-
ignoreStrings: {
|
|
27
|
-
type: 'array',
|
|
28
|
-
},
|
|
33
|
+
defaultOptions: [
|
|
34
|
+
{
|
|
35
|
+
ignoredStrings: [],
|
|
29
36
|
},
|
|
30
|
-
additionalProperties: false,
|
|
31
|
-
},
|
|
32
37
|
],
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
create(context, [options]) {
|
|
39
|
+
const { ignoredStrings } = options;
|
|
40
|
+
return {
|
|
41
|
+
JSXElement(node) {
|
|
42
|
+
if (node.openingElement.selfClosing ||
|
|
43
|
+
node.openingElement.name.name ===
|
|
44
|
+
'Translate') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (node.children.some((child) => (0, util_1.isTextLabelChild)(child, { ignoredStrings }))) {
|
|
48
|
+
context.report({ node, messageId: 'translateChildren' });
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
JSXFragment(node) {
|
|
52
|
+
if (node.children.some((child) => (0, util_1.isTextLabelChild)(child, { ignoredStrings }))) {
|
|
53
|
+
context.report({ node, messageId: 'translateChildren' });
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
36
57
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
create(context) {
|
|
40
|
-
const stringsToIgnore = context.options[0]?.ignoreStrings ?? [];
|
|
41
|
-
|
|
42
|
-
const isParentTranslate = ({child, isParentFragment}) =>
|
|
43
|
-
!isParentFragment &&
|
|
44
|
-
child.parent.openingElement.name.name === 'Translate';
|
|
45
|
-
|
|
46
|
-
const isChildValid = ({child, isParentFragment}) => {
|
|
47
|
-
if (!isTextLabelChild({child, ignoreWhitespace: true, stringsToIgnore})) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
return isParentTranslate({child, isParentFragment});
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const isNodeValid = ({node, isFragment = false} = {}) =>
|
|
54
|
-
node.children.every((child) =>
|
|
55
|
-
isChildValid({child, isParentFragment: isFragment}),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
'JSXElement[openingElement.selfClosing=false]': (node) => {
|
|
60
|
-
if (!isNodeValid({node})) {
|
|
61
|
-
report(context, node, 'translateChildren');
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
'JSXFragment[openingFragment]': (node) => {
|
|
65
|
-
if (!isNodeValid({node, isFragment: true})) {
|
|
66
|
-
report(context, node, 'translateChildren');
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
};
|
|
58
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
declare type MessageIds = 'translateChildren' | 'translateArg';
|
|
8
|
+
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
9
|
+
export default _default;
|
|
@@ -1,62 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
4
|
*
|
|
4
5
|
* This source code is licensed under the MIT license found in the
|
|
5
6
|
* LICENSE file in the root directory of this source tree.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
category: 'Possible Problems',
|
|
23
|
-
url: 'https://docusaurus.io/docs/api/misc/@docusaurus/eslint-plugin/string-literal-i18n-messages',
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
const util_1 = require("../util");
|
|
10
|
+
exports.default = (0, util_1.createRule)({
|
|
11
|
+
name: 'string-literal-i18n-messages',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'problem',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'enforce translate APIs to be called on plain text labels',
|
|
16
|
+
recommended: 'error',
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
translateChildren: '<Translate> children must be hardcoded strings. You can have in-string dynamic placeholders using the values prop.',
|
|
21
|
+
translateArg: 'translation message must be a hardcoded string. You can have in-string dynamic placeholders using the values argument.',
|
|
22
|
+
},
|
|
24
23
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
JSXElement(node) {
|
|
28
|
+
if (node.openingElement.name.name !==
|
|
29
|
+
'Translate') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (node.children.some((child) => !(0, util_1.isTextLabelChild)(child))) {
|
|
33
|
+
context.report({ node, messageId: 'translateChildren' });
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
CallExpression(node) {
|
|
37
|
+
if (node.callee.type !== 'Identifier' ||
|
|
38
|
+
node.callee.name !== 'translate') {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const param = node.arguments[0];
|
|
42
|
+
if (!param || param.type !== 'ObjectExpression') {
|
|
43
|
+
context.report({ node, messageId: 'translateArg' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const messageProperty = param.properties.find((property) => property.type === 'Property' &&
|
|
47
|
+
property.key.name === 'message');
|
|
48
|
+
if (!messageProperty) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!(0, util_1.isStringWithoutExpressions)(messageProperty.value)) {
|
|
52
|
+
context.report({ node, messageId: 'translateArg' });
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
31
56
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
create(context) {
|
|
35
|
-
const isNodeValid = (node) =>
|
|
36
|
-
node.children.every((child) => isTextLabelChild({child}));
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
"JSXElement[openingElement.name.name='Translate']": (node) => {
|
|
40
|
-
if (!isNodeValid(node)) {
|
|
41
|
-
report(context, node, 'translateChildren');
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"CallExpression > Identifier[name='translate']": (node) => {
|
|
45
|
-
const messageProperty = node.parent.arguments[0].properties.find(
|
|
46
|
-
(property) => property.key.name === 'message',
|
|
47
|
-
);
|
|
48
|
-
if (!messageProperty) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
!isStringWithoutExpressions({
|
|
54
|
-
text: messageProperty.value,
|
|
55
|
-
})
|
|
56
|
-
) {
|
|
57
|
-
report(context, node, 'translateArg');
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
},
|
|
62
|
-
};
|
|
57
|
+
});
|
package/lib/util.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
8
|
+
import type { TSESTree } from '@typescript-eslint/types/dist/ts-estree';
|
|
9
|
+
declare type CheckTranslateChildOptions = {
|
|
10
|
+
ignoredStrings?: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function isStringWithoutExpressions(text: TSESTree.Node, options?: CheckTranslateChildOptions): boolean;
|
|
13
|
+
export declare function isTextLabelChild(child: TSESTree.JSXChild, options?: CheckTranslateChildOptions): boolean;
|
|
14
|
+
export declare const createRule: <TOptions extends readonly unknown[], TMessageIds extends string, TRuleListener extends import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener = import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>({ name, meta, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<TOptions, TMessageIds, TRuleListener>>) => import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<TMessageIds, TOptions, TRuleListener>;
|
|
15
|
+
export {};
|
package/lib/util.js
CHANGED
|
@@ -1,145 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
4
|
*
|
|
4
5
|
* This source code is licensed under the MIT license found in the
|
|
5
6
|
* LICENSE file in the root directory of this source tree.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createRule = exports.isTextLabelChild = exports.isStringWithoutExpressions = void 0;
|
|
10
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
11
|
+
const isMadeOfIgnoredStrings = (text, ignoredStrings) => text
|
|
10
12
|
.trim()
|
|
11
13
|
.split(/\s+/)
|
|
12
|
-
.every((string) =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
!(ignoreWhitespace && isWhitespace(text)) &&
|
|
19
|
-
!isMadeOfIgnoredStrings({
|
|
20
|
-
text,
|
|
21
|
-
stringsToIgnore,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const isStringWithoutExpressions = ({
|
|
25
|
-
text,
|
|
26
|
-
ignoreWhitespace = false,
|
|
27
|
-
stringsToIgnore = [],
|
|
28
|
-
} = {}) => {
|
|
29
|
-
switch (text.type) {
|
|
30
|
-
case 'Literal':
|
|
31
|
-
return isTextValid({text: text.value, ignoreWhitespace, stringsToIgnore});
|
|
32
|
-
case 'TemplateLiteral':
|
|
33
|
-
return (
|
|
34
|
-
!text.expressions.length &&
|
|
35
|
-
isTextValid({
|
|
36
|
-
text: text.quasis[0].value.raw,
|
|
37
|
-
ignoreWhitespace,
|
|
38
|
-
stringsToIgnore,
|
|
39
|
-
})
|
|
40
|
-
);
|
|
41
|
-
default:
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const isTextLabelChild = ({
|
|
47
|
-
child,
|
|
48
|
-
ignoreWhitespace = false,
|
|
49
|
-
stringsToIgnore = [],
|
|
50
|
-
} = {}) => {
|
|
51
|
-
switch (child.type) {
|
|
52
|
-
case 'JSXText':
|
|
53
|
-
return isTextValid({
|
|
54
|
-
text: child.value,
|
|
55
|
-
ignoreWhitespace,
|
|
56
|
-
stringsToIgnore,
|
|
57
|
-
});
|
|
58
|
-
case 'JSXExpressionContainer':
|
|
59
|
-
return isStringWithoutExpressions({
|
|
60
|
-
text: child.expression,
|
|
61
|
-
ignoreWhitespace,
|
|
62
|
-
stringsToIgnore,
|
|
63
|
-
});
|
|
64
|
-
default:
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const report = (context, node, messageId) => {
|
|
70
|
-
context.report({
|
|
71
|
-
node,
|
|
72
|
-
messageId,
|
|
73
|
-
});
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const getCommonValidTests = () => [
|
|
77
|
-
{
|
|
78
|
-
code: '<Translate>text</Translate>',
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
code: '<Translate> text </Translate>',
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
code: '<Translate>"text"</Translate>',
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
code: "<Translate>'text'</Translate>",
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
code: '<Translate>`text`</Translate>',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
code: '<Translate>{"text"}</Translate>',
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
code: "<Translate>{'text'}</Translate>",
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
code: '<Translate>{`text`}</Translate>',
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
code: '<Component>{text}</Component>',
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
code: '<Component> {text} </Component>',
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
code: 'translate({message: `My page meta title`})',
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
code: `<Translate
|
|
112
|
-
id="homepage.title"
|
|
113
|
-
description="The homepage welcome message">
|
|
114
|
-
Welcome to my website
|
|
115
|
-
</Translate>`,
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
code: `<Translate
|
|
119
|
-
values={{firstName: 'Sébastien'}}>
|
|
120
|
-
{'Welcome, {firstName}! How are you?'}
|
|
121
|
-
</Translate>`,
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
code: `<Translate>{'This'} is {\`valid\`}</Translate>`,
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
code: "translate({message: 'My page meta title'})",
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
code: "translate({message: 'The logo of site {siteName}'}, {siteName: 'Docusaurus'})",
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
code: 'translate({otherProp: metaTitle})',
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
code: 'translate({otherProp: `My page meta title`})',
|
|
137
|
-
},
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
module.exports = {
|
|
141
|
-
isTextLabelChild,
|
|
142
|
-
report,
|
|
143
|
-
getCommonValidTests,
|
|
144
|
-
isStringWithoutExpressions,
|
|
14
|
+
.every((string) => !string || ignoredStrings.includes(string));
|
|
15
|
+
const isValidTranslationLabel = (text, { ignoredStrings } = {}) => {
|
|
16
|
+
if (!ignoredStrings) {
|
|
17
|
+
return typeof text === 'string';
|
|
18
|
+
}
|
|
19
|
+
return (typeof text === 'string' && !isMadeOfIgnoredStrings(text, ignoredStrings));
|
|
145
20
|
};
|
|
21
|
+
function isStringWithoutExpressions(text, options) {
|
|
22
|
+
switch (text.type) {
|
|
23
|
+
case 'Literal':
|
|
24
|
+
return isValidTranslationLabel(text.value, options);
|
|
25
|
+
case 'TemplateLiteral':
|
|
26
|
+
return (text.expressions.length === 0 &&
|
|
27
|
+
isValidTranslationLabel(text.quasis[0].value.raw, options));
|
|
28
|
+
default:
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.isStringWithoutExpressions = isStringWithoutExpressions;
|
|
33
|
+
function isTextLabelChild(child, options) {
|
|
34
|
+
switch (child.type) {
|
|
35
|
+
case 'JSXText':
|
|
36
|
+
return isValidTranslationLabel(child.value, options);
|
|
37
|
+
case 'JSXExpressionContainer':
|
|
38
|
+
return isStringWithoutExpressions(child.expression, options);
|
|
39
|
+
default:
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.isTextLabelChild = isTextLabelChild;
|
|
44
|
+
exports.createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://docusaurus.io/docs/api/misc/@docusaurus/eslint-plugin/${name}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/eslint-plugin",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-4940",
|
|
4
4
|
"description": "ESLint plugin to enforce best Docusaurus practices.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc"
|
|
22
|
+
},
|
|
20
23
|
"dependencies": {
|
|
21
|
-
"
|
|
24
|
+
"@typescript-eslint/utils": "^5.21.0",
|
|
25
|
+
"tslib": "^2.4.0"
|
|
22
26
|
},
|
|
23
27
|
"devDependencies": {
|
|
24
28
|
"eslint-plugin-eslint-plugin": "^4.1.0"
|
|
@@ -29,5 +33,5 @@
|
|
|
29
33
|
"engines": {
|
|
30
34
|
"node": ">=14"
|
|
31
35
|
},
|
|
32
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "e2752f0bd8c9da9eff67162cb5f64dc30194cbf9"
|
|
33
37
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import rules from './rules';
|
|
9
|
+
|
|
10
|
+
export = {
|
|
11
|
+
rules,
|
|
12
|
+
configs: {
|
|
13
|
+
recommended: {
|
|
14
|
+
rules: {
|
|
15
|
+
'@docusaurus/string-literal-i18n-messages': 'error',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
all: {
|
|
19
|
+
rules: {
|
|
20
|
+
'@docusaurus/string-literal-i18n-messages': 'error',
|
|
21
|
+
'@docusaurus/no-untranslated-text': 'warn',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import noUntranslatedText from './no-untranslated-text';
|
|
9
|
+
import stringLiteralI18nMessages from './string-literal-i18n-messages';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
'no-untranslated-text': noUntranslatedText,
|
|
13
|
+
'string-literal-i18n-messages': stringLiteralI18nMessages,
|
|
14
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {isTextLabelChild, createRule} from '../util';
|
|
9
|
+
import type {TSESTree} from '@typescript-eslint/types/dist/ts-estree';
|
|
10
|
+
|
|
11
|
+
type Options = [
|
|
12
|
+
{
|
|
13
|
+
ignoredStrings: string[];
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
type MessageIds = 'translateChildren';
|
|
17
|
+
|
|
18
|
+
export default createRule<Options, MessageIds>({
|
|
19
|
+
name: 'no-untranslated-text',
|
|
20
|
+
meta: {
|
|
21
|
+
type: 'suggestion',
|
|
22
|
+
docs: {
|
|
23
|
+
description:
|
|
24
|
+
'enforce text labels in JSX to be wrapped by translate calls',
|
|
25
|
+
recommended: false,
|
|
26
|
+
},
|
|
27
|
+
schema: [
|
|
28
|
+
{
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
ignoredStrings: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
messages: {
|
|
39
|
+
translateChildren:
|
|
40
|
+
'All text labels in JSX should be wrapped by translate calls',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
defaultOptions: [
|
|
44
|
+
{
|
|
45
|
+
ignoredStrings: [],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
create(context, [options]) {
|
|
50
|
+
const {ignoredStrings} = options;
|
|
51
|
+
return {
|
|
52
|
+
JSXElement(node) {
|
|
53
|
+
if (
|
|
54
|
+
node.openingElement.selfClosing ||
|
|
55
|
+
(node.openingElement.name as TSESTree.JSXIdentifier).name ===
|
|
56
|
+
'Translate'
|
|
57
|
+
) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
node.children.some((child) =>
|
|
62
|
+
isTextLabelChild(child, {ignoredStrings}),
|
|
63
|
+
)
|
|
64
|
+
) {
|
|
65
|
+
context.report({node, messageId: 'translateChildren'});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
JSXFragment(node) {
|
|
69
|
+
if (
|
|
70
|
+
node.children.some((child) =>
|
|
71
|
+
isTextLabelChild(child, {ignoredStrings}),
|
|
72
|
+
)
|
|
73
|
+
) {
|
|
74
|
+
context.report({node, messageId: 'translateChildren'});
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isTextLabelChild,
|
|
10
|
+
isStringWithoutExpressions,
|
|
11
|
+
createRule,
|
|
12
|
+
} from '../util';
|
|
13
|
+
import type {TSESTree} from '@typescript-eslint/types/dist/ts-estree';
|
|
14
|
+
|
|
15
|
+
type Options = [];
|
|
16
|
+
type MessageIds = 'translateChildren' | 'translateArg';
|
|
17
|
+
|
|
18
|
+
export default createRule<Options, MessageIds>({
|
|
19
|
+
name: 'string-literal-i18n-messages',
|
|
20
|
+
meta: {
|
|
21
|
+
type: 'problem',
|
|
22
|
+
docs: {
|
|
23
|
+
description: 'enforce translate APIs to be called on plain text labels',
|
|
24
|
+
recommended: 'error',
|
|
25
|
+
},
|
|
26
|
+
schema: [],
|
|
27
|
+
messages: {
|
|
28
|
+
translateChildren:
|
|
29
|
+
'<Translate> children must be hardcoded strings. You can have in-string dynamic placeholders using the values prop.',
|
|
30
|
+
translateArg:
|
|
31
|
+
'translation message must be a hardcoded string. You can have in-string dynamic placeholders using the values argument.',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultOptions: [],
|
|
35
|
+
|
|
36
|
+
create(context) {
|
|
37
|
+
return {
|
|
38
|
+
JSXElement(node) {
|
|
39
|
+
if (
|
|
40
|
+
(node.openingElement.name as TSESTree.JSXIdentifier).name !==
|
|
41
|
+
'Translate'
|
|
42
|
+
) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (node.children.some((child) => !isTextLabelChild(child))) {
|
|
46
|
+
context.report({node, messageId: 'translateChildren'});
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
CallExpression(node) {
|
|
50
|
+
if (
|
|
51
|
+
node.callee.type !== 'Identifier' ||
|
|
52
|
+
node.callee.name !== 'translate'
|
|
53
|
+
) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const param = node.arguments[0];
|
|
57
|
+
if (!param || param.type !== 'ObjectExpression') {
|
|
58
|
+
context.report({node, messageId: 'translateArg'});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const messageProperty = param.properties.find(
|
|
62
|
+
(property): property is TSESTree.Property =>
|
|
63
|
+
property.type === 'Property' &&
|
|
64
|
+
(property.key as TSESTree.Identifier).name === 'message',
|
|
65
|
+
);
|
|
66
|
+
if (!messageProperty) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!isStringWithoutExpressions(messageProperty.value)) {
|
|
71
|
+
context.report({node, messageId: 'translateArg'});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {ESLintUtils} from '@typescript-eslint/utils';
|
|
9
|
+
import type {TSESTree} from '@typescript-eslint/types/dist/ts-estree';
|
|
10
|
+
|
|
11
|
+
type CheckTranslateChildOptions = {
|
|
12
|
+
ignoredStrings?: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const isMadeOfIgnoredStrings = (text: string, ignoredStrings: string[]) =>
|
|
16
|
+
text
|
|
17
|
+
.trim()
|
|
18
|
+
.split(/\s+/)
|
|
19
|
+
.every((string) => !string || ignoredStrings.includes(string));
|
|
20
|
+
|
|
21
|
+
const isValidTranslationLabel = (
|
|
22
|
+
text: unknown,
|
|
23
|
+
{ignoredStrings}: CheckTranslateChildOptions = {},
|
|
24
|
+
) => {
|
|
25
|
+
if (!ignoredStrings) {
|
|
26
|
+
return typeof text === 'string';
|
|
27
|
+
}
|
|
28
|
+
return (
|
|
29
|
+
typeof text === 'string' && !isMadeOfIgnoredStrings(text, ignoredStrings)
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function isStringWithoutExpressions(
|
|
34
|
+
text: TSESTree.Node,
|
|
35
|
+
options?: CheckTranslateChildOptions,
|
|
36
|
+
): boolean {
|
|
37
|
+
switch (text.type) {
|
|
38
|
+
case 'Literal':
|
|
39
|
+
return isValidTranslationLabel(text.value, options);
|
|
40
|
+
case 'TemplateLiteral':
|
|
41
|
+
return (
|
|
42
|
+
text.expressions.length === 0 &&
|
|
43
|
+
isValidTranslationLabel(text.quasis[0]!.value.raw, options)
|
|
44
|
+
);
|
|
45
|
+
default:
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isTextLabelChild(
|
|
51
|
+
child: TSESTree.JSXChild,
|
|
52
|
+
options?: CheckTranslateChildOptions,
|
|
53
|
+
): boolean {
|
|
54
|
+
switch (child.type) {
|
|
55
|
+
case 'JSXText':
|
|
56
|
+
return isValidTranslationLabel(child.value, options);
|
|
57
|
+
case 'JSXExpressionContainer':
|
|
58
|
+
return isStringWithoutExpressions(child.expression, options);
|
|
59
|
+
default:
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const createRule = ESLintUtils.RuleCreator(
|
|
65
|
+
(name) =>
|
|
66
|
+
`https://docusaurus.io/docs/api/misc/@docusaurus/eslint-plugin/${name}`,
|
|
67
|
+
);
|