@gitlab/eslint-plugin 9.3.0 → 10.0.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/CHANGELOG.md +36 -0
- package/CONTRIBUTING.md +40 -0
- package/docs/development.md +15 -0
- package/docs/rules/require-valid-i18n-helpers.md +41 -0
- package/docs/rules/vue-require-valid-i18n-helpers.md +42 -0
- package/docs/rules.md +2 -2
- package/gitlab_schema_apollo.graphql +29450 -0
- package/jest.config.js +6 -0
- package/lib/configs/default.js +0 -1
- package/lib/configs/i18n.js +2 -2
- package/lib/index.js +2 -2
- package/lib/rules/require-i18n-strings.js +10 -11
- package/lib/rules/{require-string-literal-i18n-helpers.js → require-valid-i18n-helpers.js} +2 -3
- package/lib/rules/{vue-require-string-literal-i18n-helpers.js → vue-require-valid-i18n-helpers.js} +2 -3
- package/lib/utils/i18n-utils.js +48 -13
- package/lib/utils/rule-utils.js +25 -5
- package/package.json +3 -3
- package/docs/rules/require-string-literal-i18n-helpers.md +0 -26
- package/docs/rules/vue-require-string-literal-i18n-helpers.md +0 -34
package/jest.config.js
ADDED
package/lib/configs/default.js
CHANGED
package/lib/configs/i18n.js
CHANGED
|
@@ -4,7 +4,7 @@ module.exports = {
|
|
|
4
4
|
'@gitlab/require-i18n-strings': 'error',
|
|
5
5
|
'@gitlab/vue-require-i18n-attribute-strings': 'error',
|
|
6
6
|
'@gitlab/vue-require-i18n-strings': 'error',
|
|
7
|
-
'@gitlab/require-
|
|
8
|
-
'@gitlab/vue-require-
|
|
7
|
+
'@gitlab/require-valid-i18n-helpers': 'error',
|
|
8
|
+
'@gitlab/vue-require-valid-i18n-helpers': 'error',
|
|
9
9
|
},
|
|
10
10
|
};
|
package/lib/index.js
CHANGED
|
@@ -6,14 +6,14 @@ module.exports = {
|
|
|
6
6
|
'no-global-event-off': require('./rules/no-global-event-off.js'),
|
|
7
7
|
'no-runtime-template-compiler': require('./rules/no-runtime-template-compiler.js'),
|
|
8
8
|
'require-i18n-strings': require('./rules/require-i18n-strings.js'),
|
|
9
|
-
'require-
|
|
9
|
+
'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
|
|
10
10
|
'vue-emit-events-with-two-args': require('./rules/vue-emit-events-with-two-args.js'),
|
|
11
11
|
'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
|
|
12
12
|
'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
|
|
13
13
|
'vue-require-i18n-attribute-strings': require('./rules/vue-require-i18n-attribute-strings.js'),
|
|
14
14
|
'vue-require-i18n-strings': require('./rules/vue-require-i18n-strings.js'),
|
|
15
15
|
'vue-require-required-key': require('./rules/vue-require-required-key.js'),
|
|
16
|
-
'vue-require-
|
|
16
|
+
'vue-require-valid-i18n-helpers': require('./rules/vue-require-valid-i18n-helpers.js'),
|
|
17
17
|
'vue-slot-name-casing': require('./rules/vue-slot-name-casing.js'),
|
|
18
18
|
},
|
|
19
19
|
configs: {
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
+
// ------------------------------------------------------------------------------
|
|
8
|
+
// Requirements
|
|
9
|
+
// ------------------------------------------------------------------------------
|
|
10
|
+
const { isTemplateLiteral, getTemplateLiteralString } = require('../utils/rule-utils');
|
|
11
|
+
|
|
7
12
|
//------------------------------------------------------------------------------
|
|
8
13
|
// Rule Definition
|
|
9
14
|
//------------------------------------------------------------------------------
|
|
@@ -110,6 +115,10 @@ module.exports = {
|
|
|
110
115
|
);
|
|
111
116
|
}
|
|
112
117
|
|
|
118
|
+
function isMailto(str) {
|
|
119
|
+
return (str || '').startsWith('mailto:');
|
|
120
|
+
}
|
|
121
|
+
|
|
113
122
|
function isPlaceholder(value) {
|
|
114
123
|
return /^{?{\w+}}?$/.test(value);
|
|
115
124
|
}
|
|
@@ -248,6 +257,7 @@ module.exports = {
|
|
|
248
257
|
!isNumber(value) &&
|
|
249
258
|
!isPath(value) &&
|
|
250
259
|
!isUrl(value) &&
|
|
260
|
+
!isMailto(value) &&
|
|
251
261
|
!isHtml(value) &&
|
|
252
262
|
!isCssSelector(value) &&
|
|
253
263
|
!isCssValue(value) &&
|
|
@@ -269,21 +279,10 @@ module.exports = {
|
|
|
269
279
|
return node.type === 'Literal';
|
|
270
280
|
}
|
|
271
281
|
|
|
272
|
-
function isTemplateLiteral(node) {
|
|
273
|
-
return node.type === 'TemplateLiteral';
|
|
274
|
-
}
|
|
275
|
-
|
|
276
282
|
function stripVariables(value) {
|
|
277
283
|
return value ? value.replace(/(\%{[\w-]+})/g, '') : '';
|
|
278
284
|
}
|
|
279
285
|
|
|
280
|
-
function getTemplateLiteralString(node) {
|
|
281
|
-
return node.quasis
|
|
282
|
-
.filter((quasi) => quasi.type === 'TemplateElement')
|
|
283
|
-
.map((quasi) => quasi.value.raw)
|
|
284
|
-
.join('');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
286
|
function getCallExpressionString(node, callee) {
|
|
288
287
|
if (callee.name === 'sprintf') {
|
|
289
288
|
return stripVariables(node.value);
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Requirements
|
|
3
3
|
// ------------------------------------------------------------------------------
|
|
4
4
|
const { DOCS_BASE_URL } = require('../constants');
|
|
5
|
-
const { getFunctionName, isString } = require('../utils/rule-utils');
|
|
6
5
|
const { validateI18nHelperCallFactory } = require('../utils/i18n-utils');
|
|
7
6
|
|
|
8
7
|
// ------------------------------------------------------------------------------
|
|
@@ -13,9 +12,9 @@ module.exports = {
|
|
|
13
12
|
meta: {
|
|
14
13
|
type: 'error',
|
|
15
14
|
docs: {
|
|
16
|
-
description: '
|
|
15
|
+
description: 'Enforces valid usage of translation helpers in JavaScript.',
|
|
17
16
|
category: 'i18n',
|
|
18
|
-
url: DOCS_BASE_URL + '/require-
|
|
17
|
+
url: DOCS_BASE_URL + '/require-valid-i18n-helpers.md',
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
create(context) {
|
package/lib/rules/{vue-require-string-literal-i18n-helpers.js → vue-require-valid-i18n-helpers.js}
RENAMED
|
@@ -13,10 +13,9 @@ module.exports = {
|
|
|
13
13
|
meta: {
|
|
14
14
|
type: 'error',
|
|
15
15
|
docs: {
|
|
16
|
-
description:
|
|
17
|
-
'Prevents translation helpers from being called with non-string-literal arguments in Vue templates.',
|
|
16
|
+
description: 'Enforces valid usage of translation helpers in Vue templates.',
|
|
18
17
|
category: 'i18n',
|
|
19
|
-
url: DOCS_BASE_URL + '/vue-require-
|
|
18
|
+
url: DOCS_BASE_URL + '/vue-require-valid-i18n-helpers.md',
|
|
20
19
|
},
|
|
21
20
|
},
|
|
22
21
|
create(context) {
|
package/lib/utils/i18n-utils.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
getFunctionName,
|
|
3
|
+
isString,
|
|
4
|
+
isTemplateLiteral,
|
|
5
|
+
getTemplateLiteralString,
|
|
6
|
+
} = require('../utils/rule-utils');
|
|
2
7
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
8
|
+
const GETTEXT = '__';
|
|
9
|
+
const P_GETTEXT = 's__';
|
|
10
|
+
const N_GETTEXT = 'n__';
|
|
5
11
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
return functionName && I18N_HELPERS.has(functionName);
|
|
9
|
-
};
|
|
12
|
+
const ERROR_MESSAGE_STRING_LITERAL = 'Translation helpers must be called with a string literal.';
|
|
13
|
+
const ERROR_MESSAGE_MISSING_NAMESPACE = 's__ requires a translation namespace to be provided.';
|
|
10
14
|
|
|
11
|
-
const
|
|
12
|
-
if (!isI18nCall(node)) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
+
const mustBeCalledWithStringLiteralArguments = (context, node) => {
|
|
15
16
|
const report = (node) => {
|
|
16
17
|
context.report({
|
|
17
18
|
node,
|
|
18
|
-
message:
|
|
19
|
+
message: ERROR_MESSAGE_STRING_LITERAL,
|
|
19
20
|
});
|
|
20
21
|
};
|
|
21
22
|
|
|
@@ -28,6 +29,40 @@ const validateI18nHelperCallFactory = (context) => (node) => {
|
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
const mustHaveNamespace = (context, node) => {
|
|
33
|
+
if (node.arguments.length === 2) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [arg] = node.arguments;
|
|
38
|
+
const value = isTemplateLiteral(arg) ? getTemplateLiteralString(arg).trim() : arg.value;
|
|
39
|
+
if (!/^\w.+\|/.test(value)) {
|
|
40
|
+
context.report({
|
|
41
|
+
node,
|
|
42
|
+
message: ERROR_MESSAGE_MISSING_NAMESPACE,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const VALIDATORS = {
|
|
48
|
+
[GETTEXT]: [mustBeCalledWithStringLiteralArguments],
|
|
49
|
+
[P_GETTEXT]: [mustBeCalledWithStringLiteralArguments, mustHaveNamespace],
|
|
50
|
+
[N_GETTEXT]: [mustBeCalledWithStringLiteralArguments],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const validateI18nHelperCallFactory = (context) => (node) => {
|
|
54
|
+
const functionName = getFunctionName(node);
|
|
55
|
+
if (!Object.keys(VALIDATORS).includes(functionName)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
VALIDATORS[functionName].forEach((validator) => {
|
|
60
|
+
validator(context, node);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
31
64
|
module.exports = {
|
|
32
65
|
validateI18nHelperCallFactory,
|
|
33
|
-
|
|
66
|
+
ERROR_MESSAGE_STRING_LITERAL,
|
|
67
|
+
ERROR_MESSAGE_MISSING_NAMESPACE,
|
|
68
|
+
};
|
package/lib/utils/rule-utils.js
CHANGED
|
@@ -41,6 +41,29 @@ function isString(node) {
|
|
|
41
41
|
return [LITERAL, TEMPLATE_LITERAL].includes(node.type);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Checks whether a node is a template literal.
|
|
46
|
+
*
|
|
47
|
+
* @param {ASTNode} node The node to type check
|
|
48
|
+
* @returns {boolean} `true` if node is a template literal.
|
|
49
|
+
*/
|
|
50
|
+
function isTemplateLiteral(node) {
|
|
51
|
+
return node.type === TEMPLATE_LITERAL;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extracts the string out of a template literal node.
|
|
56
|
+
*
|
|
57
|
+
* @param {ASTNode} node The template literal node
|
|
58
|
+
* @returns {string} The extracted string.
|
|
59
|
+
*/
|
|
60
|
+
function getTemplateLiteralString(node) {
|
|
61
|
+
return node.quasis
|
|
62
|
+
.filter((quasi) => quasi.type === 'TemplateElement')
|
|
63
|
+
.map((quasi) => quasi.value.raw)
|
|
64
|
+
.join('');
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
/**
|
|
45
68
|
* Check that a node is a `VLiteral type`
|
|
46
69
|
* @param {ASTNode} node The node to type check
|
|
@@ -56,10 +79,6 @@ function isVLiteral(node) {
|
|
|
56
79
|
* @returns {array} In form [prefix, postfix] with the content to prepend and append to the string
|
|
57
80
|
*/
|
|
58
81
|
function selectFixFunction({ type, value }) {
|
|
59
|
-
// TODO: needs tests
|
|
60
|
-
// TODO: these are gitlab specific fixes...
|
|
61
|
-
// TODO: Need a config for the default externalization function to use
|
|
62
|
-
// TODO: probably need withExpressionWithQuotes....
|
|
63
82
|
const noQuotes = ['__(', ')'];
|
|
64
83
|
const withQuotes = ['__("', '")'];
|
|
65
84
|
const withExpression = ['{{ __(', ') }}'];
|
|
@@ -67,7 +86,6 @@ function selectFixFunction({ type, value }) {
|
|
|
67
86
|
|
|
68
87
|
const hasQuotes = isWrappedWithQuotes(value);
|
|
69
88
|
|
|
70
|
-
// TODO: double check this is still needed
|
|
71
89
|
if (type === NODE_TYPES.V_TEXT) {
|
|
72
90
|
return hasQuotes ? withExpression : withQuotesExpression;
|
|
73
91
|
} else {
|
|
@@ -172,6 +190,8 @@ module.exports = {
|
|
|
172
190
|
clearSplitLines,
|
|
173
191
|
closest,
|
|
174
192
|
isString,
|
|
193
|
+
isTemplateLiteral,
|
|
194
|
+
getTemplateLiteralString,
|
|
175
195
|
isVLiteral,
|
|
176
196
|
removeNewLines,
|
|
177
197
|
selectFixFunction,
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/eslint-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.2",
|
|
4
4
|
"description": "GitLab package for our custom eslint rules",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"createRule": "./scripts/createRule.js && yarn update",
|
|
8
8
|
"update": "./scripts/updateFiles.js && prettier --write lib/index.js",
|
|
9
9
|
"commit": "npx git-cz",
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "jest tests"
|
|
11
11
|
},
|
|
12
12
|
"repository": "https://gitlab.com/gitlab-org/frontend/eslint-plugin.git",
|
|
13
13
|
"keywords": [
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"eslint": "^7.27.0",
|
|
50
50
|
"glob": "^7.1.3",
|
|
51
|
-
"
|
|
51
|
+
"jest": "^26.6.3",
|
|
52
52
|
"prettier": "^2.2.1",
|
|
53
53
|
"pretty-quick": "^3.1.0"
|
|
54
54
|
},
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# @gitlab/require-string-literal-i18n-helpers
|
|
2
|
-
|
|
3
|
-
Prevents translation helpers from being called with non-string-literal arguments.
|
|
4
|
-
|
|
5
|
-
## Rule Details
|
|
6
|
-
|
|
7
|
-
### Examples of **incorrect** code for this rule
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
const label = __(LABEL);
|
|
11
|
-
const description = __(getDescription());
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
### Examples of **correct** code for this rule
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
const label = __('Some label');
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Options
|
|
21
|
-
|
|
22
|
-
Nothing
|
|
23
|
-
|
|
24
|
-
## Related rules
|
|
25
|
-
|
|
26
|
-
- TBA
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# @gitlab/vue-require-string-literal-i18n-helpers
|
|
2
|
-
|
|
3
|
-
Prevents translation helpers from being called with non-string-literal arguments in Vue templates.
|
|
4
|
-
|
|
5
|
-
## Rule Details
|
|
6
|
-
|
|
7
|
-
### Examples of **incorrect** code for this rule
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<template>
|
|
11
|
-
<div>
|
|
12
|
-
{{ __(LABEL) }}
|
|
13
|
-
{{ __(getDescription()) }}
|
|
14
|
-
</div>
|
|
15
|
-
</template>
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Examples of **correct** code for this rule
|
|
19
|
-
|
|
20
|
-
```html
|
|
21
|
-
<template>
|
|
22
|
-
<div>
|
|
23
|
-
{{ __('Some label') }}
|
|
24
|
-
</div>
|
|
25
|
-
</template>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Options
|
|
29
|
-
|
|
30
|
-
Nothing
|
|
31
|
-
|
|
32
|
-
## Related rules
|
|
33
|
-
|
|
34
|
-
- TBA
|