@automattic/eslint-plugin-wpvip 0.4.10 → 0.4.12
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/README.md +47 -66
- package/configs/formatting.js +17 -19
- package/configs/index.js +13 -12
- package/configs/javascript.js +12 -10
- package/configs/jsdoc.js +5 -10
- package/configs/jsx-ally.js +2 -2
- package/configs/prettier.js +2 -2
- package/configs/react.js +18 -4
- package/configs/recommended.js +28 -0
- package/configs/testing.js +2 -2
- package/configs/typescript.js +4 -4
- package/configs/weak-javascript.js +7 -1
- package/configs/weak-testing.js +0 -1
- package/configs/weak-typescript.js +11 -5
- package/index.js +2 -2
- package/init.js +1 -1
- package/package.json +6 -5
- package/rules/dependency-group.js +52 -62
- package/rules/index.js +4 -4
- package/rules/no-async-foreach.js +13 -15
- package/rules/no-unguarded-get-range-at.js +4 -4
- package/rules/no-unused-vars-before-return.js +33 -40
- package/utils/debug-log.js +7 -0
- package/utils/is-package-installed.js +34 -0
- package/.eslintignore +0 -2
- package/.eslintrc.js +0 -17
- package/.nvmrc +0 -1
- package/configs/base.js +0 -7
- package/tsconfig.json +0 -30
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# WordPress VIP ESLint plugin
|
|
2
2
|
|
|
3
|
-
This is an ESLint plugin to provide WordPress VIP's (internal) JavaScript and TypeScript coding standards. It
|
|
3
|
+
This is an ESLint plugin to provide WordPress VIP's (internal) JavaScript and TypeScript coding standards. It is inspired by and borrows from [`@wordpress/eslint-plugin`](https://github.com/WordPress/gutenberg/tree/trunk/packages/eslint-plugin).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,104 +12,85 @@ npm install --save-dev eslint @automattic/eslint-plugin-wpvip
|
|
|
12
12
|
|
|
13
13
|
## Configuration
|
|
14
14
|
|
|
15
|
-
Create an `.eslintrc.js` file
|
|
15
|
+
Create an `.eslintrc.js` file. **Note:** The `init` file allows you to avoid installing peer dependencies (available from `v0.5.0`).
|
|
16
16
|
|
|
17
17
|
```js
|
|
18
18
|
require( '@automattic/eslint-plugin-wpvip/init' );
|
|
19
19
|
|
|
20
20
|
module.exports = {
|
|
21
21
|
extends: [
|
|
22
|
-
'plugin:@automattic/wpvip/
|
|
23
|
-
]
|
|
24
|
-
}
|
|
22
|
+
'plugin:@automattic/wpvip/recommended',
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
And that's it! Code editors that are configured to work with ESLint will automatically pick up the rules and flag any errors or warnings.
|
|
28
|
-
|
|
29
|
-
Tip: Set up a `lint` npm script in `package.json`:
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
"scripts": {
|
|
33
|
-
"lint": "eslint ."
|
|
34
|
-
}
|
|
35
|
-
```
|
|
27
|
+
And that's it! It works automatically with most Babel and TypeScript projects. Code editors that are configured to work with ESLint will automatically pick up the rules and flag any errors or warnings.
|
|
36
28
|
|
|
37
29
|
You may also wish to define an `.eslintignore` file if there are files or paths that you do not want to lint.
|
|
38
30
|
|
|
39
|
-
##
|
|
31
|
+
## Recommended config
|
|
40
32
|
|
|
41
|
-
TypeScript rules
|
|
33
|
+
The "recommended" config includes rules for JavaScript, TypeScript, Jest, and React, including rules related to formatting and white space. It is intended to be strict! Opinionated defaults keep our codebases consistent and reduce the friction we experience when context-switching between projects.
|
|
42
34
|
|
|
43
|
-
|
|
35
|
+
If your project has installed [Prettier](https://prettier.io/) as a dependency, then many formatting tasks will be delegated to it (via `eslint-plugin-prettier`). You are encouraged to define your own [`.prettierrc` configuration file](https://prettier.io/docs/en/configuration.html) to fine-tune your project’s formatting.
|
|
44
36
|
|
|
45
|
-
|
|
37
|
+
Of course, this recommended config may not be ideal for every project, so feel free to "build your own" using the available modular configs. The recommended config is equivalent to:
|
|
46
38
|
|
|
47
|
-
```
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
```js
|
|
40
|
+
module.exports = {
|
|
41
|
+
extends: [
|
|
42
|
+
'plugin:@automattic/wpvip/javascript',
|
|
43
|
+
'plugin:@automattic/wpvip/typescript', // when "typescript" is installed
|
|
44
|
+
'plugin:@automattic/wpvip/formatting',
|
|
45
|
+
'plugin:@automattic/wpvip/testing', // when "jest" is installed
|
|
46
|
+
'plugin:@automattic/wpvip/react', // when "react" is installed
|
|
47
|
+
'plugin:@automattic/wpvip/prettier', // when "prettier" is installed
|
|
48
|
+
],
|
|
49
|
+
};
|
|
51
50
|
```
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"extends": [
|
|
58
|
-
"plugin:@automattic/wpvip/base",
|
|
59
|
-
"plugin:@automattic/wpvip/prettier-off"
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
```
|
|
52
|
+
Note that the order of configs can matter, since they can contain overrides. It is particularly important to add the `prettier` config last.
|
|
63
53
|
|
|
64
54
|
## CLI
|
|
65
55
|
|
|
66
56
|
The `cli` config allows certain behaviors that are usually against best practice but are useful in a codebase that produces a CLI tool:
|
|
67
57
|
|
|
68
|
-
```
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
]
|
|
74
|
-
}
|
|
58
|
+
```js
|
|
59
|
+
module.exports = {
|
|
60
|
+
extends: [
|
|
61
|
+
'plugin:@automattic/wpvip/recommended',
|
|
62
|
+
'plugin:@automattic/wpvip/cli',
|
|
63
|
+
],
|
|
64
|
+
};
|
|
75
65
|
```
|
|
76
66
|
|
|
77
|
-
If your project is not a CLI tool but calls `console` or `process` methods occasionally, don't
|
|
67
|
+
If your project is not a CLI tool but calls `console` or `process` methods occasionally, you probably don't need this config. Instead, disable those rules only where necessary and include an explanation:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
// Intentionally exiting because we have observed an unrecoverable error.
|
|
71
|
+
// eslint-disable-next-line no-process-exit
|
|
72
|
+
process.exit( 1 );
|
|
73
|
+
```
|
|
78
74
|
|
|
79
75
|
## JSDoc
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
JSDoc is considered optional, especially compared to better alternatives like TypeScript and OpenAPI documentation. If you want to enforce the use of JSDoc, use the `jsdoc` config:
|
|
82
78
|
|
|
83
79
|
```js
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Rules *are* triggered for this docblock.
|
|
92
|
-
*
|
|
93
|
-
* @param myArg
|
|
94
|
-
*/
|
|
95
|
-
function myFunc2(myArg) {}
|
|
80
|
+
module.exports = {
|
|
81
|
+
extends: [
|
|
82
|
+
'plugin:@automattic/wpvip/recommended',
|
|
83
|
+
'plugin:@automattic/wpvip/jsdoc',
|
|
84
|
+
],
|
|
85
|
+
};
|
|
96
86
|
```
|
|
97
87
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"extends": [
|
|
103
|
-
"plugin:@automattic/wpvip/base",
|
|
104
|
-
"plugin:@automattic/wpvip/jsdoc"
|
|
105
|
-
]
|
|
106
|
-
}
|
|
107
|
-
```
|
|
88
|
+
Note that rules that require `@param` and `@return` types are relaxed in TypeScript files.
|
|
108
89
|
|
|
109
90
|
## "Weak" configs
|
|
110
91
|
|
|
111
|
-
This plugin provides a few "weak" configs for legacy codebases that are working to transition to stronger standards. These configs downgrade select rules from the `
|
|
92
|
+
This plugin provides a few so-called "weak" configs for legacy codebases that are working to transition to stronger standards. These configs downgrade select rules from the `recommended` config to warnings. Warnings will still be visible in code editors and other outputs but will not fail continous integration workflows.
|
|
112
93
|
|
|
113
|
-
These configs are intended for temporary use and should not be used long-term. We also do not recommend the use of tools like [eslines](https://github.com/Automattic/eslines) to ignore errors or warnings. While the intention is to prevent large-scale changes and transition slowly to stronger standards, the effect is usually that the transition stalls and stops completely.
|
|
94
|
+
These configs are intended for temporary use and should not be used long-term. We also do not recommend the use of tools like [eslines](https://github.com/Automattic/eslines) to ignore errors or warnings. While the intention is to prevent large-scale changes and transition slowly to stronger standards, the effect is usually that the transition stalls and eventually stops completely.
|
|
114
95
|
|
|
115
|
-
|
|
96
|
+
Three "weak" configs are available: `weak-javascript`, `weak-typescript`, and `weak-jest`. While pull requests on this project are always welcome, please carefully consider whether adding rules to these configs is truly necessary. Ideally, we work to remove rules from these configs until they are no longer needed.
|
package/configs/formatting.js
CHANGED
|
@@ -4,13 +4,13 @@ module.exports = {
|
|
|
4
4
|
* disable errors, include a brief justification or reasoning.
|
|
5
5
|
*/
|
|
6
6
|
rules: {
|
|
7
|
-
'array-bracket-spacing': [
|
|
7
|
+
'array-bracket-spacing': ['error', 'always'],
|
|
8
8
|
|
|
9
|
-
'arrow-parens': [
|
|
9
|
+
'arrow-parens': ['error', 'always'],
|
|
10
10
|
|
|
11
11
|
'arrow-spacing': 'error',
|
|
12
12
|
|
|
13
|
-
'brace-style': [
|
|
13
|
+
'brace-style': ['error', '1tbs'],
|
|
14
14
|
|
|
15
15
|
// Identifiers should be in camelCase. Object properties are excluded
|
|
16
16
|
// (including when destructuring) since they often come from external
|
|
@@ -23,24 +23,24 @@ module.exports = {
|
|
|
23
23
|
},
|
|
24
24
|
],
|
|
25
25
|
|
|
26
|
-
'comma-dangle': [
|
|
26
|
+
'comma-dangle': ['error', 'always-multiline'],
|
|
27
27
|
|
|
28
28
|
'comma-spacing': 'error',
|
|
29
29
|
|
|
30
|
-
'comma-style': [
|
|
30
|
+
'comma-style': ['error', 'last'],
|
|
31
31
|
|
|
32
|
-
'computed-property-spacing': [
|
|
32
|
+
'computed-property-spacing': ['error', 'always'],
|
|
33
33
|
|
|
34
|
-
curly: [
|
|
34
|
+
curly: ['error', 'all'],
|
|
35
35
|
|
|
36
36
|
'dot-notation': 'error',
|
|
37
37
|
|
|
38
38
|
// Files must end in a newline.
|
|
39
|
-
'eol-last': [
|
|
39
|
+
'eol-last': ['error', 'always'],
|
|
40
40
|
|
|
41
41
|
'func-call-spacing': 'error',
|
|
42
42
|
|
|
43
|
-
indent: [
|
|
43
|
+
indent: ['error', 'tab', { SwitchCase: 1 }],
|
|
44
44
|
|
|
45
45
|
'key-spacing': 'error',
|
|
46
46
|
|
|
@@ -58,19 +58,19 @@ module.exports = {
|
|
|
58
58
|
|
|
59
59
|
'no-multi-str': 'error',
|
|
60
60
|
|
|
61
|
-
'no-multiple-empty-lines': [
|
|
61
|
+
'no-multiple-empty-lines': ['error', { max: 1 }],
|
|
62
62
|
|
|
63
63
|
'no-trailing-spaces': 'error',
|
|
64
64
|
|
|
65
65
|
'no-whitespace-before-property': 'error',
|
|
66
66
|
|
|
67
|
-
'object-curly-spacing': [
|
|
67
|
+
'object-curly-spacing': ['error', 'always'],
|
|
68
68
|
|
|
69
69
|
'object-shorthand': 'error',
|
|
70
70
|
|
|
71
71
|
'operator-linebreak': 'error',
|
|
72
72
|
|
|
73
|
-
'padded-blocks': [
|
|
73
|
+
'padded-blocks': ['error', 'never'],
|
|
74
74
|
|
|
75
75
|
// Arrow functions should be used for function arguments and callbacks.
|
|
76
76
|
'prefer-arrow-callback': 'warn',
|
|
@@ -81,30 +81,28 @@ module.exports = {
|
|
|
81
81
|
{ allowTemplateLiterals: true, avoidEscape: true },
|
|
82
82
|
],
|
|
83
83
|
|
|
84
|
-
'quote-props': [
|
|
84
|
+
'quote-props': ['error', 'as-needed'],
|
|
85
85
|
|
|
86
86
|
semi: 'error',
|
|
87
87
|
|
|
88
88
|
'semi-spacing': 'error',
|
|
89
89
|
|
|
90
|
-
'space-before-blocks': [
|
|
90
|
+
'space-before-blocks': ['error', 'always'],
|
|
91
91
|
|
|
92
92
|
'space-before-function-paren': [
|
|
93
93
|
'error',
|
|
94
94
|
{ anonymous: 'never', named: 'never', asyncArrow: 'always' },
|
|
95
95
|
],
|
|
96
96
|
|
|
97
|
-
'space-in-parens': [
|
|
97
|
+
'space-in-parens': ['error', 'always'],
|
|
98
98
|
|
|
99
99
|
'space-infix-ops': 'error',
|
|
100
100
|
|
|
101
|
-
'space-unary-ops': [
|
|
101
|
+
'space-unary-ops': ['error', { overrides: { '!': true, yield: true } }],
|
|
102
102
|
|
|
103
103
|
// Comments should always include consistent spacing for readability.
|
|
104
104
|
'spaced-comment': 'warn',
|
|
105
105
|
|
|
106
|
-
'template-curly-spacing': [
|
|
107
|
-
|
|
106
|
+
'template-curly-spacing': ['error', 'always'],
|
|
108
107
|
},
|
|
109
108
|
};
|
|
110
|
-
|
package/configs/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
base: require(
|
|
3
|
-
cli: require(
|
|
4
|
-
formatting: require(
|
|
5
|
-
javascript: require(
|
|
6
|
-
jsdoc: require(
|
|
7
|
-
prettier: require(
|
|
8
|
-
react: require(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'weak-
|
|
13
|
-
'weak-
|
|
2
|
+
base: require('./javascript'), // synonym for javascript
|
|
3
|
+
cli: require('./cli'),
|
|
4
|
+
formatting: require('./formatting'),
|
|
5
|
+
javascript: require('./javascript'),
|
|
6
|
+
jsdoc: require('./jsdoc'),
|
|
7
|
+
prettier: require('./prettier'),
|
|
8
|
+
react: require('./react'),
|
|
9
|
+
recommended: require('./recommended'),
|
|
10
|
+
testing: require('./testing'),
|
|
11
|
+
typescript: require('./typescript'),
|
|
12
|
+
'weak-javascript': require('./weak-javascript'),
|
|
13
|
+
'weak-testing': require('./weak-testing'),
|
|
14
|
+
'weak-typescript': require('./weak-typescript'),
|
|
14
15
|
};
|
package/configs/javascript.js
CHANGED
|
@@ -12,6 +12,8 @@ module.exports = {
|
|
|
12
12
|
node: true,
|
|
13
13
|
},
|
|
14
14
|
|
|
15
|
+
extends: ['plugin:json/recommended', 'plugin:security/recommended'],
|
|
16
|
+
|
|
15
17
|
parser: '@babel/eslint-parser',
|
|
16
18
|
|
|
17
19
|
parserOptions: {
|
|
@@ -22,15 +24,14 @@ module.exports = {
|
|
|
22
24
|
/**
|
|
23
25
|
* Note: We must explicitly add this plugin to use our custom rules.
|
|
24
26
|
*/
|
|
25
|
-
plugins: [
|
|
27
|
+
plugins: ['@automattic/wpvip', 'import'],
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Please include a short description of the rule. For rules that downgrade or
|
|
29
31
|
* disable errors, include a brief justification or reasoning.
|
|
30
32
|
*/
|
|
31
33
|
rules: {
|
|
32
|
-
//
|
|
33
|
-
// visibility:
|
|
34
|
+
// BEGIN eslint:recommended, enumerated here for visibility:
|
|
34
35
|
// https://github.com/eslint/eslint/blob/main/packages/js/src/configs/eslint-recommended.js
|
|
35
36
|
'constructor-super': 'error',
|
|
36
37
|
'for-direction': 'error',
|
|
@@ -93,6 +94,7 @@ module.exports = {
|
|
|
93
94
|
'require-yield': 'error',
|
|
94
95
|
'use-isnan': 'error',
|
|
95
96
|
// 'valid-typeof': 'error', // extended below
|
|
97
|
+
// END eslint:recommended
|
|
96
98
|
|
|
97
99
|
// Async/await must not be used in a `.forEach` method, because the result
|
|
98
100
|
// will not be awaited in the outer scope.
|
|
@@ -137,7 +139,7 @@ module.exports = {
|
|
|
137
139
|
|
|
138
140
|
// Enforce Unix linebreaks. Included here and not in "formatting" since it
|
|
139
141
|
// is not controversial and helps with interchange.
|
|
140
|
-
'linebreak-style': [
|
|
142
|
+
'linebreak-style': ['error', 'unix'],
|
|
141
143
|
|
|
142
144
|
'no-alert': 'error',
|
|
143
145
|
|
|
@@ -149,7 +151,7 @@ module.exports = {
|
|
|
149
151
|
|
|
150
152
|
'no-caller': 'error',
|
|
151
153
|
|
|
152
|
-
'no-cond-assign': [
|
|
154
|
+
'no-cond-assign': ['error', 'except-parens'],
|
|
153
155
|
|
|
154
156
|
// `console.log` should not be used directly in code. Ideally, delegate to a
|
|
155
157
|
// logging function that logs on your behalf (and ignore this rule there).
|
|
@@ -161,7 +163,7 @@ module.exports = {
|
|
|
161
163
|
|
|
162
164
|
'no-else-return': 'error',
|
|
163
165
|
|
|
164
|
-
'no-empty': [
|
|
166
|
+
'no-empty': ['error', { allowEmptyCatch: true }],
|
|
165
167
|
|
|
166
168
|
'no-eq-null': 'error',
|
|
167
169
|
|
|
@@ -179,7 +181,7 @@ module.exports = {
|
|
|
179
181
|
|
|
180
182
|
'no-unused-expressions': 'error',
|
|
181
183
|
|
|
182
|
-
'no-unused-vars': [
|
|
184
|
+
'no-unused-vars': ['error', { ignoreRestSiblings: true }],
|
|
183
185
|
|
|
184
186
|
'no-useless-computed-key': 'error',
|
|
185
187
|
|
|
@@ -189,9 +191,9 @@ module.exports = {
|
|
|
189
191
|
|
|
190
192
|
'no-var': 'error',
|
|
191
193
|
|
|
192
|
-
'one-var': [
|
|
194
|
+
'one-var': ['error', 'never'],
|
|
193
195
|
|
|
194
|
-
'prefer-const': [
|
|
196
|
+
'prefer-const': ['error', { destructuring: 'all' }],
|
|
195
197
|
|
|
196
198
|
radix: 'error',
|
|
197
199
|
|
|
@@ -203,6 +205,6 @@ module.exports = {
|
|
|
203
205
|
},
|
|
204
206
|
],
|
|
205
207
|
|
|
206
|
-
'wrap-iife': [
|
|
208
|
+
'wrap-iife': ['error', 'any'],
|
|
207
209
|
},
|
|
208
210
|
};
|
package/configs/jsdoc.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/jsdoc.js
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const globals = require(
|
|
6
|
+
const globals = require('globals');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Helpful utilities that are globally defined and known to the TypeScript compiler.
|
|
@@ -41,7 +41,7 @@ const typescriptUtilityTypes = [
|
|
|
41
41
|
];
|
|
42
42
|
|
|
43
43
|
module.exports = {
|
|
44
|
-
extends: [
|
|
44
|
+
extends: ['plugin:jsdoc/recommended'],
|
|
45
45
|
settings: {
|
|
46
46
|
jsdoc: {
|
|
47
47
|
preferredTypes: {
|
|
@@ -61,9 +61,7 @@ module.exports = {
|
|
|
61
61
|
// Required to reference browser types because we don't have the `browser` environment enabled for the project.
|
|
62
62
|
// Here we filter out all browser globals that don't begin with an uppercase letter because those
|
|
63
63
|
// generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`).
|
|
64
|
-
...Object.keys(
|
|
65
|
-
/^[A-Z]/.test( key ),
|
|
66
|
-
),
|
|
64
|
+
...Object.keys(globals.browser).filter((key) => /^[A-Z]/.test(key)),
|
|
67
65
|
...typescriptUtilityTypes,
|
|
68
66
|
'void',
|
|
69
67
|
'JSX',
|
|
@@ -75,17 +73,14 @@ module.exports = {
|
|
|
75
73
|
'jsdoc/require-returns': 'off',
|
|
76
74
|
'jsdoc/require-yields': 'off',
|
|
77
75
|
'jsdoc/tag-lines': 'off',
|
|
78
|
-
'jsdoc/no-multi-asterisks': [
|
|
79
|
-
'error',
|
|
80
|
-
{ preventAtMiddleLines: false },
|
|
81
|
-
],
|
|
76
|
+
'jsdoc/no-multi-asterisks': ['error', { preventAtMiddleLines: false }],
|
|
82
77
|
'jsdoc/check-access': 'error',
|
|
83
78
|
'jsdoc/check-alignment': 'error',
|
|
84
79
|
'jsdoc/check-line-alignment': [
|
|
85
80
|
'error',
|
|
86
81
|
'always',
|
|
87
82
|
{
|
|
88
|
-
tags: [
|
|
83
|
+
tags: ['param', 'arg', 'argument', 'property', 'prop'],
|
|
89
84
|
preserveMainDescriptionPostDelimiter: true,
|
|
90
85
|
},
|
|
91
86
|
],
|
package/configs/jsx-ally.js
CHANGED
package/configs/prettier.js
CHANGED
package/configs/react.js
CHANGED
|
@@ -7,19 +7,23 @@ module.exports = {
|
|
|
7
7
|
extends: [
|
|
8
8
|
'plugin:react/recommended',
|
|
9
9
|
'plugin:react-hooks/recommended',
|
|
10
|
-
require(
|
|
10
|
+
require('./jsx-ally'),
|
|
11
11
|
],
|
|
12
|
+
|
|
12
13
|
parserOptions: {
|
|
13
14
|
ecmaFeatures: {
|
|
14
15
|
jsx: true,
|
|
15
16
|
},
|
|
16
17
|
},
|
|
18
|
+
|
|
17
19
|
settings: {
|
|
18
20
|
react: {
|
|
19
21
|
version: 'detect',
|
|
20
22
|
},
|
|
21
23
|
},
|
|
22
|
-
|
|
24
|
+
|
|
25
|
+
plugins: ['@automattic/wpvip', 'react', 'react-hooks'],
|
|
26
|
+
|
|
23
27
|
rules: {
|
|
24
28
|
'@automattic/wpvip/no-unused-vars-before-return': [
|
|
25
29
|
'error',
|
|
@@ -27,7 +31,9 @@ module.exports = {
|
|
|
27
31
|
excludePattern: '^use',
|
|
28
32
|
},
|
|
29
33
|
],
|
|
34
|
+
|
|
30
35
|
'react/display-name': 'off',
|
|
36
|
+
|
|
31
37
|
'react/jsx-curly-spacing': [
|
|
32
38
|
'error',
|
|
33
39
|
{
|
|
@@ -35,13 +41,21 @@ module.exports = {
|
|
|
35
41
|
children: true,
|
|
36
42
|
},
|
|
37
43
|
],
|
|
44
|
+
|
|
38
45
|
'react/jsx-equals-spacing': 'error',
|
|
39
|
-
|
|
40
|
-
'react/jsx-indent
|
|
46
|
+
|
|
47
|
+
'react/jsx-indent': ['error', 'tab'],
|
|
48
|
+
|
|
49
|
+
'react/jsx-indent-props': ['error', 'tab'],
|
|
50
|
+
|
|
41
51
|
'react/jsx-key': 'error',
|
|
52
|
+
|
|
42
53
|
'react/jsx-tag-spacing': 'error',
|
|
54
|
+
|
|
43
55
|
'react/no-children-prop': 'off',
|
|
56
|
+
|
|
44
57
|
'react/prop-types': 'off',
|
|
58
|
+
|
|
45
59
|
'react/react-in-jsx-scope': 'off',
|
|
46
60
|
},
|
|
47
61
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const debugLog = require('../utils/debug-log');
|
|
2
|
+
const isPackageInstalled = require('../utils/is-package-installed');
|
|
3
|
+
|
|
4
|
+
const config = {
|
|
5
|
+
extends: [require.resolve('./javascript')],
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
if (isPackageInstalled('typescript')) {
|
|
9
|
+
config.extends.push(require.resolve('./typescript'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
config.extends.push(require.resolve('./formatting'));
|
|
13
|
+
|
|
14
|
+
if (isPackageInstalled('jest')) {
|
|
15
|
+
config.extends.push(require.resolve('./testing'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isPackageInstalled('react')) {
|
|
19
|
+
config.extends.push(require.resolve('./react'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (isPackageInstalled('prettier')) {
|
|
23
|
+
config.extends.push(require.resolve('./prettier'));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
debugLog(`Using the following configs:\n${config.extends.join('\n')}`);
|
|
27
|
+
|
|
28
|
+
module.exports = config;
|
package/configs/testing.js
CHANGED
package/configs/typescript.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
|
-
ignorePatterns: [
|
|
7
|
+
ignorePatterns: ['**/*.d.ts'],
|
|
8
8
|
|
|
9
9
|
overrides: [
|
|
10
10
|
{
|
|
@@ -14,7 +14,7 @@ module.exports = {
|
|
|
14
14
|
'plugin:@typescript-eslint/strict',
|
|
15
15
|
],
|
|
16
16
|
|
|
17
|
-
files: [
|
|
17
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
18
18
|
|
|
19
19
|
parser: '@typescript-eslint/parser',
|
|
20
20
|
|
|
@@ -39,12 +39,12 @@ module.exports = {
|
|
|
39
39
|
},
|
|
40
40
|
],
|
|
41
41
|
|
|
42
|
-
plugins: [
|
|
42
|
+
plugins: ['@typescript-eslint', 'jsdoc'],
|
|
43
43
|
|
|
44
44
|
settings: {
|
|
45
45
|
'import/resolver': {
|
|
46
46
|
node: {
|
|
47
|
-
extensions: [
|
|
47
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
},
|
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
overrides: [
|
|
11
11
|
{
|
|
12
12
|
// Don't apply weak rules to TypeScript files.
|
|
13
|
-
files: [
|
|
13
|
+
files: ['**/*.js', '**/*.jsx'],
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Downgrade rules from the base preset to "warn". Do not disable rules (set
|
|
@@ -31,8 +31,14 @@ module.exports = {
|
|
|
31
31
|
|
|
32
32
|
'no-case-declarations': 'warn',
|
|
33
33
|
|
|
34
|
+
'no-dupe-else-if': 'warn',
|
|
35
|
+
|
|
34
36
|
'no-else-return': 'warn',
|
|
35
37
|
|
|
38
|
+
'no-eq-null': 'warn',
|
|
39
|
+
|
|
40
|
+
'no-lonely-if': 'warn',
|
|
41
|
+
|
|
36
42
|
'no-mixed-operators': 'warn',
|
|
37
43
|
|
|
38
44
|
'no-prototype-builtins': 'warn',
|
package/configs/weak-testing.js
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* you migrate an existing project to TypeScript.
|
|
6
6
|
*/
|
|
7
7
|
module.exports = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
overrides: [
|
|
9
|
+
{
|
|
10
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Downgrade rules from the base preset to "warn". Do not disable rules (set
|
|
14
|
+
* to "off"). If a rule is already set to a warning, do not disable it.
|
|
15
|
+
*/
|
|
16
|
+
rules: {},
|
|
17
|
+
},
|
|
18
|
+
],
|
|
13
19
|
};
|
package/index.js
CHANGED
package/init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
require(
|
|
1
|
+
require('@rushstack/eslint-patch/modern-module-resolution');
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/eslint-plugin-wpvip",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"description": "ESLint plugin for internal WordPress VIP projects",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"jest": "jest",
|
|
8
8
|
"jest:update-snapshot": "jest --updateSnapshot",
|
|
9
9
|
"link-plugin": "mkdir -p ./node_modules/@automattic; ln -fns $(pwd) ./node_modules/@automattic/eslint-plugin-wpvip",
|
|
10
|
-
"lint": "eslint .",
|
|
11
|
-
"test": "
|
|
10
|
+
"lint": "npm run link-plugin && eslint .",
|
|
11
|
+
"test": "jest"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
@@ -29,14 +29,17 @@
|
|
|
29
29
|
"@rushstack/eslint-patch": "1.2.0",
|
|
30
30
|
"@typescript-eslint/eslint-plugin": "5.55.0",
|
|
31
31
|
"@typescript-eslint/parser": "5.55.0",
|
|
32
|
+
"eslint-config-prettier": "8.7.0",
|
|
32
33
|
"eslint-plugin-import": "2.27.5",
|
|
33
34
|
"eslint-plugin-jest": "27.2.1",
|
|
34
35
|
"eslint-plugin-jsdoc": "40.0.2",
|
|
35
36
|
"eslint-plugin-json": "3.1.0",
|
|
36
37
|
"eslint-plugin-jsx-a11y": "6.7.1",
|
|
38
|
+
"eslint-plugin-prettier": "4.2.1",
|
|
37
39
|
"eslint-plugin-react": "7.32.2",
|
|
38
40
|
"eslint-plugin-react-hooks": "4.6.0",
|
|
39
41
|
"eslint-plugin-security": "1.7.1",
|
|
42
|
+
"find-package-json": "1.2.0",
|
|
40
43
|
"globals": "13.20.0"
|
|
41
44
|
},
|
|
42
45
|
"peerDependencies": {
|
|
@@ -44,8 +47,6 @@
|
|
|
44
47
|
},
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"eslint": "8.35.0",
|
|
47
|
-
"eslint-config-prettier": "8.7.0",
|
|
48
|
-
"eslint-plugin-eslint-plugin": "5.0.8",
|
|
49
50
|
"jest": "29.5.0",
|
|
50
51
|
"prettier": "2.8.4",
|
|
51
52
|
"typescript": "4.9.5"
|
|
@@ -17,7 +17,7 @@ module.exports = {
|
|
|
17
17
|
schema: [],
|
|
18
18
|
fixable: 'code',
|
|
19
19
|
},
|
|
20
|
-
create(
|
|
20
|
+
create(context) {
|
|
21
21
|
const comments = context.getSourceCode().getAllComments();
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -45,8 +45,8 @@ module.exports = {
|
|
|
45
45
|
*
|
|
46
46
|
* @return {string} Expected comment node value.
|
|
47
47
|
*/
|
|
48
|
-
function getCommentValue(
|
|
49
|
-
return `*\n * ${
|
|
48
|
+
function getCommentValue(locality) {
|
|
49
|
+
return `*\n * ${locality} dependencies\n `;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -57,10 +57,10 @@ module.exports = {
|
|
|
57
57
|
*
|
|
58
58
|
* @return {WPPackageLocality} Package locality.
|
|
59
59
|
*/
|
|
60
|
-
function getPackageLocality(
|
|
61
|
-
if (
|
|
60
|
+
function getPackageLocality(source) {
|
|
61
|
+
if (source.startsWith('.')) {
|
|
62
62
|
return 'Internal';
|
|
63
|
-
} else if (
|
|
63
|
+
} else if (source.startsWith('@wordpress/')) {
|
|
64
64
|
return 'WordPress';
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -76,9 +76,9 @@ module.exports = {
|
|
|
76
76
|
*
|
|
77
77
|
* @return {boolean} Whether comment node satisfies locality.
|
|
78
78
|
*/
|
|
79
|
-
function isLocalityDependencyBlock(
|
|
79
|
+
function isLocalityDependencyBlock(node, locality) {
|
|
80
80
|
const { type, value } = node;
|
|
81
|
-
if (
|
|
81
|
+
if (type !== 'Block') {
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -88,16 +88,16 @@ module.exports = {
|
|
|
88
88
|
// - Ending period
|
|
89
89
|
// - "Node" dependencies as an alias for External.
|
|
90
90
|
|
|
91
|
-
if (
|
|
91
|
+
if (locality === 'External') {
|
|
92
92
|
locality = '(External|Node)';
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
96
96
|
const pattern = new RegExp(
|
|
97
|
-
`^\\*?\\n \\* ${
|
|
98
|
-
'i'
|
|
97
|
+
`^\\*?\\n \\* ${locality} dependencies\\.?\\n $`,
|
|
98
|
+
'i'
|
|
99
99
|
);
|
|
100
|
-
return pattern.test(
|
|
100
|
+
return pattern.test(value);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
@@ -109,12 +109,12 @@ module.exports = {
|
|
|
109
109
|
*
|
|
110
110
|
* @return {boolean} Whether node occurs before reference.
|
|
111
111
|
*/
|
|
112
|
-
function isBefore(
|
|
113
|
-
if (
|
|
112
|
+
function isBefore(node, reference) {
|
|
113
|
+
if (!node.range || !reference.range) {
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
return node.range[
|
|
117
|
+
return node.range[0] < reference.range[0];
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
@@ -128,22 +128,22 @@ module.exports = {
|
|
|
128
128
|
*
|
|
129
129
|
* @return {WPDependencyBlockCorrection | undefined} Correction, if applicable.
|
|
130
130
|
*/
|
|
131
|
-
function getDependencyBlockCorrection(
|
|
132
|
-
const value = getCommentValue(
|
|
131
|
+
function getDependencyBlockCorrection(node, locality) {
|
|
132
|
+
const value = getCommentValue(locality);
|
|
133
133
|
|
|
134
|
-
for (
|
|
135
|
-
if (
|
|
134
|
+
for (const comment of comments) {
|
|
135
|
+
if (!isBefore(comment, node)) {
|
|
136
136
|
// Exhausted options.
|
|
137
137
|
break;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
if (
|
|
140
|
+
if (!isLocalityDependencyBlock(comment, locality)) {
|
|
141
141
|
// Not usable (either not an block comment, or not one
|
|
142
142
|
// matching a tolerable pattern).
|
|
143
143
|
continue;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
if (
|
|
146
|
+
if (comment.value === value) {
|
|
147
147
|
// No change needed. (OK)
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
@@ -159,7 +159,7 @@ module.exports = {
|
|
|
159
159
|
/**
|
|
160
160
|
* @param {import('estree').Program} node Program node.
|
|
161
161
|
*/
|
|
162
|
-
Program(
|
|
162
|
+
Program(node) {
|
|
163
163
|
/**
|
|
164
164
|
* The set of package localities which have been reported for
|
|
165
165
|
* the current program. Each locality is reported at most one
|
|
@@ -181,29 +181,25 @@ module.exports = {
|
|
|
181
181
|
// Since we only care to enforce imports which occur at the
|
|
182
182
|
// top-level scope, match on Program and test its children,
|
|
183
183
|
// rather than matching the import nodes directly.
|
|
184
|
-
node.body.forEach(
|
|
184
|
+
node.body.forEach((child) => {
|
|
185
185
|
/** @type {string} */
|
|
186
186
|
let source;
|
|
187
|
-
switch (
|
|
187
|
+
switch (child.type) {
|
|
188
188
|
case 'ImportDeclaration':
|
|
189
|
-
source = /** @type {string} */ (
|
|
190
|
-
|
|
191
|
-
);
|
|
192
|
-
candidates.push( [ child, source ] );
|
|
189
|
+
source = /** @type {string} */ (child.source.value);
|
|
190
|
+
candidates.push([child, source]);
|
|
193
191
|
break;
|
|
194
192
|
|
|
195
193
|
case 'VariableDeclaration':
|
|
196
|
-
child.declarations.forEach(
|
|
194
|
+
child.declarations.forEach((declaration) => {
|
|
197
195
|
const { init } = declaration;
|
|
198
196
|
if (
|
|
199
|
-
!
|
|
197
|
+
!init ||
|
|
200
198
|
init.type !== 'CallExpression' ||
|
|
201
|
-
/** @type {import('estree').CallExpression} */ (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
init.callee
|
|
206
|
-
).name !== 'require'
|
|
199
|
+
/** @type {import('estree').CallExpression} */ (init).callee
|
|
200
|
+
.type !== 'Identifier' ||
|
|
201
|
+
/** @type {import('estree').Identifier} */ (init.callee)
|
|
202
|
+
.name !== 'require'
|
|
207
203
|
) {
|
|
208
204
|
return;
|
|
209
205
|
}
|
|
@@ -211,51 +207,45 @@ module.exports = {
|
|
|
211
207
|
const { arguments: args } = init;
|
|
212
208
|
if (
|
|
213
209
|
args.length === 1 &&
|
|
214
|
-
args[
|
|
215
|
-
typeof args[
|
|
210
|
+
args[0].type === 'Literal' &&
|
|
211
|
+
typeof args[0].value === 'string'
|
|
216
212
|
) {
|
|
217
|
-
source = args[
|
|
218
|
-
candidates.push(
|
|
213
|
+
source = args[0].value;
|
|
214
|
+
candidates.push([child, source]);
|
|
219
215
|
}
|
|
220
|
-
}
|
|
216
|
+
});
|
|
221
217
|
}
|
|
222
|
-
}
|
|
218
|
+
});
|
|
223
219
|
|
|
224
|
-
for (
|
|
225
|
-
const locality = getPackageLocality(
|
|
226
|
-
if (
|
|
220
|
+
for (const [child, source] of candidates) {
|
|
221
|
+
const locality = getPackageLocality(source);
|
|
222
|
+
if (verified.has(locality)) {
|
|
227
223
|
continue;
|
|
228
224
|
}
|
|
229
225
|
|
|
230
226
|
// Avoid verifying any other imports for the locality,
|
|
231
227
|
// regardless whether a correction must be made.
|
|
232
|
-
verified.add(
|
|
228
|
+
verified.add(locality);
|
|
233
229
|
|
|
234
230
|
// Determine whether a correction must be made.
|
|
235
|
-
const correction = getDependencyBlockCorrection(
|
|
236
|
-
|
|
237
|
-
locality,
|
|
238
|
-
);
|
|
239
|
-
if ( ! correction ) {
|
|
231
|
+
const correction = getDependencyBlockCorrection(child, locality);
|
|
232
|
+
if (!correction) {
|
|
240
233
|
continue;
|
|
241
234
|
}
|
|
242
235
|
|
|
243
|
-
context.report(
|
|
236
|
+
context.report({
|
|
244
237
|
node: child,
|
|
245
|
-
message: `Expected preceding "${
|
|
246
|
-
fix(
|
|
238
|
+
message: `Expected preceding "${locality} dependencies" comment block`,
|
|
239
|
+
fix(fixer) {
|
|
247
240
|
const { comment, value } = correction;
|
|
248
|
-
const text = `/*${
|
|
249
|
-
if (
|
|
250
|
-
return fixer.replaceTextRange(
|
|
251
|
-
comment.range,
|
|
252
|
-
text,
|
|
253
|
-
);
|
|
241
|
+
const text = `/*${value}*/`;
|
|
242
|
+
if (comment && comment.range) {
|
|
243
|
+
return fixer.replaceTextRange(comment.range, text);
|
|
254
244
|
}
|
|
255
245
|
|
|
256
|
-
return fixer.insertTextBefore(
|
|
246
|
+
return fixer.insertTextBefore(child, text + '\n');
|
|
257
247
|
},
|
|
258
|
-
}
|
|
248
|
+
});
|
|
259
249
|
}
|
|
260
250
|
},
|
|
261
251
|
};
|
package/rules/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Custom ESLint rules
|
|
3
3
|
*/
|
|
4
4
|
module.exports = {
|
|
5
|
-
'dependency-group': require(
|
|
6
|
-
'no-async-foreach': require(
|
|
7
|
-
'no-unguarded-get-range-at': require(
|
|
8
|
-
'no-unused-vars-before-return': require(
|
|
5
|
+
'dependency-group': require('./dependency-group'),
|
|
6
|
+
'no-async-foreach': require('./no-async-foreach'),
|
|
7
|
+
'no-unguarded-get-range-at': require('./no-unguarded-get-range-at'),
|
|
8
|
+
'no-unused-vars-before-return': require('./no-unused-vars-before-return'),
|
|
9
9
|
};
|
|
@@ -8,27 +8,25 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
create(
|
|
11
|
+
create(context) {
|
|
12
12
|
return {
|
|
13
|
-
ExpressionStatement(
|
|
13
|
+
ExpressionStatement(node) {
|
|
14
14
|
const { callee } = node.expression;
|
|
15
|
-
if (
|
|
15
|
+
if (!callee || !callee.property || !callee.property.name) {
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
-
if (
|
|
19
|
-
const functionArguments = node.expression.arguments.find(
|
|
20
|
-
(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if ( functionArguments ) {
|
|
28
|
-
if ( functionArguments.async ) {
|
|
18
|
+
if (callee.property.name === 'forEach') {
|
|
19
|
+
const functionArguments = node.expression.arguments.find((exp) => {
|
|
20
|
+
return (
|
|
21
|
+
exp.type === 'ArrowFunctionExpression' ||
|
|
22
|
+
exp.type === 'FunctionExpression'
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
if (functionArguments) {
|
|
26
|
+
if (functionArguments.async) {
|
|
29
27
|
context.report(
|
|
30
28
|
node,
|
|
31
|
-
'Avoid passing an async function to Array.prototype.forEach'
|
|
29
|
+
'Avoid passing an async function to Array.prototype.forEach'
|
|
32
30
|
);
|
|
33
31
|
}
|
|
34
32
|
}
|
|
@@ -8,15 +8,15 @@ module.exports = {
|
|
|
8
8
|
type: 'problem',
|
|
9
9
|
schema: [],
|
|
10
10
|
},
|
|
11
|
-
create(
|
|
11
|
+
create(context) {
|
|
12
12
|
return {
|
|
13
13
|
'CallExpression[callee.object.callee.property.name="getSelection"][callee.property.name="getRangeAt"]'(
|
|
14
|
-
node
|
|
14
|
+
node
|
|
15
15
|
) {
|
|
16
|
-
context.report(
|
|
16
|
+
context.report({
|
|
17
17
|
node,
|
|
18
18
|
message: 'Avoid unguarded getRangeAt',
|
|
19
|
-
}
|
|
19
|
+
});
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
22
|
},
|
|
@@ -23,16 +23,16 @@ const FUNCTION_SCOPE_JSX_IDENTIFIERS = new WeakMap();
|
|
|
23
23
|
*
|
|
24
24
|
* @return {ESLintScope|undefined} Function scope, if known.
|
|
25
25
|
*/
|
|
26
|
-
function getClosestFunctionScope(
|
|
26
|
+
function getClosestFunctionScope(context) {
|
|
27
27
|
let functionScope = context.getScope();
|
|
28
|
-
while (
|
|
28
|
+
while (functionScope.type !== 'function' && functionScope.upper) {
|
|
29
29
|
functionScope = functionScope.upper;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
return functionScope;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
module.exports = /** @type {import('eslint').Rule} */ (
|
|
35
|
+
module.exports = /** @type {import('eslint').Rule} */ ({
|
|
36
36
|
meta: {
|
|
37
37
|
type: 'problem',
|
|
38
38
|
schema: [
|
|
@@ -50,8 +50,8 @@ module.exports = /** @type {import('eslint').Rule} */ ( {
|
|
|
50
50
|
/**
|
|
51
51
|
* @param {ESLintRuleContext} context Rule context.
|
|
52
52
|
*/
|
|
53
|
-
create(
|
|
54
|
-
const options = context.options[
|
|
53
|
+
create(context) {
|
|
54
|
+
const options = context.options[0] || {};
|
|
55
55
|
const { excludePattern } = options;
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -65,41 +65,35 @@ module.exports = /** @type {import('eslint').Rule} */ ( {
|
|
|
65
65
|
*
|
|
66
66
|
* @return {boolean} Whether declarator is emempt from consideration.
|
|
67
67
|
*/
|
|
68
|
-
function isExemptObjectDestructureDeclarator(
|
|
69
|
-
return
|
|
70
|
-
node.id.type === 'ObjectPattern' &&
|
|
71
|
-
node.id.properties.length > 1
|
|
72
|
-
);
|
|
68
|
+
function isExemptObjectDestructureDeclarator(node) {
|
|
69
|
+
return node.id.type === 'ObjectPattern' && node.id.properties.length > 1;
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
return {
|
|
76
|
-
JSXIdentifier(
|
|
73
|
+
JSXIdentifier(node) {
|
|
77
74
|
// Currently, a scope's variable references does not include JSX
|
|
78
75
|
// identifiers. Account for this by visiting JSX identifiers
|
|
79
76
|
// first, and tracking them in a map per function scope, which
|
|
80
77
|
// is later merged with the known variable references.
|
|
81
|
-
const functionScope = getClosestFunctionScope(
|
|
82
|
-
if (
|
|
78
|
+
const functionScope = getClosestFunctionScope(context);
|
|
79
|
+
if (!functionScope) {
|
|
83
80
|
return;
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
if (
|
|
87
|
-
FUNCTION_SCOPE_JSX_IDENTIFIERS.set(
|
|
88
|
-
functionScope,
|
|
89
|
-
new Set(),
|
|
90
|
-
);
|
|
83
|
+
if (!FUNCTION_SCOPE_JSX_IDENTIFIERS.has(functionScope)) {
|
|
84
|
+
FUNCTION_SCOPE_JSX_IDENTIFIERS.set(functionScope, new Set());
|
|
91
85
|
}
|
|
92
86
|
|
|
93
|
-
FUNCTION_SCOPE_JSX_IDENTIFIERS.get(
|
|
87
|
+
FUNCTION_SCOPE_JSX_IDENTIFIERS.get(functionScope).add(node);
|
|
94
88
|
},
|
|
95
|
-
'ReturnStatement:exit'(
|
|
96
|
-
const functionScope = getClosestFunctionScope(
|
|
97
|
-
if (
|
|
89
|
+
'ReturnStatement:exit'(node) {
|
|
90
|
+
const functionScope = getClosestFunctionScope(context);
|
|
91
|
+
if (!functionScope) {
|
|
98
92
|
return;
|
|
99
93
|
}
|
|
100
94
|
|
|
101
|
-
for (
|
|
102
|
-
const declaratorCandidate = variable.defs.find(
|
|
95
|
+
for (const variable of functionScope.variables) {
|
|
96
|
+
const declaratorCandidate = variable.defs.find((def) => {
|
|
103
97
|
return (
|
|
104
98
|
def.node.type === 'VariableDeclarator' &&
|
|
105
99
|
// Allow declarations which are not initialized.
|
|
@@ -107,21 +101,21 @@ module.exports = /** @type {import('eslint').Rule} */ ( {
|
|
|
107
101
|
// Target function calls as "expensive".
|
|
108
102
|
def.node.init.type === 'CallExpression' &&
|
|
109
103
|
// Allow unused if part of an object destructuring.
|
|
110
|
-
!
|
|
104
|
+
!isExemptObjectDestructureDeclarator(def.node) &&
|
|
111
105
|
// Only target assignments preceding `return`.
|
|
112
|
-
def.node.range[
|
|
106
|
+
def.node.range[1] < node.range[1]
|
|
113
107
|
);
|
|
114
|
-
}
|
|
108
|
+
});
|
|
115
109
|
|
|
116
|
-
if (
|
|
110
|
+
if (!declaratorCandidate) {
|
|
117
111
|
continue;
|
|
118
112
|
}
|
|
119
113
|
|
|
120
114
|
if (
|
|
121
115
|
excludePattern !== undefined &&
|
|
122
116
|
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
123
|
-
new RegExp(
|
|
124
|
-
declaratorCandidate.node.init.callee.name
|
|
117
|
+
new RegExp(excludePattern).test(
|
|
118
|
+
declaratorCandidate.node.init.callee.name
|
|
125
119
|
)
|
|
126
120
|
) {
|
|
127
121
|
continue;
|
|
@@ -130,33 +124,32 @@ module.exports = /** @type {import('eslint').Rule} */ ( {
|
|
|
130
124
|
// The first entry in `references` is the declaration
|
|
131
125
|
// itself, which can be ignored.
|
|
132
126
|
const identifiers = variable.references
|
|
133
|
-
.slice(
|
|
134
|
-
.map(
|
|
127
|
+
.slice(1)
|
|
128
|
+
.map((reference) => reference.identifier);
|
|
135
129
|
|
|
136
130
|
// Merge with any JSX identifiers in scope, if any.
|
|
137
|
-
if (
|
|
131
|
+
if (FUNCTION_SCOPE_JSX_IDENTIFIERS.has(functionScope)) {
|
|
138
132
|
const jsxIdentifiers =
|
|
139
|
-
FUNCTION_SCOPE_JSX_IDENTIFIERS.get(
|
|
133
|
+
FUNCTION_SCOPE_JSX_IDENTIFIERS.get(functionScope);
|
|
140
134
|
|
|
141
|
-
identifiers.push(
|
|
135
|
+
identifiers.push(...jsxIdentifiers);
|
|
142
136
|
}
|
|
143
137
|
|
|
144
138
|
const isUsedBeforeReturn = identifiers.some(
|
|
145
|
-
( identifier
|
|
146
|
-
identifier.range[ 1 ] < node.range[ 1 ],
|
|
139
|
+
(identifier) => identifier.range[1] < node.range[1]
|
|
147
140
|
);
|
|
148
141
|
|
|
149
|
-
if (
|
|
142
|
+
if (isUsedBeforeReturn) {
|
|
150
143
|
continue;
|
|
151
144
|
}
|
|
152
145
|
|
|
153
146
|
context.report(
|
|
154
147
|
declaratorCandidate.node,
|
|
155
148
|
'Variables should not be assigned until just prior its first reference. ' +
|
|
156
|
-
'An early return statement may leave this variable unused.'
|
|
149
|
+
'An early return statement may leave this variable unused.'
|
|
157
150
|
);
|
|
158
151
|
}
|
|
159
152
|
},
|
|
160
153
|
};
|
|
161
154
|
},
|
|
162
|
-
}
|
|
155
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const debugLog = require('./debug-log');
|
|
3
|
+
const findPackageJson = require('find-package-json');
|
|
4
|
+
|
|
5
|
+
function findParent() {
|
|
6
|
+
const packages = [...findPackageJson()];
|
|
7
|
+
const firstPackage = packages.shift();
|
|
8
|
+
|
|
9
|
+
// A little hack to help us use this plugin to lint itself: When installed via
|
|
10
|
+
// NPM, .eslintrc.js will be missing (due to .npmignore).
|
|
11
|
+
if ('@automattic/eslint-plugin-wpvip' === firstPackage.name) {
|
|
12
|
+
const eslintRc = firstPackage.__path.replace(
|
|
13
|
+
/package\.json$/,
|
|
14
|
+
'.eslintrc.js'
|
|
15
|
+
);
|
|
16
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
17
|
+
if (fs.statSync(eslintRc)) {
|
|
18
|
+
return firstPackage;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return packages.pop() || {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const parent = findParent();
|
|
26
|
+
|
|
27
|
+
debugLog(`Found package.json: ${parent.__path || 'none'}`);
|
|
28
|
+
|
|
29
|
+
module.exports = function isPackageInstalled(packageName) {
|
|
30
|
+
const isDevDependency = !!parent.devDependencies?.[`${packageName}`];
|
|
31
|
+
const isProdDependency = !!parent.dependencies?.[`${packageName}`];
|
|
32
|
+
|
|
33
|
+
return isDevDependency || isProdDependency;
|
|
34
|
+
};
|
package/.eslintignore
DELETED
package/.eslintrc.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Do not copy this .eslintrc for your project. See the README for instructions.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
extends: [
|
|
7
|
-
'plugin:eslint-plugin/recommended', // linting for eslint plugins!
|
|
8
|
-
'plugin:@automattic/wpvip/base',
|
|
9
|
-
'plugin:@automattic/wpvip/formatting',
|
|
10
|
-
'plugin:@automattic/wpvip/testing',
|
|
11
|
-
'plugin:@automattic/wpvip/typescript',
|
|
12
|
-
],
|
|
13
|
-
parserOptions: {
|
|
14
|
-
project: './tsconfig.json',
|
|
15
|
-
},
|
|
16
|
-
root: true,
|
|
17
|
-
};
|
package/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
16
|
package/configs/base.js
DELETED
package/tsconfig.json
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
// Allow JavaScript files as we migrate.
|
|
4
|
-
"allowJs": true,
|
|
5
|
-
"checkJs": false,
|
|
6
|
-
|
|
7
|
-
// https://www.typescriptlang.org/tsconfig#isolatedModules
|
|
8
|
-
"isolatedModules": true,
|
|
9
|
-
|
|
10
|
-
// Custom output directory (Nest default is ./dist).
|
|
11
|
-
"outDir": "./build",
|
|
12
|
-
|
|
13
|
-
// Preserve comments in output.
|
|
14
|
-
"removeComments": true,
|
|
15
|
-
|
|
16
|
-
// Allow importing JSON files directly.
|
|
17
|
-
"resolveJsonModule": true,
|
|
18
|
-
|
|
19
|
-
// The options below come from the default Nest tsconfig, minus those that
|
|
20
|
-
// are overridden above.
|
|
21
|
-
"allowSyntheticDefaultImports": true,
|
|
22
|
-
"baseUrl": ".",
|
|
23
|
-
"declaration": true,
|
|
24
|
-
"emitDecoratorMetadata": true,
|
|
25
|
-
"experimentalDecorators": true,
|
|
26
|
-
"incremental": true,
|
|
27
|
-
"sourceMap": true,
|
|
28
|
-
"strictNullChecks": true
|
|
29
|
-
}
|
|
30
|
-
}
|