@gitlab/eslint-plugin 19.3.0 → 19.5.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 +15 -0
- package/docs/rules/require-valid-i18n-helpers.md +7 -0
- package/docs/rules/vtu-no-wrapper-vm.md +53 -0
- package/docs/rules/vue-require-valid-i18n-helpers.md +5 -0
- package/docs/rules.md +1 -0
- package/lib/index.js +1 -0
- package/lib/rules/vtu-no-wrapper-vm.js +111 -0
- package/lib/utils/i18n-utils.js +28 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# [19.5.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v19.4.0...v19.5.0) (2024-03-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **require-valid-i18n-helpers:** lint against string interpolation ([268d858](https://gitlab.com/gitlab-org/frontend/eslint-plugin/commit/268d858f8bd02cab06ea74f2c6844166489d0d89))
|
|
7
|
+
|
|
8
|
+
# [19.4.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v19.3.0...v19.4.0) (2024-01-10)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **vtu-no-wrapper-vm:** Improve reporting with $nextTick ([0cb92b8](https://gitlab.com/gitlab-org/frontend/eslint-plugin/commit/0cb92b870feeea8d889ceec68bb30145b584c823))
|
|
14
|
+
* **vtu-no-wrapper-vm:** Prevent saving off a reference to wrapper.vm ([96a59b0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/commit/96a59b066bed3cca5bc62dcfaeb7c26160eda92a))
|
|
15
|
+
|
|
1
16
|
# [19.3.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v19.2.0...v19.3.0) (2023-12-05)
|
|
2
17
|
|
|
3
18
|
|
|
@@ -5,6 +5,8 @@ following:
|
|
|
5
5
|
|
|
6
6
|
- All helpers should be called with string-literal arguments to ensure the strings are properly
|
|
7
7
|
collected by the `gettext:regenerate` script.
|
|
8
|
+
- The helpers' arguments should not use string interpolation as this also prevents the strings from
|
|
9
|
+
being collected.
|
|
8
10
|
- `__` should not be called with a namespace.
|
|
9
11
|
- `s__` requires a translation namespace to be provided.
|
|
10
12
|
- `n__` should use the same namespace, or no namespace at all, for both the singular and plural forms.
|
|
@@ -21,6 +23,11 @@ const description = __(getDescription());
|
|
|
21
23
|
// Extraneous namespace
|
|
22
24
|
const globalTranslation = __('Global|Translation');
|
|
23
25
|
|
|
26
|
+
// String interpolation
|
|
27
|
+
const withStringInterpolation1 = __(`String ${interpolation}`);
|
|
28
|
+
const withStringInterpolation2 = s__("SomeContext", `String ${interpolation}`);
|
|
29
|
+
const withStringInterpolation3 = n__("%d apple", `%d ${apples}`, appleCount);
|
|
30
|
+
|
|
24
31
|
// Missing translation namespace
|
|
25
32
|
const namespacedTranslation = s__('NamespacedTranslation');
|
|
26
33
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @gitlab/vtu-no-wrapper-vm
|
|
2
|
+
|
|
3
|
+
Prevent direct access to `vm` internals for `@vue/test-utils` wrappers.
|
|
4
|
+
This is helpful for writing maintainable Vue tests.
|
|
5
|
+
|
|
6
|
+
See https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html#what-and-how-to-test
|
|
7
|
+
|
|
8
|
+
## Rule Details
|
|
9
|
+
|
|
10
|
+
### Examples of **incorrect** code for this rule
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
it('calls spy when foo-ed', () => {
|
|
14
|
+
// !! Direct access to `vm` properties !!
|
|
15
|
+
wrapper.vm.onFoo();
|
|
16
|
+
|
|
17
|
+
expect(spy).toHaveBeenCalled();
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Examples of **correct** code for this rule
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
it('calls spy when foo-ed', () => {
|
|
25
|
+
wrapper.find('button').trigger('click');
|
|
26
|
+
|
|
27
|
+
expect(spy).toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Options
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"@gitlab/vtu-no-wrapper-vm": [
|
|
36
|
+
"error",
|
|
37
|
+
{
|
|
38
|
+
"allow": ["$emit"]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `allow` contains a list of allowable property names that can be directly
|
|
45
|
+
accessed under a vm.
|
|
46
|
+
|
|
47
|
+
## Related Rules
|
|
48
|
+
|
|
49
|
+
N/A.
|
|
50
|
+
|
|
51
|
+
## When Not To Use It
|
|
52
|
+
|
|
53
|
+
Don't use it if you don't want it.
|
|
@@ -13,6 +13,11 @@ This rule is like [require-valid-i18n-helpers](./require-valid-i18n-helpers.md)
|
|
|
13
13
|
{{ __(LABEL) }}
|
|
14
14
|
{{ __(getDescription()) }}
|
|
15
15
|
|
|
16
|
+
<!-- String interpolation -->
|
|
17
|
+
{{ __(`String ${interpolation}`) }}
|
|
18
|
+
{{ s__("SomeContext", `String ${interpolation}`) }}
|
|
19
|
+
{{ n__("%d apple", `%d ${apples}`, appleCount) }}
|
|
20
|
+
|
|
16
21
|
<!-- Missing translation namespace -->
|
|
17
22
|
{{ s__('NamespacedTranslation') }}
|
|
18
23
|
</div>
|
package/docs/rules.md
CHANGED
|
@@ -9,6 +9,7 @@ Available rules:
|
|
|
9
9
|
- [require-i18n-strings](./rules/require-i18n-strings.md): Detect a string which has been hard coded and requires externalization.
|
|
10
10
|
- [require-valid-i18n-helpers](./rules/require-valid-i18n-helpers.md): Enforces valid usage of translation helpers in JavaScript.
|
|
11
11
|
- [vtu-no-explicit-wrapper-destroy](./rules/vtu-no-explicit-wrapper-destroy.md): Prevents redundant destroy calls and null asignment
|
|
12
|
+
- [vtu-no-wrapper-vm](./rules/vtu-no-wrapper-vm.md): Prevent direct access to `vm` internals for `@vue/test-utils` wrappers.
|
|
12
13
|
- [vue-no-data-toggle](./rules/vue-no-data-toggle.md): Restrict the use of `data-toggle` bootstrap behaviors within Vue templates
|
|
13
14
|
- [vue-no-new-non-primitive-in-template](./rules/vue-no-new-non-primitive-in-template.md): Prevents non-primitive values from being declared in templates
|
|
14
15
|
- [vue-prefer-dollar-scopedslots](./rules/vue-prefer-dollar-scopedslots.md): Prefer $scopedSlots over $slots for Vue 2.x.
|
package/lib/index.js
CHANGED
|
@@ -8,6 +8,7 @@ module.exports = {
|
|
|
8
8
|
'require-i18n-strings': require('./rules/require-i18n-strings.js'),
|
|
9
9
|
'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
|
|
10
10
|
'vtu-no-explicit-wrapper-destroy': require('./rules/vtu-no-explicit-wrapper-destroy.js'),
|
|
11
|
+
'vtu-no-wrapper-vm': require('./rules/vtu-no-wrapper-vm.js'),
|
|
11
12
|
'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
|
|
12
13
|
'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
|
|
13
14
|
'vue-prefer-dollar-scopedslots': require('./rules/vue-prefer-dollar-scopedslots.js'),
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// ------------------------------------------------------------------------------
|
|
2
|
+
// Requirements
|
|
3
|
+
// ------------------------------------------------------------------------------
|
|
4
|
+
const { DOCS_BASE_URL } = require('../constants');
|
|
5
|
+
|
|
6
|
+
// ------------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
const getMemberName = (node) => {
|
|
11
|
+
if (node.type === 'Identifier') {
|
|
12
|
+
return node.name;
|
|
13
|
+
}
|
|
14
|
+
if (node.type === 'Literal') {
|
|
15
|
+
return node.value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const isMemberName = (node, name) => {
|
|
22
|
+
return getMemberName(node) === name;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const isVmReference = (node) => {
|
|
26
|
+
if (node.type === 'MemberExpression') {
|
|
27
|
+
return isMemberName(node.property, 'vm');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isAllowableVmProperty = (node, allowable) => {
|
|
34
|
+
const name = getMemberName(node);
|
|
35
|
+
|
|
36
|
+
if (!name) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return allowable.has(name);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ------------------------------------------------------------------------------
|
|
44
|
+
// Rule Definition
|
|
45
|
+
// ------------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @param {Set} allowable
|
|
50
|
+
*/
|
|
51
|
+
const getErrorMessage = (property, allowable) => {
|
|
52
|
+
if (getMemberName(property) === '$nextTick') {
|
|
53
|
+
return `Do not access \`vm\` internals directly. For \`$nextTick\`, import \`nextTick\` directly from the Vue package.`;
|
|
54
|
+
}
|
|
55
|
+
if (allowable.size) {
|
|
56
|
+
const part = Array.from(allowable).join(', ');
|
|
57
|
+
|
|
58
|
+
return `Do not access \`vm\` internals directly. Only the following properties are allowed: ${part}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return 'Do not access `vm` internals directly.';
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
meta: {
|
|
66
|
+
type: 'error',
|
|
67
|
+
docs: {
|
|
68
|
+
description: 'Prevent direct access to `vm` internals for `@vue/test-utils` wrappers.',
|
|
69
|
+
category: undefined,
|
|
70
|
+
url: DOCS_BASE_URL + '/vtu-no-wrapper-vm.md',
|
|
71
|
+
},
|
|
72
|
+
schema: [
|
|
73
|
+
{
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
allow: {
|
|
77
|
+
type: 'array',
|
|
78
|
+
items: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
create(context) {
|
|
88
|
+
const options = context.options[0] || {};
|
|
89
|
+
const allowable = new Set(options.allow);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
AssignmentExpression(node) {
|
|
93
|
+
if (isVmReference(node.right)) {
|
|
94
|
+
context.report({
|
|
95
|
+
node: node.right,
|
|
96
|
+
message:
|
|
97
|
+
'Do not access `vm` internals directly or work around this by saving a reference to `vm`.',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
MemberExpression(node) {
|
|
102
|
+
if (isVmReference(node.object) && !isAllowableVmProperty(node.property, allowable)) {
|
|
103
|
+
context.report({
|
|
104
|
+
node,
|
|
105
|
+
message: getErrorMessage(node.property, allowable),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
package/lib/utils/i18n-utils.js
CHANGED
|
@@ -10,6 +10,8 @@ const P_GETTEXT = 's__';
|
|
|
10
10
|
const N_GETTEXT = 'n__';
|
|
11
11
|
|
|
12
12
|
const ERROR_MESSAGE_STRING_LITERAL = 'Translation helpers must be called with a string literal.';
|
|
13
|
+
const ERROR_MESSAGE_STRING_INTERPOLATION =
|
|
14
|
+
'Translation helpers should not use string interpolation.';
|
|
13
15
|
const ERROR_MESSAGE_EXTRANEOUS_NAMESPACE =
|
|
14
16
|
'__ should not be used with a translation namespace, consider using s__ instead.';
|
|
15
17
|
const ERROR_MESSAGE_MISSING_NAMESPACE = 's__ requires a translation namespace to be provided.';
|
|
@@ -38,6 +40,16 @@ const mustBeCalledWithStringLiteralArguments = (context, node) => {
|
|
|
38
40
|
}
|
|
39
41
|
};
|
|
40
42
|
|
|
43
|
+
const mustNotHaveStringInterpolation = (context, node) => {
|
|
44
|
+
const [arg1, arg2] = node.arguments;
|
|
45
|
+
if ([arg1, arg2].some((arg) => arg?.expressions?.length)) {
|
|
46
|
+
context.report({
|
|
47
|
+
node,
|
|
48
|
+
message: ERROR_MESSAGE_STRING_INTERPOLATION,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
41
53
|
const mustHaveNamespace = (context, node) => {
|
|
42
54
|
if (node.arguments.length === 2) {
|
|
43
55
|
return;
|
|
@@ -94,9 +106,21 @@ const namespacesMustMatch = (context, node) => {
|
|
|
94
106
|
};
|
|
95
107
|
|
|
96
108
|
const VALIDATORS = {
|
|
97
|
-
[GETTEXT]: [
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
[GETTEXT]: [
|
|
110
|
+
mustBeCalledWithStringLiteralArguments,
|
|
111
|
+
mustNotHaveStringInterpolation,
|
|
112
|
+
mustNotHaveNamespace,
|
|
113
|
+
],
|
|
114
|
+
[P_GETTEXT]: [
|
|
115
|
+
mustBeCalledWithStringLiteralArguments,
|
|
116
|
+
mustNotHaveStringInterpolation,
|
|
117
|
+
mustHaveNamespace,
|
|
118
|
+
],
|
|
119
|
+
[N_GETTEXT]: [
|
|
120
|
+
mustBeCalledWithStringLiteralArguments,
|
|
121
|
+
mustNotHaveStringInterpolation,
|
|
122
|
+
namespacesMustMatch,
|
|
123
|
+
],
|
|
100
124
|
};
|
|
101
125
|
|
|
102
126
|
const validateI18nHelperCallFactory = (context) => (node) => {
|
|
@@ -113,6 +137,7 @@ const validateI18nHelperCallFactory = (context) => (node) => {
|
|
|
113
137
|
module.exports = {
|
|
114
138
|
validateI18nHelperCallFactory,
|
|
115
139
|
ERROR_MESSAGE_STRING_LITERAL,
|
|
140
|
+
ERROR_MESSAGE_STRING_INTERPOLATION,
|
|
116
141
|
ERROR_MESSAGE_EXTRANEOUS_NAMESPACE,
|
|
117
142
|
ERROR_MESSAGE_MISSING_NAMESPACE,
|
|
118
143
|
ERROR_MESSAGE_NAMESPACES_MISMATCH,
|