@gitlab/eslint-plugin 9.4.0 → 11.0.0
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 +41 -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 -3
- package/gitlab_schema_apollo.graphql +30690 -0
- package/jest.config.js +6 -0
- package/lib/configs/i18n.js +2 -2
- package/lib/index.js +2 -3
- 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-emit-events-with-two-args.md +0 -38
- package/docs/rules/vue-require-string-literal-i18n-helpers.md +0 -34
- package/lib/rules/vue-emit-events-with-two-args.js +0 -54
package/jest.config.js
ADDED
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,13 @@ 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-
|
|
10
|
-
'vue-emit-events-with-two-args': require('./rules/vue-emit-events-with-two-args.js'),
|
|
9
|
+
'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
|
|
11
10
|
'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
|
|
12
11
|
'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
|
|
13
12
|
'vue-require-i18n-attribute-strings': require('./rules/vue-require-i18n-attribute-strings.js'),
|
|
14
13
|
'vue-require-i18n-strings': require('./rules/vue-require-i18n-strings.js'),
|
|
15
14
|
'vue-require-required-key': require('./rules/vue-require-required-key.js'),
|
|
16
|
-
'vue-require-
|
|
15
|
+
'vue-require-valid-i18n-helpers': require('./rules/vue-require-valid-i18n-helpers.js'),
|
|
17
16
|
'vue-slot-name-casing': require('./rules/vue-slot-name-casing.js'),
|
|
18
17
|
},
|
|
19
18
|
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": "11.0.0",
|
|
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,38 +0,0 @@
|
|
|
1
|
-
# @gitlab/vue-emit-events-with-two-args
|
|
2
|
-
|
|
3
|
-
Throw an error when emit is called with more than two arguments.
|
|
4
|
-
|
|
5
|
-
## Rule Details
|
|
6
|
-
|
|
7
|
-
It has been decided to use `mitt` as an event bus implementation and this library does not support argument list as the event arguments, [it expects two arguments only](https://github.com/developit/mitt#emit).
|
|
8
|
-
|
|
9
|
-
To pass an argument list, an object should be used as the second argument.
|
|
10
|
-
|
|
11
|
-
### Examples of **incorrect** code for this rule
|
|
12
|
-
|
|
13
|
-
`emit` method called with more than two arguments.
|
|
14
|
-
|
|
15
|
-
```javascript
|
|
16
|
-
vm.$emit('keyspressed', 'enter', '1');
|
|
17
|
-
vm.emit('keyspressed', 'enter', '1');
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### Examples of **correct** code for this rule
|
|
21
|
-
|
|
22
|
-
Pass an object as the second argument to `emit`.
|
|
23
|
-
|
|
24
|
-
```javascript
|
|
25
|
-
vm.$emit('keyspressed', { keys: ['enter', '1'] });
|
|
26
|
-
vm.emit('keyspressed', { keys: ['enter', '1'] });
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
`emit` method called with one argument.
|
|
30
|
-
|
|
31
|
-
```javascript
|
|
32
|
-
vm.$emit('focus');
|
|
33
|
-
vm.emit('focus');
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Options
|
|
37
|
-
|
|
38
|
-
Nothing
|
|
@@ -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
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// ------------------------------------------------------------------------------
|
|
2
|
-
// Requirements
|
|
3
|
-
// ------------------------------------------------------------------------------
|
|
4
|
-
const { DOCS_BASE_URL } = require('../constants');
|
|
5
|
-
const { getFunctionName } = require('../utils/rule-utils');
|
|
6
|
-
|
|
7
|
-
// ------------------------------------------------------------------------------
|
|
8
|
-
// Helpers
|
|
9
|
-
// ------------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
const EMIT_LIST = new Set(['$emit', 'emit']);
|
|
12
|
-
const ERROR_MESSAGE = `Expected emit to be called with no more than two arguments.`;
|
|
13
|
-
|
|
14
|
-
// ------------------------------------------------------------------------------
|
|
15
|
-
// Rule Definition
|
|
16
|
-
// ------------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
module.exports = {
|
|
19
|
-
meta: {
|
|
20
|
-
type: 'problem',
|
|
21
|
-
docs: {
|
|
22
|
-
description: `Throw an error when emit is called with more than two arguments.`,
|
|
23
|
-
url: DOCS_BASE_URL + '/vue-emit-events-with-two-args.md',
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
create(context) {
|
|
27
|
-
function report(node) {
|
|
28
|
-
context.report({
|
|
29
|
-
node,
|
|
30
|
-
message: ERROR_MESSAGE,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function isEmitCall(node) {
|
|
35
|
-
const functionName = getFunctionName(node);
|
|
36
|
-
|
|
37
|
-
return functionName && EMIT_LIST.has(functionName);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function checkFunctionArguments(callExpression) {
|
|
41
|
-
if (callExpression.arguments.length > 2) {
|
|
42
|
-
report(callExpression);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
CallExpression(node) {
|
|
48
|
-
if (isEmitCall(node)) {
|
|
49
|
-
checkFunctionArguments(node);
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
};
|