@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 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
 
@@ -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
@@ -18,7 +18,7 @@ Detect non externalized strings in vue `<template>`
18
18
  Correctly externalized attributes or attributes that contain no letters
19
19
 
20
20
  ```html
21
- <div>{{ _("test") }}</div>
21
+ <div>{{ __("test") }}</div>
22
22
  <div>{{ test }}</div>
23
23
  <p>→</p>
24
24
  ```
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.
@@ -9,5 +9,9 @@ module.exports = {
9
9
  ignoreTypeOfDescribeName: true,
10
10
  },
11
11
  ],
12
+ 'jest/no-restricted-matchers': ['error', {
13
+ toBeFalsy: 'Prefer a stronger matcher like `toBe(false)`',
14
+ toBeTruthy: 'Prefer a stronger matcher like `toBe(true)`'
15
+ }]
12
16
  },
13
17
  };
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
+ };
@@ -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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/eslint-plugin",
3
- "version": "18.0.0",
3
+ "version": "18.2.0",
4
4
  "description": "GitLab package for our custom eslint rules",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {