eslint-plugin-unicorn 69.0.0 → 70.0.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/package.json +1 -1
- package/readme.md +10 -0
- package/rules/consistent-boolean-name.js +637 -36
- package/rules/expiring-todo-comments.js +23 -0
- package/rules/fix/{add-parenthesizes-to-return-or-throw-expression.js → add-parentheses-to-return-or-throw-expression.js} +1 -1
- package/rules/fix/index.js +1 -1
- package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
- package/rules/index.js +10 -0
- package/rules/isolated-functions.js +156 -12
- package/rules/no-async-promise-finally.js +111 -0
- package/rules/no-collection-bracket-access.js +27 -4
- package/rules/no-manually-wrapped-comments.js +5 -1
- package/rules/no-negated-array-predicate.js +2 -2
- package/rules/no-negated-comparison.js +24 -61
- package/rules/no-negated-condition.js +2 -2
- package/rules/no-negation-in-equality-check.js +2 -2
- package/rules/no-non-function-verb-prefix.js +25 -2
- package/rules/no-return-array-push.js +9 -2
- package/rules/no-typeof-undefined.js +2 -2
- package/rules/no-unnecessary-array-flat-map.js +307 -0
- package/rules/no-unnecessary-await.js +2 -2
- package/rules/no-unnecessary-fetch-options.js +624 -0
- package/rules/no-unsafe-promise-all-settled-values.js +865 -0
- package/rules/no-useless-else.js +32 -1
- package/rules/no-useless-iterator-to-array.js +42 -16
- package/rules/no-useless-spread.js +2 -2
- package/rules/prefer-abort-signal-any.js +1391 -0
- package/rules/prefer-array-flat-map.js +6 -11
- package/rules/prefer-array-from-range.js +98 -0
- package/rules/prefer-block-statement-over-iife.js +159 -0
- package/rules/prefer-continue.js +6 -2
- package/rules/prefer-early-return.js +3 -1
- package/rules/prefer-group-by.js +576 -0
- package/rules/prefer-iterator-helpers.js +196 -0
- package/rules/prefer-number-coercion.js +1 -1
- package/rules/prefer-object-iterable-methods.js +57 -14
- package/rules/prefer-simplified-conditions.js +584 -0
- package/rules/prefer-spread.js +3 -0
- package/rules/prefer-string-raw.js +2 -2
- package/rules/shared/array-range.js +66 -0
- package/rules/shared/iterator-helpers.js +43 -1
- package/rules/utils/comparison.js +65 -0
- package/rules/utils/is-boolean.js +21 -6
- package/rules/utils/track-branch-exits.js +8 -7
|
@@ -23,6 +23,7 @@ const MESSAGE_ID_HAVE_PACKAGE = 'unicorn/havePackage';
|
|
|
23
23
|
const MESSAGE_ID_DONT_HAVE_PACKAGE = 'unicorn/dontHavePackage';
|
|
24
24
|
const MESSAGE_ID_VERSION_MATCHES = 'unicorn/versionMatches';
|
|
25
25
|
const MESSAGE_ID_PEER_VERSION_MATCHES = 'unicorn/peerVersionMatches';
|
|
26
|
+
const MESSAGE_ID_UNSUPPORTED_CATALOG_PROTOCOL = 'unicorn/unsupportedCatalogProtocol';
|
|
26
27
|
const MESSAGE_ID_ENGINE_MATCHES = 'unicorn/engineMatches';
|
|
27
28
|
const MESSAGE_ID_REMOVE_WHITESPACE = 'unicorn/removeWhitespaces';
|
|
28
29
|
const MESSAGE_ID_MISSING_AT_SYMBOL = 'unicorn/missingAtSymbol';
|
|
@@ -46,6 +47,8 @@ const messages = {
|
|
|
46
47
|
'Due since package version matched: {{comparison}}. {{message}}',
|
|
47
48
|
[MESSAGE_ID_PEER_VERSION_MATCHES]:
|
|
48
49
|
'Due since peer dependency version matched: {{comparison}}. {{message}}',
|
|
50
|
+
[MESSAGE_ID_UNSUPPORTED_CATALOG_PROTOCOL]:
|
|
51
|
+
'Cannot check {{dependencyType}} version because {{package}} uses the unsupported `catalog:` protocol. {{message}}',
|
|
49
52
|
[MESSAGE_ID_ENGINE_MATCHES]:
|
|
50
53
|
'Due since Node.js version matched: {{comparison}}. {{message}}',
|
|
51
54
|
[MESSAGE_ID_REMOVE_WHITESPACE]:
|
|
@@ -284,6 +287,10 @@ function getRangeFloor(range) {
|
|
|
284
287
|
return semver.validRange(range) ? semver.minVersion(range) : undefined;
|
|
285
288
|
}
|
|
286
289
|
|
|
290
|
+
function isCatalogProtocol(rawVersion) {
|
|
291
|
+
return typeof rawVersion === 'string' && rawVersion.startsWith('catalog:');
|
|
292
|
+
}
|
|
293
|
+
|
|
287
294
|
const DEFAULT_OPTIONS = {
|
|
288
295
|
terms: ['todo', 'fixme', 'xxx'],
|
|
289
296
|
ignore: [],
|
|
@@ -480,6 +487,14 @@ const create = context => {
|
|
|
480
487
|
continue;
|
|
481
488
|
}
|
|
482
489
|
|
|
490
|
+
if (isCatalogProtocol(targetPackageRawVersion)) {
|
|
491
|
+
report(MESSAGE_ID_UNSUPPORTED_CATALOG_PROTOCOL, {
|
|
492
|
+
dependencyType: 'dependency',
|
|
493
|
+
package: dependency.name,
|
|
494
|
+
});
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
483
498
|
const targetPackageVersion = tryToCoerceVersion(targetPackageRawVersion);
|
|
484
499
|
|
|
485
500
|
/* c8 ignore start */
|
|
@@ -505,6 +520,14 @@ const create = context => {
|
|
|
505
520
|
continue;
|
|
506
521
|
}
|
|
507
522
|
|
|
523
|
+
if (isCatalogProtocol(targetPeerRawVersion)) {
|
|
524
|
+
report(MESSAGE_ID_UNSUPPORTED_CATALOG_PROTOCOL, {
|
|
525
|
+
dependencyType: 'peer dependency',
|
|
526
|
+
package: peerDependency.name,
|
|
527
|
+
});
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
508
531
|
const targetPeerVersion = getRangeFloor(targetPeerRawVersion);
|
|
509
532
|
|
|
510
533
|
if (targetPeerVersion && satisfiesRange(targetPeerVersion, peerDependency.condition, peerDependency.version)) {
|
|
@@ -11,7 +11,7 @@ import {isSemicolonToken} from '@eslint-community/eslint-utils';
|
|
|
11
11
|
@param {ESLint.Rule.RuleContext} context - The ESLint rule context object.
|
|
12
12
|
@returns {ESLint.Rule.ReportFixer}
|
|
13
13
|
*/
|
|
14
|
-
export default function *
|
|
14
|
+
export default function * addParenthesesToReturnOrThrowExpression(fixer, node, context) {
|
|
15
15
|
if (node.type !== 'ReturnStatement' && node.type !== 'ThrowStatement') {
|
|
16
16
|
return;
|
|
17
17
|
}
|
package/rules/fix/index.js
CHANGED
|
@@ -21,4 +21,4 @@ export {default as replaceReferenceIdentifier} from './replace-reference-identif
|
|
|
21
21
|
export {default as replaceNodeOrTokenAndSpacesBefore} from './replace-node-or-token-and-spaces-before.js';
|
|
22
22
|
export {default as fixSpaceAroundKeyword} from './fix-space-around-keywords.js';
|
|
23
23
|
export {default as replaceStringRaw} from './replace-string-raw.js';
|
|
24
|
-
export {default as
|
|
24
|
+
export {default as addParenthesesToReturnOrThrowExpression} from './add-parentheses-to-return-or-throw-expression.js';
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
isParenthesized,
|
|
4
4
|
isOnSameLine,
|
|
5
5
|
} from '../utils/index.js';
|
|
6
|
-
import
|
|
6
|
+
import addParenthesesToReturnOrThrowExpression from './add-parentheses-to-return-or-throw-expression.js';
|
|
7
7
|
import removeSpaceAfter from './remove-spaces-after.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -39,6 +39,6 @@ export default function * switchNewExpressionToCallExpression(newExpression, con
|
|
|
39
39
|
if (!isOnSameLine(newToken, newExpression.callee, context) && !isParenthesized(newExpression, context)) {
|
|
40
40
|
// Ideally, we should use first parenthesis of the `callee`, and should check spaces after the `new` token
|
|
41
41
|
// But adding extra parentheses is harmless, no need to be too complicated
|
|
42
|
-
yield
|
|
42
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, newExpression.parent, context);
|
|
43
43
|
}
|
|
44
44
|
}
|
package/rules/index.js
CHANGED
|
@@ -53,6 +53,7 @@ export {default as 'no-array-sort-for-min-max'} from './no-array-sort-for-min-ma
|
|
|
53
53
|
export {default as 'no-array-sort'} from './no-array-sort.js';
|
|
54
54
|
export {default as 'no-array-splice'} from './no-array-splice.js';
|
|
55
55
|
export {default as 'no-asterisk-prefix-in-documentation-comments'} from './no-asterisk-prefix-in-documentation-comments.js';
|
|
56
|
+
export {default as 'no-async-promise-finally'} from './no-async-promise-finally.js';
|
|
56
57
|
export {default as 'no-await-expression-member'} from './no-await-expression-member.js';
|
|
57
58
|
export {default as 'no-await-in-promise-methods'} from './no-await-in-promise-methods.js';
|
|
58
59
|
export {default as 'no-blob-to-file'} from './no-blob-to-file.js';
|
|
@@ -129,9 +130,11 @@ export {default as 'no-typeof-undefined'} from './no-typeof-undefined.js';
|
|
|
129
130
|
export {default as 'no-uncalled-method'} from './no-uncalled-method.js';
|
|
130
131
|
export {default as 'no-undeclared-class-members'} from './no-undeclared-class-members.js';
|
|
131
132
|
export {default as 'no-unnecessary-array-flat-depth'} from './no-unnecessary-array-flat-depth.js';
|
|
133
|
+
export {default as 'no-unnecessary-array-flat-map'} from './no-unnecessary-array-flat-map.js';
|
|
132
134
|
export {default as 'no-unnecessary-array-splice-count'} from './no-unnecessary-array-splice-count.js';
|
|
133
135
|
export {default as 'no-unnecessary-await'} from './no-unnecessary-await.js';
|
|
134
136
|
export {default as 'no-unnecessary-boolean-comparison'} from './no-unnecessary-boolean-comparison.js';
|
|
137
|
+
export {default as 'no-unnecessary-fetch-options'} from './no-unnecessary-fetch-options.js';
|
|
135
138
|
export {default as 'no-unnecessary-global-this'} from './no-unnecessary-global-this.js';
|
|
136
139
|
export {default as 'no-unnecessary-nested-ternary'} from './no-unnecessary-nested-ternary.js';
|
|
137
140
|
export {default as 'no-unnecessary-polyfills'} from './no-unnecessary-polyfills.js';
|
|
@@ -144,6 +147,7 @@ export {default as 'no-unreadable-new-expression'} from './no-unreadable-new-exp
|
|
|
144
147
|
export {default as 'no-unreadable-object-destructuring'} from './no-unreadable-object-destructuring.js';
|
|
145
148
|
export {default as 'no-unsafe-buffer-conversion'} from './no-unsafe-buffer-conversion.js';
|
|
146
149
|
export {default as 'no-unsafe-dom-html'} from './no-unsafe-dom-html.js';
|
|
150
|
+
export {default as 'no-unsafe-promise-all-settled-values'} from './no-unsafe-promise-all-settled-values.js';
|
|
147
151
|
export {default as 'no-unsafe-property-key'} from './no-unsafe-property-key.js';
|
|
148
152
|
export {default as 'no-unsafe-string-replacement'} from './no-unsafe-string-replacement.js';
|
|
149
153
|
export {default as 'no-unused-array-method-return'} from './no-unused-array-method-return.js';
|
|
@@ -173,6 +177,7 @@ export {default as 'no-zero-fractions'} from './no-zero-fractions.js';
|
|
|
173
177
|
export {default as 'number-literal-case'} from './number-literal-case.js';
|
|
174
178
|
export {default as 'numeric-separators-style'} from './numeric-separators-style.js';
|
|
175
179
|
export {default as 'operator-assignment'} from './operator-assignment.js';
|
|
180
|
+
export {default as 'prefer-abort-signal-any'} from './prefer-abort-signal-any.js';
|
|
176
181
|
export {default as 'prefer-abort-signal-timeout'} from './prefer-abort-signal-timeout.js';
|
|
177
182
|
export {default as 'prefer-add-event-listener-options'} from './prefer-add-event-listener-options.js';
|
|
178
183
|
export {default as 'prefer-add-event-listener'} from './prefer-add-event-listener.js';
|
|
@@ -182,6 +187,7 @@ export {default as 'prefer-array-flat-map'} from './prefer-array-flat-map.js';
|
|
|
182
187
|
export {default as 'prefer-array-flat'} from './prefer-array-flat.js';
|
|
183
188
|
export {default as 'prefer-array-from-async'} from './prefer-array-from-async.js';
|
|
184
189
|
export {default as 'prefer-array-from-map'} from './prefer-array-from-map.js';
|
|
190
|
+
export {default as 'prefer-array-from-range'} from './prefer-array-from-range.js';
|
|
185
191
|
export {default as 'prefer-array-index-of'} from './prefer-array-index-of.js';
|
|
186
192
|
export {default as 'prefer-array-iterable-methods'} from './prefer-array-iterable-methods.js';
|
|
187
193
|
export {default as 'prefer-array-last-methods'} from './prefer-array-last-methods.js';
|
|
@@ -191,6 +197,7 @@ export {default as 'prefer-at'} from './prefer-at.js';
|
|
|
191
197
|
export {default as 'prefer-await'} from './prefer-await.js';
|
|
192
198
|
export {default as 'prefer-bigint-literals'} from './prefer-bigint-literals.js';
|
|
193
199
|
export {default as 'prefer-blob-reading-methods'} from './prefer-blob-reading-methods.js';
|
|
200
|
+
export {default as 'prefer-block-statement-over-iife'} from './prefer-block-statement-over-iife.js';
|
|
194
201
|
export {default as 'prefer-boolean-return'} from './prefer-boolean-return.js';
|
|
195
202
|
export {default as 'prefer-class-fields'} from './prefer-class-fields.js';
|
|
196
203
|
export {default as 'prefer-classlist-toggle'} from './prefer-classlist-toggle.js';
|
|
@@ -214,6 +221,7 @@ export {default as 'prefer-flat-math-min-max'} from './prefer-flat-math-min-max.
|
|
|
214
221
|
export {default as 'prefer-get-or-insert-computed'} from './prefer-get-or-insert-computed.js';
|
|
215
222
|
export {default as 'prefer-global-number-constants'} from './prefer-global-number-constants.js';
|
|
216
223
|
export {default as 'prefer-global-this'} from './prefer-global-this.js';
|
|
224
|
+
export {default as 'prefer-group-by'} from './prefer-group-by.js';
|
|
217
225
|
export {default as 'prefer-has-check'} from './prefer-has-check.js';
|
|
218
226
|
export {default as 'prefer-hoisting-branch-code'} from './prefer-hoisting-branch-code.js';
|
|
219
227
|
export {default as 'prefer-https'} from './prefer-https.js';
|
|
@@ -223,6 +231,7 @@ export {default as 'prefer-includes-over-repeated-comparisons'} from './prefer-i
|
|
|
223
231
|
export {default as 'prefer-includes'} from './prefer-includes.js';
|
|
224
232
|
export {default as 'prefer-iterable-in-constructor'} from './prefer-iterable-in-constructor.js';
|
|
225
233
|
export {default as 'prefer-iterator-concat'} from './prefer-iterator-concat.js';
|
|
234
|
+
export {default as 'prefer-iterator-helpers'} from './prefer-iterator-helpers.js';
|
|
226
235
|
export {default as 'prefer-iterator-to-array-at-end'} from './prefer-iterator-to-array-at-end.js';
|
|
227
236
|
export {default as 'prefer-iterator-to-array'} from './prefer-iterator-to-array.js';
|
|
228
237
|
export {default as 'prefer-keyboard-event-key'} from './prefer-keyboard-event-key.js';
|
|
@@ -267,6 +276,7 @@ export {default as 'prefer-set-size'} from './prefer-set-size.js';
|
|
|
267
276
|
export {default as 'prefer-short-arrow-method'} from './prefer-short-arrow-method.js';
|
|
268
277
|
export {default as 'prefer-simple-condition-first'} from './prefer-simple-condition-first.js';
|
|
269
278
|
export {default as 'prefer-simple-sort-comparator'} from './prefer-simple-sort-comparator.js';
|
|
279
|
+
export {default as 'prefer-simplified-conditions'} from './prefer-simplified-conditions.js';
|
|
270
280
|
export {default as 'prefer-single-array-predicate'} from './prefer-single-array-predicate.js';
|
|
271
281
|
export {default as 'prefer-single-call'} from './prefer-single-call.js';
|
|
272
282
|
export {default as 'prefer-single-object-destructuring'} from './prefer-single-object-destructuring.js';
|
|
@@ -1,19 +1,95 @@
|
|
|
1
1
|
import globals from 'globals';
|
|
2
|
-
import {functionTypes} from './ast/index.js';
|
|
2
|
+
import {functionTypes, isMemberExpression, isMethodCall} from './ast/index.js';
|
|
3
3
|
|
|
4
4
|
const MESSAGE_ID_EXTERNALLY_SCOPED_VARIABLE = 'externally-scoped-variable';
|
|
5
|
+
const MESSAGE_ID_SUPER = 'super';
|
|
6
|
+
const MESSAGE_ID_THIS_EXPRESSION = 'this-expression';
|
|
7
|
+
const functionContextReferenceMessageIds = new Map([
|
|
8
|
+
['Super', MESSAGE_ID_SUPER],
|
|
9
|
+
['ThisExpression', MESSAGE_ID_THIS_EXPRESSION],
|
|
10
|
+
]);
|
|
11
|
+
|
|
5
12
|
const messages = {
|
|
6
13
|
[MESSAGE_ID_EXTERNALLY_SCOPED_VARIABLE]: 'Variable {{name}} not defined in scope of isolated function. Function is isolated because: {{reason}}.',
|
|
14
|
+
[MESSAGE_ID_SUPER]: 'Unexpected `super` in isolated function. Function is isolated because: {{reason}}.',
|
|
15
|
+
[MESSAGE_ID_THIS_EXPRESSION]: 'Unexpected `this` in isolated function. Function is isolated because: {{reason}}.',
|
|
7
16
|
};
|
|
8
17
|
|
|
9
18
|
/** @type {{functions: string[], selectors: string[], comments: string[], overrideGlobals?: import('eslint').Linter.Globals}} */
|
|
10
19
|
const defaultOptions = {
|
|
11
|
-
functions: ['makeSynchronous'],
|
|
20
|
+
functions: ['makeSynchronous', 'workerize'],
|
|
12
21
|
selectors: [],
|
|
13
22
|
comments: ['@isolated'],
|
|
14
23
|
overrideGlobals: {},
|
|
15
24
|
};
|
|
16
25
|
|
|
26
|
+
const scriptingObjects = ['browser', 'chrome'];
|
|
27
|
+
|
|
28
|
+
const getObjectPropertyName = node => {
|
|
29
|
+
if (node.computed && node.key.type !== 'Literal') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (node.key.type === 'Identifier') {
|
|
34
|
+
return node.key.name;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
|
|
38
|
+
return node.key.value;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getDefaultArgumentCallReason = node => {
|
|
43
|
+
if (
|
|
44
|
+
node.parent.type !== 'CallExpression'
|
|
45
|
+
|| node.parent.arguments[0] !== node
|
|
46
|
+
) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isMethodCall(node.parent, {object: 'browser', method: 'execute'})) {
|
|
51
|
+
return 'callee of method named "browser.execute"';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isMethodCall(node.parent, {object: 'page', method: 'evaluate'})) {
|
|
55
|
+
return 'callee of method named "page.evaluate"';
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getScriptingExecuteScriptObjectName = node => {
|
|
60
|
+
if (
|
|
61
|
+
!isMethodCall(node, {method: 'executeScript'})
|
|
62
|
+
|| !isMemberExpression(node.callee.object, {
|
|
63
|
+
objects: scriptingObjects,
|
|
64
|
+
property: 'scripting',
|
|
65
|
+
})
|
|
66
|
+
) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return node.callee.object.object.name;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getExecuteScriptPropertyReason = node => {
|
|
74
|
+
if (
|
|
75
|
+
node.parent.type !== 'Property'
|
|
76
|
+
|| node.parent.kind !== 'init'
|
|
77
|
+
|| getObjectPropertyName(node.parent) !== 'func'
|
|
78
|
+
|| node.parent.parent.type !== 'ObjectExpression'
|
|
79
|
+
|| node.parent.parent.parent.type !== 'CallExpression'
|
|
80
|
+
|| node.parent.parent.parent.arguments[0] !== node.parent.parent
|
|
81
|
+
) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const scriptingObjectName = getScriptingExecuteScriptObjectName(node.parent.parent.parent);
|
|
86
|
+
if (!scriptingObjectName) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return `property "func" passed to "${scriptingObjectName}.scripting.executeScript"`;
|
|
91
|
+
};
|
|
92
|
+
|
|
17
93
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
18
94
|
const create = context => {
|
|
19
95
|
const {sourceCode} = context;
|
|
@@ -29,8 +105,58 @@ const create = context => {
|
|
|
29
105
|
};
|
|
30
106
|
const checked = new WeakSet();
|
|
31
107
|
|
|
108
|
+
function * getFunctionContextProblems(node, reason, root = node) {
|
|
109
|
+
const messageId = functionContextReferenceMessageIds.get(node.type);
|
|
110
|
+
if (messageId) {
|
|
111
|
+
yield {
|
|
112
|
+
node,
|
|
113
|
+
messageId,
|
|
114
|
+
data: {reason},
|
|
115
|
+
};
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (node !== root) {
|
|
120
|
+
if (functionTypes.includes(node.type) && node.type !== 'ArrowFunctionExpression') {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
|
|
125
|
+
if (node.superClass) {
|
|
126
|
+
yield * getFunctionContextProblems(node.superClass, reason, root);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const classElement of node.body.body) {
|
|
130
|
+
if (classElement.computed) {
|
|
131
|
+
yield * getFunctionContextProblems(classElement.key, reason, root);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const key of sourceCode.visitorKeys[node.type] ?? []) {
|
|
140
|
+
const value = node[key];
|
|
141
|
+
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
for (const childNode of value) {
|
|
144
|
+
if (childNode?.type) {
|
|
145
|
+
yield * getFunctionContextProblems(childNode, reason, root);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (value?.type) {
|
|
153
|
+
yield * getFunctionContextProblems(value, reason, root);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
32
158
|
/** @param {import('estree').Node} node */
|
|
33
|
-
const
|
|
159
|
+
const reportIsolatedFunctionProblems = (node, reason) => {
|
|
34
160
|
if (checked.has(node) || !functionTypes.includes(node.type)) {
|
|
35
161
|
return;
|
|
36
162
|
}
|
|
@@ -69,12 +195,33 @@ const create = context => {
|
|
|
69
195
|
data: {name: identifier.name, reason},
|
|
70
196
|
});
|
|
71
197
|
}
|
|
198
|
+
|
|
199
|
+
for (const problem of getFunctionContextProblems(node, reason)) {
|
|
200
|
+
context.report(problem);
|
|
201
|
+
}
|
|
72
202
|
};
|
|
73
203
|
|
|
74
204
|
const isComment = token => token?.type === 'Block' || token?.type === 'Line';
|
|
75
205
|
|
|
206
|
+
const canCommentApplyToParent = (node, parent) =>
|
|
207
|
+
parent && (
|
|
208
|
+
[
|
|
209
|
+
'VariableDeclarator',
|
|
210
|
+
'VariableDeclaration',
|
|
211
|
+
'ExportNamedDeclaration',
|
|
212
|
+
'ExportDefaultDeclaration',
|
|
213
|
+
].includes(parent.type)
|
|
214
|
+
|| (
|
|
215
|
+
(
|
|
216
|
+
parent.type === 'Property'
|
|
217
|
+
|| parent.type === 'MethodDefinition'
|
|
218
|
+
)
|
|
219
|
+
&& parent.value === node
|
|
220
|
+
)
|
|
221
|
+
);
|
|
222
|
+
|
|
76
223
|
/**
|
|
77
|
-
Find a comment on this node or its parent, in cases where the node passed is part of a variable or
|
|
224
|
+
Find a comment on this node or its parent, in cases where the node passed is part of a variable, export, property, or method declaration.
|
|
78
225
|
@param {import('estree').Node} node
|
|
79
226
|
*/
|
|
80
227
|
const findComment = node => {
|
|
@@ -82,12 +229,7 @@ const create = context => {
|
|
|
82
229
|
let commentableNode = node;
|
|
83
230
|
while (
|
|
84
231
|
!isComment(previousToken)
|
|
85
|
-
&&
|
|
86
|
-
'VariableDeclarator',
|
|
87
|
-
'VariableDeclaration',
|
|
88
|
-
'ExportNamedDeclaration',
|
|
89
|
-
'ExportDefaultDeclaration',
|
|
90
|
-
].includes(commentableNode.parent.type)
|
|
232
|
+
&& canCommentApplyToParent(commentableNode, commentableNode.parent)
|
|
91
233
|
) {
|
|
92
234
|
commentableNode = commentableNode.parent;
|
|
93
235
|
previousToken = sourceCode.getTokenBefore(commentableNode, {includeComments: true});
|
|
@@ -127,6 +269,8 @@ const create = context => {
|
|
|
127
269
|
) {
|
|
128
270
|
return `callee of function named ${JSON.stringify(node.parent.callee.name)}`;
|
|
129
271
|
}
|
|
272
|
+
|
|
273
|
+
return getDefaultArgumentCallReason(node) ?? getExecuteScriptPropertyReason(node);
|
|
130
274
|
};
|
|
131
275
|
|
|
132
276
|
context.onExit(
|
|
@@ -137,7 +281,7 @@ const create = context => {
|
|
|
137
281
|
return;
|
|
138
282
|
}
|
|
139
283
|
|
|
140
|
-
return
|
|
284
|
+
return reportIsolatedFunctionProblems(node, reason);
|
|
141
285
|
},
|
|
142
286
|
);
|
|
143
287
|
|
|
@@ -146,7 +290,7 @@ const create = context => {
|
|
|
146
290
|
selector,
|
|
147
291
|
node => {
|
|
148
292
|
const reason = `matches selector ${JSON.stringify(selector)}`;
|
|
149
|
-
return
|
|
293
|
+
return reportIsolatedFunctionProblems(node, reason);
|
|
150
294
|
},
|
|
151
295
|
);
|
|
152
296
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {findVariable, getPropertyName} from '@eslint-community/eslint-utils';
|
|
2
|
+
import {isFunction} from './ast/index.js';
|
|
3
|
+
import {
|
|
4
|
+
getConstVariableInitializer,
|
|
5
|
+
isPromiseType,
|
|
6
|
+
unwrapTypeScriptExpression,
|
|
7
|
+
} from './utils/index.js';
|
|
8
|
+
|
|
9
|
+
const MESSAGE_ID = 'no-async-promise-finally';
|
|
10
|
+
const messages = {
|
|
11
|
+
[MESSAGE_ID]: 'Do not pass an async function to `Promise#finally()`.',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const isAsyncNonGeneratorFunction = node =>
|
|
15
|
+
isFunction(node)
|
|
16
|
+
&& node.async
|
|
17
|
+
&& !node.generator;
|
|
18
|
+
|
|
19
|
+
function isPromiseObject(node, context) {
|
|
20
|
+
const {parserServices} = context.sourceCode;
|
|
21
|
+
if (!parserServices?.program) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return isPromiseType(
|
|
27
|
+
parserServices.getTypeAtLocation(node),
|
|
28
|
+
parserServices.program.getTypeChecker(),
|
|
29
|
+
);
|
|
30
|
+
} catch {
|
|
31
|
+
// TypeScript can throw while resolving incomplete projects; keep this rule best-effort.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isAsyncFunctionDeclarationReference(node, context) {
|
|
36
|
+
if (node.type !== 'Identifier') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const variable = findVariable(context.sourceCode.getScope(node), node);
|
|
41
|
+
if (variable?.defs.length !== 1) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const [definition] = variable.defs;
|
|
46
|
+
return definition.type === 'FunctionName'
|
|
47
|
+
&& isAsyncNonGeneratorFunction(definition.node);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isAsyncFinallyCallback(node, context) {
|
|
51
|
+
node = unwrapTypeScriptExpression(node);
|
|
52
|
+
|
|
53
|
+
if (isAsyncNonGeneratorFunction(node)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const initializer = getConstVariableInitializer(node, context);
|
|
58
|
+
if (initializer && isAsyncNonGeneratorFunction(unwrapTypeScriptExpression(initializer))) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return isAsyncFunctionDeclarationReference(node, context);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
66
|
+
const create = context => {
|
|
67
|
+
context.on('CallExpression', callExpression => {
|
|
68
|
+
const {callee} = callExpression;
|
|
69
|
+
if (callee.type !== 'MemberExpression') {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const method = getPropertyName(callee, context.sourceCode.getScope(callExpression));
|
|
74
|
+
if (method !== 'finally') {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const promiseObject = isPromiseObject(callee.object, context);
|
|
79
|
+
if (promiseObject === false) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const [callback] = callExpression.arguments;
|
|
84
|
+
if (!callback || !isAsyncFinallyCallback(callback, context)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
node: callback,
|
|
90
|
+
messageId: MESSAGE_ID,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
96
|
+
const config = {
|
|
97
|
+
create,
|
|
98
|
+
meta: {
|
|
99
|
+
type: 'problem',
|
|
100
|
+
docs: {
|
|
101
|
+
description: 'Disallow async functions as `Promise#finally()` callbacks.',
|
|
102
|
+
recommended: 'unopinionated',
|
|
103
|
+
},
|
|
104
|
+
messages,
|
|
105
|
+
languages: [
|
|
106
|
+
'js/js',
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default config;
|
|
@@ -56,6 +56,28 @@ const collectionTypes = [
|
|
|
56
56
|
// `.method(…)` without parentheses, so suggestions are limited to them.
|
|
57
57
|
const simpleObjectTypes = new Set(['Identifier', 'ThisExpression', 'MemberExpression']);
|
|
58
58
|
|
|
59
|
+
function getStaticPropertyValues(node, sourceCode) {
|
|
60
|
+
const staticResult = getStaticValue(node, sourceCode.getScope(node));
|
|
61
|
+
if (staticResult) {
|
|
62
|
+
return [staticResult.value];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (node.type !== 'ConditionalExpression') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const consequentValues = getStaticPropertyValues(node.consequent, sourceCode);
|
|
70
|
+
const alternateValues = getStaticPropertyValues(node.alternate, sourceCode);
|
|
71
|
+
if (!consequentValues || !alternateValues) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [
|
|
76
|
+
...consequentValues,
|
|
77
|
+
...alternateValues,
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
function getProblem(node, collection, context) {
|
|
60
82
|
const {sourceCode} = context;
|
|
61
83
|
const {name: type} = collection;
|
|
@@ -132,11 +154,12 @@ const create = context => {
|
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
// Allow accessing a real member, including `Symbol` ones like `map[Symbol.iterator]`.
|
|
135
|
-
const
|
|
157
|
+
const staticPropertyValues = getStaticPropertyValues(node.property, context.sourceCode);
|
|
136
158
|
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
159
|
+
staticPropertyValues?.every(value =>
|
|
160
|
+
(typeof value === 'string' || typeof value === 'symbol')
|
|
161
|
+
&& Reflect.has(collection.prototype, value),
|
|
162
|
+
)
|
|
140
163
|
) {
|
|
141
164
|
return;
|
|
142
165
|
}
|
|
@@ -11,12 +11,14 @@ const messages = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// Trailing `:` marks a complete line (heading, label, list intro), not a wrapped sentence.
|
|
14
|
-
const sentenceEndPattern = /[!.:?]$/v;
|
|
14
|
+
const sentenceEndPattern = /(?:[\p{Extended_Pictographic}!.:?]\p{Variation_Selector}?|\p{RGI_Emoji})$/v;
|
|
15
15
|
const directiveCommentPattern = /^(?:[#\/@]|eslint(?:$|\s|-)|globals?\b|exported\b|no default$|noinspection\b|(?:c8|istanbul|v8)\s+ignore\b|(?:biome|deno|dprint|oxlint|prettier)-|(?:cspell|spell-checker):)/v;
|
|
16
|
+
const annotationCommentPattern = /^[A-Z]{2,}(?:\([^\)]+\))?:/v;
|
|
16
17
|
const spdxCommentPattern = /^SPDX-/v;
|
|
17
18
|
const copyrightCommentPattern = /^(?:©|copyright\b)/iv;
|
|
18
19
|
// Editor modelines: vim (`vim:`, `vi:`) and Emacs (`-*- … -*-`).
|
|
19
20
|
const modelineCommentPattern = /^(?:vim?:|-\*-)/v;
|
|
21
|
+
const structuredCommentPattern = /^(?:language=\S+|(?:end)?region(?:$|\s)|<\/?editor-fold\b)/v;
|
|
20
22
|
const listCommentPattern = /^(?:[*+\-]\s|\d+(?:\.|\))\s)/v;
|
|
21
23
|
const separatorCommentPattern = /^[#*\-=_~]{3,}$/v;
|
|
22
24
|
const urlPattern = /\bhttps?:\/\/|www\./v;
|
|
@@ -28,9 +30,11 @@ const endsWithSentencePunctuation = comment => sentenceEndPattern.test(getCommen
|
|
|
28
30
|
|
|
29
31
|
const isIgnoredCommentText = text =>
|
|
30
32
|
directiveCommentPattern.test(text)
|
|
33
|
+
|| annotationCommentPattern.test(text)
|
|
31
34
|
|| spdxCommentPattern.test(text)
|
|
32
35
|
|| copyrightCommentPattern.test(text)
|
|
33
36
|
|| modelineCommentPattern.test(text)
|
|
37
|
+
|| structuredCommentPattern.test(text)
|
|
34
38
|
|| listCommentPattern.test(text)
|
|
35
39
|
|| separatorCommentPattern.test(text)
|
|
36
40
|
|| urlPattern.test(text)
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
shouldAddParenthesesToUnaryExpressionArgument,
|
|
9
9
|
} from './utils/index.js';
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
addParenthesesToReturnOrThrowExpression,
|
|
12
12
|
fixSpaceAroundKeyword,
|
|
13
13
|
} from './fix/index.js';
|
|
14
14
|
import {isFunction, isMethodCall} from './ast/index.js';
|
|
@@ -177,7 +177,7 @@ const create = context => {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
if (needsReturnOrThrowParentheses) {
|
|
180
|
-
yield
|
|
180
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, parent, context);
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
183
183
|
|