@gitlab/eslint-plugin 18.0.0 → 18.2.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 +14 -0
- package/docs/development.md +5 -0
- package/docs/rules/vtu-no-explicit-wrapper-destroy.md +65 -0
- package/docs/rules/vue-require-i18n-strings.md +1 -1
- package/docs/rules.md +1 -0
- package/lib/configs/jest.js +4 -0
- package/lib/index.js +1 -0
- package/lib/rules/vtu-no-explicit-wrapper-destroy.js +145 -0
- package/lib/utils/rule-utils.js +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [18.2.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v18.1.0...v18.2.0) (2023-03-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Implement no explicit wrapper destroy rule ([afae3a8](https://gitlab.com/gitlab-org/frontend/eslint-plugin/commit/afae3a8f5fe3e68e5680774bbbf4fdb52f21dc4f))
|
|
7
|
+
|
|
8
|
+
# [18.1.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v18.0.0...v18.1.0) (2022-09-26)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **jest:** restrict usage of toBeTruthy or toBeFalsy ([43fa628](https://gitlab.com/gitlab-org/frontend/eslint-plugin/commit/43fa6283a94b1ba2f04408f60dc3b7bcc800a3a5))
|
|
14
|
+
|
|
1
15
|
# [18.0.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v17.0.0...v18.0.0) (2022-09-21)
|
|
2
16
|
|
|
3
17
|
|
package/docs/development.md
CHANGED
|
@@ -19,6 +19,11 @@ Run `yarn createRule`, it will create:
|
|
|
19
19
|
- `lib/rules/<name>.js` (rule definition)
|
|
20
20
|
- `tests/rules/<name>.js` (rule unit tests)
|
|
21
21
|
|
|
22
|
+
## Exploring AST
|
|
23
|
+
|
|
24
|
+
Eslint works with [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) and exposes AST nodes for the rules to use.
|
|
25
|
+
To better understrand how the syntax tree looks like for your code example, you can use (AST Explorer tool)[https://astexplorer.net/]
|
|
26
|
+
|
|
22
27
|
## Testing
|
|
23
28
|
|
|
24
29
|
Run `yarn test` to execute the test suite, the suite includes rule tests and util tests.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @gitlab/vtu-no-explicit-wrapper-destroy
|
|
2
|
+
|
|
3
|
+
Throw an error when detecting a `wrapper.destroy()` and/or a `wrapper = null` in Jest hooks: `afterEach`, `afterAll`, `beforeEach`, `beforeAll`.
|
|
4
|
+
|
|
5
|
+
## Rule Details
|
|
6
|
+
|
|
7
|
+
This rules checks if there are unnecessary calls and assignments in Jest hooks.
|
|
8
|
+
|
|
9
|
+
With [enabling auto-destroy](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100389)
|
|
10
|
+
we no longer need to call `wrapper.destroy()` explicitly in our test code.
|
|
11
|
+
It is also [not necessary to explicitly nullify a wrapper](https://gitlab.com/gitlab-org/frontend/rfcs/-/issues/71).
|
|
12
|
+
|
|
13
|
+
This rule checks the contents of Jest hooks and checks if something that looks like a wrapper for a vm gets explicitly destroyed.
|
|
14
|
+
|
|
15
|
+
### Examples of **incorrect** code for this rule
|
|
16
|
+
|
|
17
|
+
A variable containing `wrapper` or `vm` has a `.destroy()` call.
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
drawerWrapper.destroy();
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vm.destroy();
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A variable containing `wrapper` or `vm` has a `= null` assignment.
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
wrapper = null;
|
|
36
|
+
wrapper.destroy();
|
|
37
|
+
jest.resetAllMocks();
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Examples of **correct** code for this rule
|
|
42
|
+
|
|
43
|
+
A variable that doesn't have a `wrapper` of `vm` is not treated as a target for this rule.
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
instance.destroy();
|
|
48
|
+
instance = null;
|
|
49
|
+
resetHTMLFixture();
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Options
|
|
54
|
+
|
|
55
|
+
Nothing
|
|
56
|
+
|
|
57
|
+
## Related rules
|
|
58
|
+
|
|
59
|
+
None
|
|
60
|
+
|
|
61
|
+
## When Not To Use It
|
|
62
|
+
|
|
63
|
+
Feel free to disable the rule for any lines that you know are either
|
|
64
|
+
wrongly caught by the rule, or are the exceptional cases where the destroy
|
|
65
|
+
call or null assignment are required
|
package/docs/rules.md
CHANGED
|
@@ -8,6 +8,7 @@ Available rules:
|
|
|
8
8
|
- [no-runtime-template-compiler](./rules/no-runtime-template-compiler.md): Disallow components which rely on a runtime template compiler.
|
|
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
|
+
- [vtu-no-explicit-wrapper-destroy](./rules/vtu-no-explicit-wrapper-destroy.md): Prevents redundant destroy calls and null asignment
|
|
11
12
|
- [vue-no-data-toggle](./rules/vue-no-data-toggle.md): Restrict the use of `data-toggle` bootstrap behaviors within Vue templates
|
|
12
13
|
- [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
|
|
13
14
|
- [vue-prefer-dollar-scopedslots](./rules/vue-prefer-dollar-scopedslots.md): Prefer $scopedSlots over $slots for Vue 2.x.
|
package/lib/configs/jest.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ module.exports = {
|
|
|
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
9
|
'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
|
|
10
|
+
'vtu-no-explicit-wrapper-destroy': require('./rules/vtu-no-explicit-wrapper-destroy.js'),
|
|
10
11
|
'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
|
|
11
12
|
'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
|
|
12
13
|
'vue-prefer-dollar-scopedslots': require('./rules/vue-prefer-dollar-scopedslots.js'),
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// ------------------------------------------------------------------------------
|
|
2
|
+
// Requirements
|
|
3
|
+
// ------------------------------------------------------------------------------
|
|
4
|
+
const { DOCS_BASE_URL } = require('../constants');
|
|
5
|
+
const { NODE_TYPES } = require('../utils/rule-utils');
|
|
6
|
+
|
|
7
|
+
// ------------------------------------------------------------------------------
|
|
8
|
+
// Helpers
|
|
9
|
+
// ------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
class Rule {
|
|
12
|
+
#vmWrapperNameParts;
|
|
13
|
+
#context;
|
|
14
|
+
|
|
15
|
+
constructor(context, options) {
|
|
16
|
+
this.#vmWrapperNameParts = options.wrapperNames || commonVmWrapperNameParts;
|
|
17
|
+
this.#context = context;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
verifyExpression = (expression) => {
|
|
21
|
+
if (
|
|
22
|
+
expression.type === NODE_TYPES.CALL_EXPRESSION &&
|
|
23
|
+
this.#isRedundantDestroyCall(expression)
|
|
24
|
+
) {
|
|
25
|
+
this.#context.report({
|
|
26
|
+
node: expression,
|
|
27
|
+
messageId: 'redundantDestroy',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
expression.type === NODE_TYPES.ASSIGNMENT_EXPRESSION &&
|
|
33
|
+
this.#isRedundantNullAssignment(expression)
|
|
34
|
+
) {
|
|
35
|
+
this.#context.report({
|
|
36
|
+
node: expression,
|
|
37
|
+
messageId: 'redundantNullAssignment',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
#isPotentialVmWrapper = (variableName) =>
|
|
43
|
+
this.#vmWrapperNameParts.some((namePart) =>
|
|
44
|
+
variableName.toLowerCase().includes(namePart.toLowerCase()),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
#isRedundantDestroyCall = (expression) => {
|
|
48
|
+
const calleeName = expression.callee.object?.name;
|
|
49
|
+
const functionCallName = expression.callee.property?.name;
|
|
50
|
+
if (!calleeName || !functionCallName) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.#isPotentialVmWrapper(calleeName) && functionCallName === 'destroy';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
#isRedundantNullAssignment = (expression) => {
|
|
58
|
+
if (
|
|
59
|
+
expression.left.type !== NODE_TYPES.IDENTIFIER ||
|
|
60
|
+
expression.right.type !== NODE_TYPES.LITERAL
|
|
61
|
+
) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const identifierName = expression.left.name;
|
|
66
|
+
const assignmentLiteralValue = expression.right.value;
|
|
67
|
+
|
|
68
|
+
return this.#isPotentialVmWrapper(identifierName) && assignmentLiteralValue === null;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const functionNameRuleTrigger = 'afterEach';
|
|
73
|
+
const defaultOptions = {
|
|
74
|
+
ruleTriggerer: functionNameRuleTrigger,
|
|
75
|
+
wrapperNames: ['vm', 'wrapper'],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ------------------------------------------------------------------------------
|
|
79
|
+
// Rule Definition
|
|
80
|
+
// ------------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
meta: {
|
|
84
|
+
type: 'error',
|
|
85
|
+
docs: {
|
|
86
|
+
description: 'Prevents redundant destroy calls and null asignment',
|
|
87
|
+
category: 'maintainability',
|
|
88
|
+
url: DOCS_BASE_URL + '/vtu-no-explicit-wrapper-destroy.md',
|
|
89
|
+
},
|
|
90
|
+
messages: {
|
|
91
|
+
redundantDestroy: `Redundant 'destroy()' call in 'after...' block.`,
|
|
92
|
+
redundantNullAssignment: `Redundant null assignment in 'after...' block.`,
|
|
93
|
+
},
|
|
94
|
+
//Optional:
|
|
95
|
+
schema: [
|
|
96
|
+
{
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
ruleTriggerer: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
},
|
|
102
|
+
wrapperNames: {
|
|
103
|
+
type: 'array',
|
|
104
|
+
items: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
},
|
|
107
|
+
uniqueItems: true,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
additionalProperties: false,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
create(context) {
|
|
115
|
+
return {
|
|
116
|
+
CallExpression(node) {
|
|
117
|
+
const options = { ...defaultOptions, ...(context.options[0] || {}) };
|
|
118
|
+
|
|
119
|
+
const name = node.callee.name;
|
|
120
|
+
if (name !== options.ruleTriggerer) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const rule = new Rule(context, options);
|
|
125
|
+
|
|
126
|
+
const afterEachFunction = node.arguments[0].body;
|
|
127
|
+
|
|
128
|
+
if (afterEachFunction.type === NODE_TYPES.BLOCK_STATEMENT) {
|
|
129
|
+
const statements = afterEachFunction.body;
|
|
130
|
+
for (const statement of statements) {
|
|
131
|
+
if (statement.type !== NODE_TYPES.EXPRESSION_STATEMENT) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const expression = statement.expression;
|
|
136
|
+
|
|
137
|
+
rule.verifyExpression(expression);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
rule.verifyExpression(afterEachFunction);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
};
|
package/lib/utils/rule-utils.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const CALL_EXPRESSION = 'CallExpression';
|
|
2
|
+
const ASSIGNMENT_EXPRESSION = 'AssignmentExpression';
|
|
3
|
+
const BLOCK_STATEMENT = 'BlockStatement';
|
|
4
|
+
const EXPRESSION_STATEMENT = 'ExpressionStatement';
|
|
2
5
|
const V_ATTRIBUTE = 'VAttribute';
|
|
3
6
|
const V_DIRECTIVE_KEY = 'VDirectiveKey';
|
|
4
7
|
const V_EXPRESSION_CONTAINER = 'VExpressionContainer';
|
|
@@ -11,6 +14,9 @@ const IDENTIFIER = 'Identifier';
|
|
|
11
14
|
|
|
12
15
|
const NODE_TYPES = {
|
|
13
16
|
CALL_EXPRESSION,
|
|
17
|
+
BLOCK_STATEMENT,
|
|
18
|
+
ASSIGNMENT_EXPRESSION,
|
|
19
|
+
EXPRESSION_STATEMENT,
|
|
14
20
|
V_ATTRIBUTE,
|
|
15
21
|
V_DIRECTIVE_KEY,
|
|
16
22
|
V_EXPRESSION_CONTAINER,
|