@automattic/eslint-plugin-wpvip 0.4.7 → 0.4.9
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/.eslintrc.js +2 -0
- package/configs/base.js +2 -133
- package/configs/cli.js +2 -2
- package/configs/{prettier-off.js → formatting.js} +1 -1
- package/configs/index.js +11 -6
- package/configs/javascript.js +222 -0
- package/configs/jsdoc.js +107 -7
- package/configs/jsx-ally.js +21 -0
- package/configs/prettier.js +12 -0
- package/configs/react.js +47 -0
- package/configs/testing.js +15 -0
- package/configs/typescript.js +51 -0
- package/configs/{weak.js → weak-javascript.js} +2 -3
- package/configs/weak-typescript.js +2 -7
- package/index.js +2 -2
- package/init.js +1 -1
- package/package.json +12 -3
- package/rules/dependency-group.js +263 -0
- package/rules/index.js +7 -2
- package/rules/no-async-foreach.js +10 -9
- package/rules/no-unguarded-get-range-at.js +23 -0
- package/rules/no-unused-vars-before-return.js +162 -0
package/.eslintrc.js
CHANGED
|
@@ -6,6 +6,8 @@ module.exports = {
|
|
|
6
6
|
extends: [
|
|
7
7
|
'plugin:eslint-plugin/recommended', // linting for eslint plugins!
|
|
8
8
|
'plugin:@automattic/wpvip/base',
|
|
9
|
+
'plugin:@automattic/wpvip/testing',
|
|
10
|
+
'plugin:@automattic/wpvip/typescript',
|
|
9
11
|
],
|
|
10
12
|
parserOptions: {
|
|
11
13
|
project: './tsconfig.json',
|
package/configs/base.js
CHANGED
|
@@ -1,138 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const baseConfig = {
|
|
4
|
-
env: {
|
|
5
|
-
node: true,
|
|
6
|
-
},
|
|
1
|
+
module.exports = {
|
|
7
2
|
extends: [
|
|
8
|
-
'eslint:recommended',
|
|
9
|
-
'plugin:@wordpress/eslint-plugin/recommended',
|
|
10
3
|
'plugin:json/recommended',
|
|
11
4
|
'plugin:security/recommended',
|
|
5
|
+
require.resolve( './javascript' ),
|
|
12
6
|
],
|
|
13
|
-
/**
|
|
14
|
-
* We must explicitly add this plugin to use our custom rules.
|
|
15
|
-
*/
|
|
16
|
-
plugins: ['@automattic/wpvip'],
|
|
17
|
-
/**
|
|
18
|
-
* Please include a short description of the rule. For rules that downgrade or
|
|
19
|
-
* disable errors, include a brief justification or reasoning.
|
|
20
|
-
*/
|
|
21
|
-
rules: {
|
|
22
|
-
// Async/await must not be used in a `.forEach` method, because the result
|
|
23
|
-
// will not be awaited in the outer scope.
|
|
24
|
-
'@automattic/wpvip/no-async-foreach': 'error',
|
|
25
|
-
|
|
26
|
-
// Identifiers should be in camelCase. Object properties are excluded
|
|
27
|
-
// (including when destructuring) since they often come from external
|
|
28
|
-
// sources (like APIs).
|
|
29
|
-
camelcase: [
|
|
30
|
-
'warn',
|
|
31
|
-
{
|
|
32
|
-
properties: 'never',
|
|
33
|
-
ignoreDestructuring: true,
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
|
|
37
|
-
// Maximum cyclomatic complexity must not be above 20.
|
|
38
|
-
complexity: 'error',
|
|
39
|
-
|
|
40
|
-
// Files must end in a newline.
|
|
41
|
-
'eol-last': ['error', 'always'],
|
|
42
|
-
|
|
43
|
-
// Identifiers should be between 2 and 40 characters in length.
|
|
44
|
-
'id-length': [
|
|
45
|
-
'warn',
|
|
46
|
-
{
|
|
47
|
-
min: 2,
|
|
48
|
-
max: 40,
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
|
|
52
|
-
// Disable JSDoc rule to require param definitions. While other JSDoc rules
|
|
53
|
-
// are still present and active, they are effectively dormant unless
|
|
54
|
-
// triggered by invalid or insufficient JSDoc. In other words, if you don't
|
|
55
|
-
// attempt JSDoc, none will be required by the linter.
|
|
56
|
-
//
|
|
57
|
-
// Reenable this rule with the jsdoc preset.
|
|
58
|
-
'jsdoc/require-param': 'off',
|
|
59
|
-
|
|
60
|
-
// Lines containing code should be a maximum of 200 characters in length.
|
|
61
|
-
'max-len': [
|
|
62
|
-
'warn',
|
|
63
|
-
{
|
|
64
|
-
code: 200,
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
|
|
68
|
-
// Async/await must not be used in a loop, because it leads to sequential
|
|
69
|
-
// execution, when parallel execution is almost always preferred.
|
|
70
|
-
'no-await-in-loop': 'error',
|
|
71
|
-
|
|
72
|
-
// `console.log` should not be used directly in code. Ideally, delegate to a
|
|
73
|
-
// logging function that logs on your behalf (and ignore this rule there).
|
|
74
|
-
'no-console': 'warn',
|
|
75
|
-
|
|
76
|
-
// A single `import` statement should be used when importing multiple things
|
|
77
|
-
// from a module.
|
|
78
|
-
'no-duplicate-imports': 'warn',
|
|
79
|
-
|
|
80
|
-
// `process.exit` must not be used.
|
|
81
|
-
'no-process-exit': 'error',
|
|
82
|
-
|
|
83
|
-
// Negating the left operand of a statment frequently leads to logical
|
|
84
|
-
// errors. Example: `!key in obj` vs. `!(key in obj)`.
|
|
85
|
-
'no-unsafe-negation': 'error',
|
|
86
|
-
|
|
87
|
-
// Defined variables must not go unused.
|
|
88
|
-
'no-unused-vars': 'error',
|
|
89
|
-
|
|
90
|
-
// Arrow functions should be used for function arguments and callbacks.
|
|
91
|
-
'prefer-arrow-callback': 'warn',
|
|
92
|
-
|
|
93
|
-
// `parseInt` calls must always supply a radix argument.
|
|
94
|
-
radix: 'error',
|
|
95
|
-
|
|
96
|
-
// Comments should always include consistent spacing for readability.
|
|
97
|
-
'spaced-comment': 'warn',
|
|
98
|
-
|
|
99
|
-
// The result of `typeof` must always be compared to a literal string.
|
|
100
|
-
'valid-typeof': [
|
|
101
|
-
'error',
|
|
102
|
-
{
|
|
103
|
-
requireStringLiterals: true,
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
overrides: [],
|
|
108
7
|
};
|
|
109
|
-
|
|
110
|
-
// Make our default TypeScript rules more useful.
|
|
111
|
-
if (isPackageInstalled('typescript')) {
|
|
112
|
-
baseConfig.overrides.push({
|
|
113
|
-
extends: [
|
|
114
|
-
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
|
115
|
-
'plugin:@typescript-eslint/strict',
|
|
116
|
-
],
|
|
117
|
-
files: ['**/*.ts', '**/*.tsx'],
|
|
118
|
-
rules: {
|
|
119
|
-
// TypeScript `any` type must not be used. This is a warning in the base
|
|
120
|
-
// config, and is elevated to an error here.
|
|
121
|
-
'@typescript-eslint/no-explicit-any': 'error',
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Make our default React rules more useful.
|
|
127
|
-
if (isPackageInstalled('react')) {
|
|
128
|
-
// @TODO
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Add Jest-specific rules if it is installed.
|
|
132
|
-
if (isPackageInstalled('jest')) {
|
|
133
|
-
baseConfig.env['jest/globals'] = true;
|
|
134
|
-
baseConfig.extends.push('plugin:jest/recommended');
|
|
135
|
-
baseConfig.plugins.push('jest');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
module.exports = baseConfig;
|
package/configs/cli.js
CHANGED
|
@@ -5,9 +5,9 @@ module.exports = {
|
|
|
5
5
|
*/
|
|
6
6
|
rules: {
|
|
7
7
|
// Allow child_process and non-literal `exec` arguments.
|
|
8
|
-
'security/detect-child-process':
|
|
8
|
+
'security/detect-child-process': 'off',
|
|
9
9
|
|
|
10
10
|
// Process.exit is used in CLI context to stop execution.
|
|
11
|
-
'no-process-exit':
|
|
11
|
+
'no-process-exit': 'off',
|
|
12
12
|
},
|
|
13
13
|
};
|
package/configs/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
base: require('./base'),
|
|
3
|
-
cli: require('./cli'),
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
base: require( './base' ),
|
|
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
|
+
testing: require( './testing' ),
|
|
10
|
+
typescript: require( './typescript' ),
|
|
11
|
+
'weak-javascript': require( './weak-javascript' ),
|
|
12
|
+
'weak-typescript': require( './weak-typescript' ),
|
|
8
13
|
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* - https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/jshint.js
|
|
4
|
+
* - https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/es5.js
|
|
5
|
+
* - https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/esnext.js
|
|
6
|
+
* - https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/recommended-with-formatting.js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
env: {
|
|
11
|
+
es2022: true, // 100% covered by Node.js v16
|
|
12
|
+
node: true,
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
parser: '@babel/eslint-parser',
|
|
16
|
+
|
|
17
|
+
parserOptions: {
|
|
18
|
+
requireConfigFile: false,
|
|
19
|
+
sourceType: 'module',
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Note: We must explicitly add this plugin to use our custom rules.
|
|
24
|
+
*/
|
|
25
|
+
plugins: [ '@automattic/wpvip', 'import' ],
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Please include a short description of the rule. For rules that downgrade or
|
|
29
|
+
* disable errors, include a brief justification or reasoning.
|
|
30
|
+
*/
|
|
31
|
+
rules: {
|
|
32
|
+
// These rules are from eslint:recommended, but are enumerated here for
|
|
33
|
+
// visibility:
|
|
34
|
+
// https://github.com/eslint/eslint/blob/main/packages/js/src/configs/eslint-recommended.js
|
|
35
|
+
'constructor-super': 'error',
|
|
36
|
+
'for-direction': 'error',
|
|
37
|
+
'getter-return': 'error',
|
|
38
|
+
'no-async-promise-executor': 'error',
|
|
39
|
+
'no-case-declarations': 'error',
|
|
40
|
+
'no-class-assign': 'error',
|
|
41
|
+
'no-compare-neg-zero': 'error',
|
|
42
|
+
// 'no-cond-assign': 'error', // overridden below
|
|
43
|
+
'no-const-assign': 'error',
|
|
44
|
+
'no-constant-condition': 'error',
|
|
45
|
+
'no-control-regex': 'error',
|
|
46
|
+
'no-debugger': 'error',
|
|
47
|
+
'no-delete-var': 'error',
|
|
48
|
+
'no-dupe-args': 'error',
|
|
49
|
+
'no-dupe-class-members': 'error',
|
|
50
|
+
'no-dupe-else-if': 'error',
|
|
51
|
+
'no-dupe-keys': 'error',
|
|
52
|
+
'no-duplicate-case': 'error',
|
|
53
|
+
'no-empty': 'error',
|
|
54
|
+
'no-empty-character-class': 'error',
|
|
55
|
+
'no-empty-pattern': 'error',
|
|
56
|
+
'no-ex-assign': 'error',
|
|
57
|
+
'no-extra-boolean-cast': 'error',
|
|
58
|
+
'no-extra-semi': 'error',
|
|
59
|
+
'no-fallthrough': 'error',
|
|
60
|
+
'no-func-assign': 'error',
|
|
61
|
+
'no-global-assign': 'error',
|
|
62
|
+
'no-import-assign': 'error',
|
|
63
|
+
'no-inner-declarations': 'error',
|
|
64
|
+
'no-invalid-regexp': 'error',
|
|
65
|
+
'no-irregular-whitespace': 'error',
|
|
66
|
+
'no-loss-of-precision': 'error',
|
|
67
|
+
'no-misleading-character-class': 'error',
|
|
68
|
+
'no-mixed-spaces-and-tabs': 'error',
|
|
69
|
+
'no-new-symbol': 'error',
|
|
70
|
+
'no-nonoctal-decimal-escape': 'error',
|
|
71
|
+
'no-obj-calls': 'error',
|
|
72
|
+
'no-octal': 'error',
|
|
73
|
+
'no-prototype-builtins': 'error',
|
|
74
|
+
'no-redeclare': 'error',
|
|
75
|
+
'no-regex-spaces': 'error',
|
|
76
|
+
'no-self-assign': 'error',
|
|
77
|
+
'no-setter-return': 'error',
|
|
78
|
+
'no-shadow-restricted-names': 'error',
|
|
79
|
+
'no-sparse-arrays': 'error',
|
|
80
|
+
'no-this-before-super': 'error',
|
|
81
|
+
'no-undef': 'error',
|
|
82
|
+
'no-unexpected-multiline': 'error',
|
|
83
|
+
'no-unreachable': 'error',
|
|
84
|
+
'no-unsafe-finally': 'error',
|
|
85
|
+
'no-unsafe-negation': 'error',
|
|
86
|
+
'no-unsafe-optional-chaining': 'error',
|
|
87
|
+
'no-unused-labels': 'error',
|
|
88
|
+
// 'no-unused-vars': 'error', // overridden below
|
|
89
|
+
'no-useless-backreference': 'error',
|
|
90
|
+
'no-useless-catch': 'error',
|
|
91
|
+
'no-useless-escape': 'error',
|
|
92
|
+
'no-with': 'error',
|
|
93
|
+
'require-yield': 'error',
|
|
94
|
+
'use-isnan': 'error',
|
|
95
|
+
// 'valid-typeof': 'error', // overridden below
|
|
96
|
+
|
|
97
|
+
// Async/await must not be used in a `.forEach` method, because the result
|
|
98
|
+
// will not be awaited in the outer scope.
|
|
99
|
+
'@automattic/wpvip/no-async-foreach': 'error',
|
|
100
|
+
|
|
101
|
+
// Variables that are potentially not needed before a return statement can
|
|
102
|
+
// be assigned after the return.
|
|
103
|
+
'@automattic/wpvip/no-unused-vars-before-return': 'error',
|
|
104
|
+
|
|
105
|
+
// Unguarded getRangeAt calls can throw errors in some browsers.
|
|
106
|
+
'@automattic/wpvip/no-unguarded-get-range-at': 'error',
|
|
107
|
+
|
|
108
|
+
'array-callback-return': 'error',
|
|
109
|
+
|
|
110
|
+
// Maximum cyclomatic complexity must not be above 20.
|
|
111
|
+
complexity: 'error',
|
|
112
|
+
|
|
113
|
+
'dot-notation': 'error',
|
|
114
|
+
|
|
115
|
+
eqeqeq: 'error',
|
|
116
|
+
|
|
117
|
+
'func-call-spacing': 'error',
|
|
118
|
+
|
|
119
|
+
// Identifiers should be between 2 and 40 characters in length in order to
|
|
120
|
+
// provide a concise semantic meaning.
|
|
121
|
+
'id-length': [
|
|
122
|
+
'warn',
|
|
123
|
+
{
|
|
124
|
+
min: 2,
|
|
125
|
+
max: 40,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
|
|
129
|
+
'import/default': 'warn',
|
|
130
|
+
|
|
131
|
+
'import/named': 'warn',
|
|
132
|
+
|
|
133
|
+
'import/no-extraneous-dependencies': [
|
|
134
|
+
'error',
|
|
135
|
+
{
|
|
136
|
+
peerDependencies: true,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
|
|
140
|
+
'import/no-unresolved': 'error',
|
|
141
|
+
|
|
142
|
+
// Enforce Unix linebreaks. Included here and not in "formatting" since it
|
|
143
|
+
// is not controversial and helps with interchange.
|
|
144
|
+
'linebreak-style': [ 'error', 'unix' ],
|
|
145
|
+
|
|
146
|
+
'no-alert': 'error',
|
|
147
|
+
|
|
148
|
+
// Async/await must not be used in a loop, because it leads to sequential
|
|
149
|
+
// execution, when parallel execution is almost always preferred.
|
|
150
|
+
'no-await-in-loop': 'error',
|
|
151
|
+
|
|
152
|
+
'no-bitwise': 'error',
|
|
153
|
+
|
|
154
|
+
'no-caller': 'error',
|
|
155
|
+
|
|
156
|
+
'no-cond-assign': [ 'error', 'except-parens' ],
|
|
157
|
+
|
|
158
|
+
// `console.log` should not be used directly in code. Ideally, delegate to a
|
|
159
|
+
// logging function that logs on your behalf (and ignore this rule there).
|
|
160
|
+
'no-console': 'warn',
|
|
161
|
+
|
|
162
|
+
// A single `import` statement should be used when importing multiple things
|
|
163
|
+
// from a module.
|
|
164
|
+
'no-duplicate-imports': 'error',
|
|
165
|
+
|
|
166
|
+
'no-else-return': 'error',
|
|
167
|
+
|
|
168
|
+
'no-eq-null': 'error',
|
|
169
|
+
|
|
170
|
+
'no-eval': 'error',
|
|
171
|
+
|
|
172
|
+
'no-lonely-if': 'error',
|
|
173
|
+
|
|
174
|
+
'no-mixed-operators': 'error',
|
|
175
|
+
|
|
176
|
+
'no-multi-spaces': 'error',
|
|
177
|
+
|
|
178
|
+
'no-multi-str': 'error',
|
|
179
|
+
|
|
180
|
+
'no-multiple-empty-lines': [ 'error', { max: 1 } ],
|
|
181
|
+
|
|
182
|
+
'no-nested-ternary': 'error',
|
|
183
|
+
|
|
184
|
+
'no-shadow': 'error',
|
|
185
|
+
|
|
186
|
+
'no-undef-init': 'error',
|
|
187
|
+
|
|
188
|
+
'no-unused-expressions': 'error',
|
|
189
|
+
|
|
190
|
+
'no-unused-vars': [ 'error', { ignoreRestSiblings: true } ],
|
|
191
|
+
|
|
192
|
+
'no-useless-computed-key': 'error',
|
|
193
|
+
|
|
194
|
+
'no-useless-constructor': 'error',
|
|
195
|
+
|
|
196
|
+
'no-useless-return': 'error',
|
|
197
|
+
|
|
198
|
+
'no-var': 'error',
|
|
199
|
+
|
|
200
|
+
// Non-controversial formatting rule.
|
|
201
|
+
'no-whitespace-before-property': 'error',
|
|
202
|
+
|
|
203
|
+
'one-var': [ 'error', 'never' ],
|
|
204
|
+
|
|
205
|
+
'prefer-const': [ 'error', { destructuring: 'all' } ],
|
|
206
|
+
|
|
207
|
+
radix: 'error',
|
|
208
|
+
|
|
209
|
+
// Non-controversial formatting rule.
|
|
210
|
+
semi: 'error',
|
|
211
|
+
|
|
212
|
+
// The result of `typeof` must always be compared to a literal string.
|
|
213
|
+
'valid-typeof': [
|
|
214
|
+
'error',
|
|
215
|
+
{
|
|
216
|
+
requireStringLiterals: true,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
|
|
220
|
+
'wrap-iife': [ 'error', 'any' ],
|
|
221
|
+
},
|
|
222
|
+
};
|
package/configs/jsdoc.js
CHANGED
|
@@ -1,12 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/jsdoc.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const globals = require( 'globals' );
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Helpful utilities that are globally defined and known to the TypeScript compiler.
|
|
10
|
+
*
|
|
11
|
+
* @see http://www.typescriptlang.org/docs/handbook/utility-types.html
|
|
12
|
+
*/
|
|
13
|
+
const typescriptUtilityTypes = [
|
|
14
|
+
'ArrayLike',
|
|
15
|
+
'Exclude',
|
|
16
|
+
'Extract',
|
|
17
|
+
'InstanceType',
|
|
18
|
+
'Iterable',
|
|
19
|
+
'IterableIterator',
|
|
20
|
+
'NonNullable',
|
|
21
|
+
'Omit',
|
|
22
|
+
'Parameters',
|
|
23
|
+
'Partial',
|
|
24
|
+
'Pick',
|
|
25
|
+
'PromiseLike',
|
|
26
|
+
'Readonly',
|
|
27
|
+
'ReadonlyArray',
|
|
28
|
+
'ReadonlyMap',
|
|
29
|
+
'ReadonlySet',
|
|
30
|
+
'Record',
|
|
31
|
+
'Required',
|
|
32
|
+
'ReturnType',
|
|
33
|
+
'ThisType',
|
|
34
|
+
'unknown',
|
|
35
|
+
'never',
|
|
36
|
+
'NodeJS',
|
|
37
|
+
'AsyncIterableIterator',
|
|
38
|
+
'NodeRequire',
|
|
39
|
+
'true',
|
|
40
|
+
'false',
|
|
41
|
+
];
|
|
42
|
+
|
|
1
43
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
44
|
+
extends: [ 'plugin:jsdoc/recommended' ],
|
|
45
|
+
settings: {
|
|
46
|
+
jsdoc: {
|
|
47
|
+
preferredTypes: {
|
|
48
|
+
object: 'Object',
|
|
49
|
+
},
|
|
50
|
+
tagNamePreference: {
|
|
51
|
+
returns: 'return',
|
|
52
|
+
yields: 'yield',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
6
56
|
rules: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
57
|
+
'jsdoc/no-undefined-types': [
|
|
58
|
+
'error',
|
|
59
|
+
{
|
|
60
|
+
definedTypes: [
|
|
61
|
+
// Required to reference browser types because we don't have the `browser` environment enabled for the project.
|
|
62
|
+
// Here we filter out all browser globals that don't begin with an uppercase letter because those
|
|
63
|
+
// generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`).
|
|
64
|
+
...Object.keys( globals.browser ).filter( ( key ) =>
|
|
65
|
+
/^[A-Z]/.test( key ),
|
|
66
|
+
),
|
|
67
|
+
...typescriptUtilityTypes,
|
|
68
|
+
'void',
|
|
69
|
+
'JSX',
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
'jsdoc/require-jsdoc': 'off',
|
|
74
|
+
'jsdoc/require-param-description': 'off',
|
|
75
|
+
'jsdoc/require-returns': 'off',
|
|
76
|
+
'jsdoc/require-yields': 'off',
|
|
77
|
+
'jsdoc/tag-lines': 'off',
|
|
78
|
+
'jsdoc/no-multi-asterisks': [
|
|
79
|
+
'error',
|
|
80
|
+
{ preventAtMiddleLines: false },
|
|
81
|
+
],
|
|
82
|
+
'jsdoc/check-access': 'error',
|
|
83
|
+
'jsdoc/check-alignment': 'error',
|
|
84
|
+
'jsdoc/check-line-alignment': [
|
|
85
|
+
'error',
|
|
86
|
+
'always',
|
|
87
|
+
{
|
|
88
|
+
tags: [ 'param', 'arg', 'argument', 'property', 'prop' ],
|
|
89
|
+
preserveMainDescriptionPostDelimiter: true,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
'jsdoc/check-param-names': 'error',
|
|
93
|
+
'jsdoc/check-property-names': 'error',
|
|
94
|
+
'jsdoc/check-tag-names': 'error',
|
|
95
|
+
'jsdoc/check-types': 'error',
|
|
96
|
+
'jsdoc/check-values': 'off',
|
|
97
|
+
'jsdoc/empty-tags': 'error',
|
|
98
|
+
'jsdoc/implements-on-classes': 'error',
|
|
99
|
+
'jsdoc/newline-after-description': 'error',
|
|
10
100
|
'jsdoc/require-param': 'error',
|
|
101
|
+
'jsdoc/require-param-name': 'error',
|
|
102
|
+
'jsdoc/require-param-type': 'error',
|
|
103
|
+
'jsdoc/require-property': 'error',
|
|
104
|
+
'jsdoc/require-property-description': 'error',
|
|
105
|
+
'jsdoc/require-property-name': 'error',
|
|
106
|
+
'jsdoc/require-property-type': 'error',
|
|
107
|
+
'jsdoc/require-returns-check': 'error',
|
|
108
|
+
'jsdoc/require-returns-description': 'error',
|
|
109
|
+
'jsdoc/require-returns-type': 'error',
|
|
110
|
+
'jsdoc/valid-types': 'error',
|
|
11
111
|
},
|
|
12
112
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/jsx-a11y.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
extends: [ 'plugin:jsx-a11y/recommended' ],
|
|
8
|
+
plugins: [ 'jsx-a11y' ],
|
|
9
|
+
rules: {
|
|
10
|
+
'jsx-a11y/label-has-associated-control': [
|
|
11
|
+
'error',
|
|
12
|
+
{
|
|
13
|
+
assert: 'htmlFor',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
'jsx-a11y/media-has-caption': 'off',
|
|
17
|
+
'jsx-a11y/no-noninteractive-tabindex': 'off',
|
|
18
|
+
'jsx-a11y/role-has-required-aria-props': 'off',
|
|
19
|
+
'jsx-quotes': 'error',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/recommended.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
extends: [ 'plugin:prettier/recommended' ],
|
|
8
|
+
plugins: [ 'prettier' ],
|
|
9
|
+
rules: {
|
|
10
|
+
'prettier/prettier': 'error',
|
|
11
|
+
},
|
|
12
|
+
};
|
package/configs/react.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/react.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
extends: [
|
|
8
|
+
'plugin:react/recommended',
|
|
9
|
+
'plugin:react-hooks/recommended',
|
|
10
|
+
require( './jsx-ally' ),
|
|
11
|
+
],
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaFeatures: {
|
|
14
|
+
jsx: true,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
settings: {
|
|
18
|
+
react: {
|
|
19
|
+
version: 'detect',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
plugins: [ '@automattic/wpvip', 'react', 'react-hooks' ],
|
|
23
|
+
rules: {
|
|
24
|
+
'@automattic/wpvip/no-unused-vars-before-return': [
|
|
25
|
+
'error',
|
|
26
|
+
{
|
|
27
|
+
excludePattern: '^use',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
'react/display-name': 'off',
|
|
31
|
+
'react/jsx-curly-spacing': [
|
|
32
|
+
'error',
|
|
33
|
+
{
|
|
34
|
+
when: 'always',
|
|
35
|
+
children: true,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
'react/jsx-equals-spacing': 'error',
|
|
39
|
+
'react/jsx-indent': [ 'error', 'tab' ],
|
|
40
|
+
'react/jsx-indent-props': [ 'error', 'tab' ],
|
|
41
|
+
'react/jsx-key': 'error',
|
|
42
|
+
'react/jsx-tag-spacing': 'error',
|
|
43
|
+
'react/no-children-prop': 'off',
|
|
44
|
+
'react/prop-types': 'off',
|
|
45
|
+
'react/react-in-jsx-scope': 'off',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* - https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/test-unit.js
|
|
4
|
+
* - https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/test-e2e.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
extends: [ 'plugin:jest/recommended' ],
|
|
9
|
+
|
|
10
|
+
env: {
|
|
11
|
+
'jest/globals': true,
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
plugins: [ 'jest' ],
|
|
15
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/configs/recommended.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
ignorePatterns: [ '**/*.d.ts' ],
|
|
8
|
+
|
|
9
|
+
overrides: [
|
|
10
|
+
{
|
|
11
|
+
extends: [
|
|
12
|
+
'plugin:@typescript-eslint/eslint-recommended',
|
|
13
|
+
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
|
14
|
+
'plugin:@typescript-eslint/strict',
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
files: [ '**/*.ts', '**/*.tsx' ],
|
|
18
|
+
|
|
19
|
+
parser: '@typescript-eslint/parser',
|
|
20
|
+
|
|
21
|
+
rules: {
|
|
22
|
+
// TypeScript `any` type must not be used. This is a warning in the base
|
|
23
|
+
// config, and is elevated to an error here.
|
|
24
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
25
|
+
|
|
26
|
+
// Don't require redundant JSDoc types in TypeScript files.
|
|
27
|
+
'jsdoc/require-param-type': 'off',
|
|
28
|
+
'jsdoc/require-returns-type': 'off',
|
|
29
|
+
|
|
30
|
+
// Use TypeScript-specific rules.
|
|
31
|
+
'no-duplicate-imports': 'off',
|
|
32
|
+
'no-shadow': 'off',
|
|
33
|
+
'@typescript-eslint/no-duplicate-imports': 'error',
|
|
34
|
+
'@typescript-eslint/no-shadow': 'error',
|
|
35
|
+
|
|
36
|
+
// Handled by TS itself.
|
|
37
|
+
'no-unused-vars': 'off',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
plugins: [ '@typescript-eslint', 'jsdoc' ],
|
|
43
|
+
|
|
44
|
+
settings: {
|
|
45
|
+
'import/resolver': {
|
|
46
|
+
node: {
|
|
47
|
+
extensions: [ '.js', '.jsx', '.ts', '.tsx' ],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = {
|
|
10
10
|
/**
|
|
11
|
-
* Downgrade rules from the base preset to "
|
|
12
|
-
*
|
|
13
|
-
* disable it.
|
|
11
|
+
* Downgrade rules from the base preset to "warn". Do not disable rules (set
|
|
12
|
+
* to "off"). If a rule is already set to a warning, do not disable it.
|
|
14
13
|
*/
|
|
15
14
|
rules: {},
|
|
16
15
|
};
|
|
@@ -6,13 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
module.exports = {
|
|
8
8
|
/**
|
|
9
|
-
* Downgrade rules from the base preset to "
|
|
10
|
-
*
|
|
11
|
-
* disable it.
|
|
12
|
-
*
|
|
13
|
-
* An example is the `@typescript-eslint/no-explicit-any` rule, which is set
|
|
14
|
-
* to `warning` in the base preset (via `@typescript-eslint/recommended`) and
|
|
15
|
-
* is intentionally not overridden here.
|
|
9
|
+
* Downgrade rules from the base preset to "warn". Do not disable rules (set
|
|
10
|
+
* to "off"). If a rule is already set to a warning, do not disable it.
|
|
16
11
|
*/
|
|
17
12
|
rules: {},
|
|
18
13
|
};
|
package/index.js
CHANGED
package/init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
require('@rushstack/eslint-patch/modern-module-resolution');
|
|
1
|
+
require( '@rushstack/eslint-patch/modern-module-resolution' );
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/eslint-plugin-wpvip",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "ESLint plugin for internal WordPress VIP projects",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,17 +25,26 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/Automattic/eslint-config-wpvip#readme",
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@babel/eslint-parser": "7.21.3",
|
|
28
29
|
"@rushstack/eslint-patch": "1.2.0",
|
|
29
|
-
"@
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "5.55.0",
|
|
31
|
+
"@typescript-eslint/parser": "5.55.0",
|
|
32
|
+
"eslint-plugin-import": "2.27.5",
|
|
30
33
|
"eslint-plugin-jest": "27.2.1",
|
|
34
|
+
"eslint-plugin-jsdoc": "40.0.2",
|
|
31
35
|
"eslint-plugin-json": "3.1.0",
|
|
32
|
-
"eslint-plugin-
|
|
36
|
+
"eslint-plugin-jsx-a11y": "6.7.1",
|
|
37
|
+
"eslint-plugin-react": "7.32.2",
|
|
38
|
+
"eslint-plugin-react-hooks": "4.6.0",
|
|
39
|
+
"eslint-plugin-security": "1.7.1",
|
|
40
|
+
"globals": "13.20.0"
|
|
33
41
|
},
|
|
34
42
|
"peerDependencies": {
|
|
35
43
|
"eslint": ">=8"
|
|
36
44
|
},
|
|
37
45
|
"devDependencies": {
|
|
38
46
|
"eslint": "8.35.0",
|
|
47
|
+
"eslint-config-prettier": "8.7.0",
|
|
39
48
|
"eslint-plugin-eslint-plugin": "5.0.8",
|
|
40
49
|
"jest": "29.5.0",
|
|
41
50
|
"prettier": "2.8.4",
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/rules/dependency-group.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('estree').Comment} Comment */
|
|
7
|
+
/** @typedef {import('estree').Node} Node */
|
|
8
|
+
|
|
9
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
10
|
+
module.exports = {
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'layout',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Enforce dependencies docblocks formatting',
|
|
15
|
+
url: 'https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/dependency-group.md',
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
},
|
|
20
|
+
create( context ) {
|
|
21
|
+
const comments = context.getSourceCode().getAllComments();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Locality classification of an import, one of "External",
|
|
25
|
+
* "WordPress", "Internal".
|
|
26
|
+
*
|
|
27
|
+
* @typedef {string} WPPackageLocality
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Object describing a dependency block correction to be made.
|
|
32
|
+
*
|
|
33
|
+
* @typedef WPDependencyBlockCorrection
|
|
34
|
+
*
|
|
35
|
+
* @property {Comment} [comment] Comment node on which to replace value,
|
|
36
|
+
* if one can be salvaged.
|
|
37
|
+
* @property {string} value Expected comment node value.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Given a desired locality, generates the expected comment node value
|
|
42
|
+
* property.
|
|
43
|
+
*
|
|
44
|
+
* @param {WPPackageLocality} locality Desired package locality.
|
|
45
|
+
*
|
|
46
|
+
* @return {string} Expected comment node value.
|
|
47
|
+
*/
|
|
48
|
+
function getCommentValue( locality ) {
|
|
49
|
+
return `*\n * ${ locality } dependencies\n `;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Given an import source string, returns the locality classification
|
|
54
|
+
* of the import sort.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} source Import source string.
|
|
57
|
+
*
|
|
58
|
+
* @return {WPPackageLocality} Package locality.
|
|
59
|
+
*/
|
|
60
|
+
function getPackageLocality( source ) {
|
|
61
|
+
if ( source.startsWith( '.' ) ) {
|
|
62
|
+
return 'Internal';
|
|
63
|
+
} else if ( source.startsWith( '@wordpress/' ) ) {
|
|
64
|
+
return 'WordPress';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return 'External';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Returns true if the given comment node satisfies a desired locality,
|
|
72
|
+
* or false otherwise.
|
|
73
|
+
*
|
|
74
|
+
* @param {Comment} node Comment node to check.
|
|
75
|
+
* @param {WPPackageLocality} locality Desired package locality.
|
|
76
|
+
*
|
|
77
|
+
* @return {boolean} Whether comment node satisfies locality.
|
|
78
|
+
*/
|
|
79
|
+
function isLocalityDependencyBlock( node, locality ) {
|
|
80
|
+
const { type, value } = node;
|
|
81
|
+
if ( type !== 'Block' ) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Tolerances:
|
|
86
|
+
// - Normalize `/**` and `/*`
|
|
87
|
+
// - Case insensitive "Dependencies" vs. "dependencies"
|
|
88
|
+
// - Ending period
|
|
89
|
+
// - "Node" dependencies as an alias for External.
|
|
90
|
+
|
|
91
|
+
if ( locality === 'External' ) {
|
|
92
|
+
locality = '(External|Node)';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
96
|
+
const pattern = new RegExp(
|
|
97
|
+
`^\\*?\\n \\* ${ locality } dependencies\\.?\\n $`,
|
|
98
|
+
'i',
|
|
99
|
+
);
|
|
100
|
+
return pattern.test( value );
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns true if the given node occurs prior in code to a reference,
|
|
105
|
+
* or false otherwise.
|
|
106
|
+
*
|
|
107
|
+
* @param {Comment} node Node to test being before reference.
|
|
108
|
+
* @param {Node} reference Node against which to compare.
|
|
109
|
+
*
|
|
110
|
+
* @return {boolean} Whether node occurs before reference.
|
|
111
|
+
*/
|
|
112
|
+
function isBefore( node, reference ) {
|
|
113
|
+
if ( ! node.range || ! reference.range ) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return node.range[ 0 ] < reference.range[ 0 ];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Tests source comments to determine whether a comment exists which
|
|
122
|
+
* satisfies the desired locality. If a match is found and requires no
|
|
123
|
+
* updates, the function returns undefined. Otherwise, it will return
|
|
124
|
+
* a WPDependencyBlockCorrection object describing a correction.
|
|
125
|
+
*
|
|
126
|
+
* @param {Node} node Node to test.
|
|
127
|
+
* @param {WPPackageLocality} locality Desired package locality.
|
|
128
|
+
*
|
|
129
|
+
* @return {WPDependencyBlockCorrection | undefined} Correction, if applicable.
|
|
130
|
+
*/
|
|
131
|
+
function getDependencyBlockCorrection( node, locality ) {
|
|
132
|
+
const value = getCommentValue( locality );
|
|
133
|
+
|
|
134
|
+
for ( const comment of comments ) {
|
|
135
|
+
if ( ! isBefore( comment, node ) ) {
|
|
136
|
+
// Exhausted options.
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if ( ! isLocalityDependencyBlock( comment, locality ) ) {
|
|
141
|
+
// Not usable (either not an block comment, or not one
|
|
142
|
+
// matching a tolerable pattern).
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if ( comment.value === value ) {
|
|
147
|
+
// No change needed. (OK)
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Found a comment needing correction.
|
|
152
|
+
return { comment, value };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { value };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
/**
|
|
160
|
+
* @param {import('estree').Program} node Program node.
|
|
161
|
+
*/
|
|
162
|
+
Program( node ) {
|
|
163
|
+
/**
|
|
164
|
+
* The set of package localities which have been reported for
|
|
165
|
+
* the current program. Each locality is reported at most one
|
|
166
|
+
* time, since otherwise the fixer would insert a comment
|
|
167
|
+
* block for each individual import statement.
|
|
168
|
+
*
|
|
169
|
+
* @type {Set<WPPackageLocality>}
|
|
170
|
+
*/
|
|
171
|
+
const verified = new Set();
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Nodes to check for violations associated with module import,
|
|
175
|
+
* an array of tuples of the node and its import source string.
|
|
176
|
+
*
|
|
177
|
+
* @type {Array<[Node,string]>}
|
|
178
|
+
*/
|
|
179
|
+
const candidates = [];
|
|
180
|
+
|
|
181
|
+
// Since we only care to enforce imports which occur at the
|
|
182
|
+
// top-level scope, match on Program and test its children,
|
|
183
|
+
// rather than matching the import nodes directly.
|
|
184
|
+
node.body.forEach( ( child ) => {
|
|
185
|
+
/** @type {string} */
|
|
186
|
+
let source;
|
|
187
|
+
switch ( child.type ) {
|
|
188
|
+
case 'ImportDeclaration':
|
|
189
|
+
source = /** @type {string} */ (
|
|
190
|
+
child.source.value
|
|
191
|
+
);
|
|
192
|
+
candidates.push( [ child, source ] );
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case 'VariableDeclaration':
|
|
196
|
+
child.declarations.forEach( ( declaration ) => {
|
|
197
|
+
const { init } = declaration;
|
|
198
|
+
if (
|
|
199
|
+
! init ||
|
|
200
|
+
init.type !== 'CallExpression' ||
|
|
201
|
+
/** @type {import('estree').CallExpression} */ (
|
|
202
|
+
init
|
|
203
|
+
).callee.type !== 'Identifier' ||
|
|
204
|
+
/** @type {import('estree').Identifier} */ (
|
|
205
|
+
init.callee
|
|
206
|
+
).name !== 'require'
|
|
207
|
+
) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const { arguments: args } = init;
|
|
212
|
+
if (
|
|
213
|
+
args.length === 1 &&
|
|
214
|
+
args[ 0 ].type === 'Literal' &&
|
|
215
|
+
typeof args[ 0 ].value === 'string'
|
|
216
|
+
) {
|
|
217
|
+
source = args[ 0 ].value;
|
|
218
|
+
candidates.push( [ child, source ] );
|
|
219
|
+
}
|
|
220
|
+
} );
|
|
221
|
+
}
|
|
222
|
+
} );
|
|
223
|
+
|
|
224
|
+
for ( const [ child, source ] of candidates ) {
|
|
225
|
+
const locality = getPackageLocality( source );
|
|
226
|
+
if ( verified.has( locality ) ) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Avoid verifying any other imports for the locality,
|
|
231
|
+
// regardless whether a correction must be made.
|
|
232
|
+
verified.add( locality );
|
|
233
|
+
|
|
234
|
+
// Determine whether a correction must be made.
|
|
235
|
+
const correction = getDependencyBlockCorrection(
|
|
236
|
+
child,
|
|
237
|
+
locality,
|
|
238
|
+
);
|
|
239
|
+
if ( ! correction ) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
context.report( {
|
|
244
|
+
node: child,
|
|
245
|
+
message: `Expected preceding "${ locality } dependencies" comment block`,
|
|
246
|
+
fix( fixer ) {
|
|
247
|
+
const { comment, value } = correction;
|
|
248
|
+
const text = `/*${ value }*/`;
|
|
249
|
+
if ( comment && comment.range ) {
|
|
250
|
+
return fixer.replaceTextRange(
|
|
251
|
+
comment.range,
|
|
252
|
+
text,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return fixer.insertTextBefore( child, text + '\n' );
|
|
257
|
+
},
|
|
258
|
+
} );
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
};
|
package/rules/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rules
|
|
3
|
+
*/
|
|
1
4
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
'no-async-foreach': require('./no-async-foreach'),
|
|
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' ),
|
|
4
9
|
};
|
|
@@ -8,26 +8,27 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
create(context) {
|
|
11
|
+
create( context ) {
|
|
12
12
|
return {
|
|
13
|
-
ExpressionStatement(node) {
|
|
13
|
+
ExpressionStatement( node ) {
|
|
14
14
|
const { callee } = node.expression;
|
|
15
|
-
if (!callee || !callee.property || !callee.property.name)
|
|
15
|
+
if ( ! callee || ! callee.property || ! callee.property.name ) {
|
|
16
16
|
return;
|
|
17
|
-
|
|
17
|
+
}
|
|
18
|
+
if ( callee.property.name === 'forEach' ) {
|
|
18
19
|
const functionArguments = node.expression.arguments.find(
|
|
19
|
-
(exp) => {
|
|
20
|
+
( exp ) => {
|
|
20
21
|
return (
|
|
21
22
|
exp.type === 'ArrowFunctionExpression' ||
|
|
22
23
|
exp.type === 'FunctionExpression'
|
|
23
24
|
);
|
|
24
|
-
}
|
|
25
|
+
},
|
|
25
26
|
);
|
|
26
|
-
if (functionArguments) {
|
|
27
|
-
if (functionArguments.async) {
|
|
27
|
+
if ( functionArguments ) {
|
|
28
|
+
if ( functionArguments.async ) {
|
|
28
29
|
context.report(
|
|
29
30
|
node,
|
|
30
|
-
'Avoid passing an async function to Array.prototype.forEach'
|
|
31
|
+
'Avoid passing an async function to Array.prototype.forEach',
|
|
31
32
|
);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/rules/no-unguarded-get-range-at.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'problem',
|
|
9
|
+
schema: [],
|
|
10
|
+
},
|
|
11
|
+
create( context ) {
|
|
12
|
+
return {
|
|
13
|
+
'CallExpression[callee.object.callee.property.name="getSelection"][callee.property.name="getRangeAt"]'(
|
|
14
|
+
node,
|
|
15
|
+
) {
|
|
16
|
+
context.report( {
|
|
17
|
+
node,
|
|
18
|
+
message: 'Avoid unguarded getRangeAt',
|
|
19
|
+
} );
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From:
|
|
3
|
+
* https://github.com/WordPress/gutenberg/blob/%40wordpress/eslint-plugin%4014.1.0/packages/eslint-plugin/rules/no-unused-vars-before-return.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('eslint').Scope.Scope} ESLintScope */
|
|
7
|
+
/** @typedef {import('eslint').Rule.RuleContext} ESLintRuleContext */
|
|
8
|
+
/** @typedef {import('estree').Node} ESTreeNode */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mapping of function scope objects to a set of identified JSX identifiers
|
|
12
|
+
* within that scope.
|
|
13
|
+
*
|
|
14
|
+
* @type {WeakMap<ESLintScope,Set<ESTreeNode>>}
|
|
15
|
+
*/
|
|
16
|
+
const FUNCTION_SCOPE_JSX_IDENTIFIERS = new WeakMap();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns the closest function scope for the current ESLint context object, or
|
|
20
|
+
* undefined if it cannot be determined.
|
|
21
|
+
*
|
|
22
|
+
* @param {ESLintRuleContext} context ESLint context object.
|
|
23
|
+
*
|
|
24
|
+
* @return {ESLintScope|undefined} Function scope, if known.
|
|
25
|
+
*/
|
|
26
|
+
function getClosestFunctionScope( context ) {
|
|
27
|
+
let functionScope = context.getScope();
|
|
28
|
+
while ( functionScope.type !== 'function' && functionScope.upper ) {
|
|
29
|
+
functionScope = functionScope.upper;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return functionScope;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = /** @type {import('eslint').Rule} */ ( {
|
|
36
|
+
meta: {
|
|
37
|
+
type: 'problem',
|
|
38
|
+
schema: [
|
|
39
|
+
{
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
excludePattern: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* @param {ESLintRuleContext} context Rule context.
|
|
52
|
+
*/
|
|
53
|
+
create( context ) {
|
|
54
|
+
const options = context.options[ 0 ] || {};
|
|
55
|
+
const { excludePattern } = options;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Given an Espree VariableDeclarator node, returns true if the node
|
|
59
|
+
* can be exempted from consideration as unused, or false otherwise. A
|
|
60
|
+
* node can be exempt if it destructures to multiple variables, since
|
|
61
|
+
* those other variables may be used prior to the return statement. A
|
|
62
|
+
* future enhancement could validate that they are in-fact referenced.
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} node Node to test.
|
|
65
|
+
*
|
|
66
|
+
* @return {boolean} Whether declarator is emempt from consideration.
|
|
67
|
+
*/
|
|
68
|
+
function isExemptObjectDestructureDeclarator( node ) {
|
|
69
|
+
return (
|
|
70
|
+
node.id.type === 'ObjectPattern' &&
|
|
71
|
+
node.id.properties.length > 1
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
JSXIdentifier( node ) {
|
|
77
|
+
// Currently, a scope's variable references does not include JSX
|
|
78
|
+
// identifiers. Account for this by visiting JSX identifiers
|
|
79
|
+
// first, and tracking them in a map per function scope, which
|
|
80
|
+
// is later merged with the known variable references.
|
|
81
|
+
const functionScope = getClosestFunctionScope( context );
|
|
82
|
+
if ( ! functionScope ) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ( ! FUNCTION_SCOPE_JSX_IDENTIFIERS.has( functionScope ) ) {
|
|
87
|
+
FUNCTION_SCOPE_JSX_IDENTIFIERS.set(
|
|
88
|
+
functionScope,
|
|
89
|
+
new Set(),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
FUNCTION_SCOPE_JSX_IDENTIFIERS.get( functionScope ).add( node );
|
|
94
|
+
},
|
|
95
|
+
'ReturnStatement:exit'( node ) {
|
|
96
|
+
const functionScope = getClosestFunctionScope( context );
|
|
97
|
+
if ( ! functionScope ) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for ( const variable of functionScope.variables ) {
|
|
102
|
+
const declaratorCandidate = variable.defs.find( ( def ) => {
|
|
103
|
+
return (
|
|
104
|
+
def.node.type === 'VariableDeclarator' &&
|
|
105
|
+
// Allow declarations which are not initialized.
|
|
106
|
+
def.node.init &&
|
|
107
|
+
// Target function calls as "expensive".
|
|
108
|
+
def.node.init.type === 'CallExpression' &&
|
|
109
|
+
// Allow unused if part of an object destructuring.
|
|
110
|
+
! isExemptObjectDestructureDeclarator( def.node ) &&
|
|
111
|
+
// Only target assignments preceding `return`.
|
|
112
|
+
def.node.range[ 1 ] < node.range[ 1 ]
|
|
113
|
+
);
|
|
114
|
+
} );
|
|
115
|
+
|
|
116
|
+
if ( ! declaratorCandidate ) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
excludePattern !== undefined &&
|
|
122
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
123
|
+
new RegExp( excludePattern ).test(
|
|
124
|
+
declaratorCandidate.node.init.callee.name,
|
|
125
|
+
)
|
|
126
|
+
) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// The first entry in `references` is the declaration
|
|
131
|
+
// itself, which can be ignored.
|
|
132
|
+
const identifiers = variable.references
|
|
133
|
+
.slice( 1 )
|
|
134
|
+
.map( ( reference ) => reference.identifier );
|
|
135
|
+
|
|
136
|
+
// Merge with any JSX identifiers in scope, if any.
|
|
137
|
+
if ( FUNCTION_SCOPE_JSX_IDENTIFIERS.has( functionScope ) ) {
|
|
138
|
+
const jsxIdentifiers =
|
|
139
|
+
FUNCTION_SCOPE_JSX_IDENTIFIERS.get( functionScope );
|
|
140
|
+
|
|
141
|
+
identifiers.push( ...jsxIdentifiers );
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const isUsedBeforeReturn = identifiers.some(
|
|
145
|
+
( identifier ) =>
|
|
146
|
+
identifier.range[ 1 ] < node.range[ 1 ],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if ( isUsedBeforeReturn ) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
context.report(
|
|
154
|
+
declaratorCandidate.node,
|
|
155
|
+
'Variables should not be assigned until just prior its first reference. ' +
|
|
156
|
+
'An early return statement may leave this variable unused.',
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
} );
|