eslint-plugin-unicorn 69.0.0 → 71.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 +26 -25
- package/readme.md +15 -1
- package/rules/ast/call-or-new-expression.js +2 -8
- package/rules/ast/is-member-expression.js +2 -8
- package/rules/comment-content.js +4 -4
- package/rules/consistent-boolean-name.js +727 -42
- package/rules/consistent-compound-words.js +1 -2
- package/rules/consistent-json-file-read.js +2 -5
- package/rules/dom-node-dataset.js +3 -7
- package/rules/escape-case.js +4 -4
- package/rules/expiring-todo-comments.js +25 -2
- package/rules/explicit-length-check.js +3 -3
- package/rules/filename-case.js +4 -4
- 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/remove-argument.js +34 -8
- package/rules/fix/remove-statement.js +1 -3
- package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
- package/rules/id-match.js +2 -2
- package/rules/index.js +10 -0
- package/rules/isolated-functions.js +203 -19
- package/rules/name-replacements.js +1 -2
- package/rules/no-anonymous-default-export.js +1 -3
- package/rules/no-array-callback-reference.js +2 -10
- package/rules/no-async-promise-finally.js +111 -0
- package/rules/no-chained-comparison.js +1 -1
- package/rules/no-collection-bracket-access.js +27 -4
- package/rules/no-computed-property-existence-check.js +1 -4
- package/rules/no-declarations-before-early-exit.js +6 -5
- package/rules/no-error-property-assignment.js +3 -3
- package/rules/no-for-loop.js +4 -4
- package/rules/no-keyword-prefix.js +2 -2
- package/rules/no-manually-wrapped-comments.js +5 -1
- package/rules/no-mismatched-map-key.js +13 -20
- package/rules/no-named-default.js +2 -5
- package/rules/no-negated-array-predicate.js +6 -13
- package/rules/no-negated-comparison.js +31 -71
- package/rules/no-negated-condition.js +2 -2
- package/rules/no-negation-in-equality-check.js +4 -7
- package/rules/no-non-function-verb-prefix.js +25 -2
- package/rules/no-process-exit.js +6 -6
- package/rules/no-return-array-push.js +9 -2
- package/rules/no-static-only-class.js +2 -8
- package/rules/no-thenable.js +9 -15
- package/rules/no-typeof-undefined.js +2 -2
- package/rules/no-undeclared-class-members.js +1 -5
- package/rules/no-unnecessary-array-flat-map.js +308 -0
- package/rules/no-unnecessary-await.js +2 -2
- package/rules/no-unnecessary-fetch-options.js +624 -0
- package/rules/no-unnecessary-global-this.js +5 -9
- package/rules/no-unsafe-promise-all-settled-values.js +865 -0
- package/rules/no-unused-properties.js +0 -1
- package/rules/no-useless-coercion.js +1 -2
- 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/no-useless-undefined.js +2 -2
- package/rules/prefer-abort-signal-any.js +1391 -0
- package/rules/prefer-array-find.js +2 -5
- package/rules/prefer-array-flat-map.js +6 -11
- package/rules/prefer-array-from-range.js +98 -0
- package/rules/prefer-bigint-literals.js +1 -0
- package/rules/prefer-block-statement-over-iife.js +159 -0
- package/rules/prefer-boolean-return.js +20 -3
- package/rules/prefer-continue.js +6 -2
- package/rules/prefer-default-parameters.js +2 -2
- package/rules/prefer-early-return.js +3 -1
- package/rules/prefer-else-if.js +5 -9
- package/rules/prefer-group-by.js +576 -0
- package/rules/prefer-has-check.js +6 -3
- package/rules/prefer-https.js +3 -3
- package/rules/prefer-identifier-import-export-specifiers.js +1 -3
- package/rules/prefer-iterator-helpers.js +196 -0
- package/rules/prefer-minimal-ternary.js +4 -4
- package/rules/prefer-number-coercion.js +1 -1
- package/rules/prefer-object-define-properties.js +1 -3
- package/rules/prefer-object-iterable-methods.js +57 -14
- package/rules/prefer-observer-apis.js +2 -5
- package/rules/prefer-private-class-fields.js +1 -3
- package/rules/prefer-simple-sort-comparator.js +6 -6
- package/rules/prefer-simplified-conditions.js +584 -0
- package/rules/prefer-single-object-destructuring.js +2 -5
- package/rules/prefer-single-replace.js +2 -2
- package/rules/prefer-spread.js +25 -32
- package/rules/prefer-string-pad-start-end.js +1 -6
- package/rules/prefer-string-raw.js +2 -2
- package/rules/prefer-string-repeat.js +16 -9
- package/rules/prefer-string-replace-all.js +1 -1
- package/rules/prefer-ternary.js +5 -5
- package/rules/prefer-toggle-attribute.js +3 -3
- package/rules/prefer-top-level-await.js +6 -1
- package/rules/prefer-uint8array-base64.js +3 -1
- package/rules/require-proxy-trap-boolean-return.js +2 -2
- package/rules/rule/to-eslint-create.js +1 -2
- package/rules/rule/to-eslint-listener.js +1 -3
- package/rules/rule/to-eslint-problem.js +1 -3
- package/rules/rule/to-eslint-rule-fixer.js +1 -3
- package/rules/rule/to-eslint-rule.js +1 -3
- package/rules/rule/to-eslint-rules.js +1 -3
- package/rules/rule/unicorn-context.js +1 -3
- package/rules/rule/unicorn-listeners.js +1 -3
- package/rules/shared/array-range.js +66 -0
- package/rules/shared/iterator-helpers.js +43 -1
- package/rules/shared/no-array-mutate-rule.js +1 -3
- package/rules/shared/regexp-escape.js +4 -4
- package/rules/utils/can-tokens-be-adjacent.js +95 -0
- package/rules/utils/comparison.js +65 -0
- package/rules/utils/escape-string.js +2 -8
- package/rules/utils/get-available-variable-name.js +2 -13
- package/rules/utils/get-precedence.js +112 -0
- package/rules/utils/get-token-store.js +1 -3
- package/rules/utils/global-reference-tracker.js +3 -6
- package/rules/utils/index.js +4 -0
- package/rules/utils/is-boolean.js +103 -30
- package/rules/utils/is-identifier-name.js +5 -0
- package/rules/utils/is-promise-type.js +1 -3
- package/rules/utils/is-react-hook-name.js +5 -0
- package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
- package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
- package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
- package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
- package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
- package/rules/utils/to-location.js +1 -3
- package/rules/utils/track-branch-exits.js +8 -7
|
@@ -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;
|
|
@@ -22,15 +98,103 @@ const create = context => {
|
|
|
22
98
|
|
|
23
99
|
options.comments = options.comments.map(comment => comment.toLowerCase());
|
|
24
100
|
|
|
25
|
-
const
|
|
101
|
+
const configuredGlobals = {
|
|
26
102
|
...(globals[`es${context.languageOptions.ecmaVersion}`] ?? globals.builtins),
|
|
27
103
|
...context.languageOptions.globals,
|
|
28
|
-
...options.overrideGlobals,
|
|
29
104
|
};
|
|
30
105
|
const checked = new WeakSet();
|
|
31
106
|
|
|
107
|
+
const getAllowedGlobalValue = reference => {
|
|
108
|
+
const {identifier, resolved} = reference;
|
|
109
|
+
const {name} = identifier;
|
|
110
|
+
|
|
111
|
+
if (Object.hasOwn(options.overrideGlobals, name)) {
|
|
112
|
+
const overrideGlobalValue = options.overrideGlobals[name];
|
|
113
|
+
|
|
114
|
+
if (overrideGlobalValue === 'off') {
|
|
115
|
+
return 'off';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (resolved && !sourceCode.isGlobalReference(identifier)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return overrideGlobalValue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
Object.hasOwn(configuredGlobals, name)
|
|
127
|
+
&& configuredGlobals[name] === 'off'
|
|
128
|
+
) {
|
|
129
|
+
return 'off';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!sourceCode.isGlobalReference(identifier)) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
!Object.hasOwn(configuredGlobals, name)
|
|
138
|
+
&& !resolved?.eslintExplicitGlobal
|
|
139
|
+
) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return resolved?.writeable ? 'writable' : 'readonly';
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
function * getFunctionContextProblems(node, reason, root = node) {
|
|
147
|
+
const messageId = functionContextReferenceMessageIds.get(node.type);
|
|
148
|
+
if (messageId) {
|
|
149
|
+
yield {
|
|
150
|
+
node,
|
|
151
|
+
messageId,
|
|
152
|
+
data: {reason},
|
|
153
|
+
};
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (node !== root) {
|
|
158
|
+
if (functionTypes.includes(node.type) && node.type !== 'ArrowFunctionExpression') {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
|
|
163
|
+
if (node.superClass) {
|
|
164
|
+
yield * getFunctionContextProblems(node.superClass, reason, root);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const classElement of node.body.body) {
|
|
168
|
+
if (classElement.computed) {
|
|
169
|
+
yield * getFunctionContextProblems(classElement.key, reason, root);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const key of sourceCode.visitorKeys[node.type] ?? []) {
|
|
178
|
+
const value = node[key];
|
|
179
|
+
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
for (const childNode of value) {
|
|
182
|
+
if (childNode?.type) {
|
|
183
|
+
yield * getFunctionContextProblems(childNode, reason, root);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (value?.type) {
|
|
191
|
+
yield * getFunctionContextProblems(value, reason, root);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
32
196
|
/** @param {import('estree').Node} node */
|
|
33
|
-
const
|
|
197
|
+
const reportIsolatedFunctionProblems = (node, reason) => {
|
|
34
198
|
if (checked.has(node) || !functionTypes.includes(node.type)) {
|
|
35
199
|
return;
|
|
36
200
|
}
|
|
@@ -47,18 +211,20 @@ const create = context => {
|
|
|
47
211
|
continue;
|
|
48
212
|
}
|
|
49
213
|
|
|
50
|
-
|
|
214
|
+
const allowedGlobalValue = getAllowedGlobalValue(reference);
|
|
215
|
+
let problemReason = reason;
|
|
216
|
+
|
|
217
|
+
if (allowedGlobalValue !== undefined && allowedGlobalValue !== 'off') {
|
|
51
218
|
if (reference.isReadOnly()) {
|
|
52
219
|
continue;
|
|
53
220
|
}
|
|
54
221
|
|
|
55
|
-
const
|
|
56
|
-
const isGlobalWritable = [true, 'writable', 'writeable'].includes(globalsValue);
|
|
222
|
+
const isGlobalWritable = [true, 'writable', 'writeable'].includes(allowedGlobalValue);
|
|
57
223
|
if (isGlobalWritable) {
|
|
58
224
|
continue;
|
|
59
225
|
}
|
|
60
226
|
|
|
61
|
-
|
|
227
|
+
problemReason += ' (global variable is not writable)';
|
|
62
228
|
}
|
|
63
229
|
|
|
64
230
|
// Could consider checking for typeof operator here, like in no-undef?
|
|
@@ -66,15 +232,36 @@ const create = context => {
|
|
|
66
232
|
context.report({
|
|
67
233
|
node: identifier,
|
|
68
234
|
messageId: MESSAGE_ID_EXTERNALLY_SCOPED_VARIABLE,
|
|
69
|
-
data: {name: identifier.name, reason},
|
|
235
|
+
data: {name: identifier.name, reason: problemReason},
|
|
70
236
|
});
|
|
71
237
|
}
|
|
238
|
+
|
|
239
|
+
for (const problem of getFunctionContextProblems(node, reason)) {
|
|
240
|
+
context.report(problem);
|
|
241
|
+
}
|
|
72
242
|
};
|
|
73
243
|
|
|
74
244
|
const isComment = token => token?.type === 'Block' || token?.type === 'Line';
|
|
75
245
|
|
|
246
|
+
const canCommentApplyToParent = (node, parent) =>
|
|
247
|
+
parent && (
|
|
248
|
+
[
|
|
249
|
+
'VariableDeclarator',
|
|
250
|
+
'VariableDeclaration',
|
|
251
|
+
'ExportNamedDeclaration',
|
|
252
|
+
'ExportDefaultDeclaration',
|
|
253
|
+
].includes(parent.type)
|
|
254
|
+
|| (
|
|
255
|
+
(
|
|
256
|
+
parent.type === 'Property'
|
|
257
|
+
|| parent.type === 'MethodDefinition'
|
|
258
|
+
)
|
|
259
|
+
&& parent.value === node
|
|
260
|
+
)
|
|
261
|
+
);
|
|
262
|
+
|
|
76
263
|
/**
|
|
77
|
-
Find a comment on this node or its parent, in cases where the node passed is part of a variable or
|
|
264
|
+
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
265
|
@param {import('estree').Node} node
|
|
79
266
|
*/
|
|
80
267
|
const findComment = node => {
|
|
@@ -82,12 +269,7 @@ const create = context => {
|
|
|
82
269
|
let commentableNode = node;
|
|
83
270
|
while (
|
|
84
271
|
!isComment(previousToken)
|
|
85
|
-
&&
|
|
86
|
-
'VariableDeclarator',
|
|
87
|
-
'VariableDeclaration',
|
|
88
|
-
'ExportNamedDeclaration',
|
|
89
|
-
'ExportDefaultDeclaration',
|
|
90
|
-
].includes(commentableNode.parent.type)
|
|
272
|
+
&& canCommentApplyToParent(commentableNode, commentableNode.parent)
|
|
91
273
|
) {
|
|
92
274
|
commentableNode = commentableNode.parent;
|
|
93
275
|
previousToken = sourceCode.getTokenBefore(commentableNode, {includeComments: true});
|
|
@@ -127,6 +309,8 @@ const create = context => {
|
|
|
127
309
|
) {
|
|
128
310
|
return `callee of function named ${JSON.stringify(node.parent.callee.name)}`;
|
|
129
311
|
}
|
|
312
|
+
|
|
313
|
+
return getDefaultArgumentCallReason(node) ?? getExecuteScriptPropertyReason(node);
|
|
130
314
|
};
|
|
131
315
|
|
|
132
316
|
context.onExit(
|
|
@@ -137,7 +321,7 @@ const create = context => {
|
|
|
137
321
|
return;
|
|
138
322
|
}
|
|
139
323
|
|
|
140
|
-
return
|
|
324
|
+
return reportIsolatedFunctionProblems(node, reason);
|
|
141
325
|
},
|
|
142
326
|
);
|
|
143
327
|
|
|
@@ -146,7 +330,7 @@ const create = context => {
|
|
|
146
330
|
selector,
|
|
147
331
|
node => {
|
|
148
332
|
const reason = `matches selector ${JSON.stringify(selector)}`;
|
|
149
|
-
return
|
|
333
|
+
return reportIsolatedFunctionProblems(node, reason);
|
|
150
334
|
},
|
|
151
335
|
);
|
|
152
336
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {isRegExp} from 'node:util/types';
|
|
3
|
-
import helperValidatorIdentifier from '@babel/helper-validator-identifier';
|
|
4
3
|
import {
|
|
5
4
|
getAvailableVariableName,
|
|
6
5
|
cartesianProductSamples,
|
|
@@ -9,6 +8,7 @@ import {
|
|
|
9
8
|
upperFirst,
|
|
10
9
|
lowerFirst,
|
|
11
10
|
isVirtualFilename,
|
|
11
|
+
isIdentifierName,
|
|
12
12
|
} from './utils/index.js';
|
|
13
13
|
import {defaultReplacements, defaultAllowList, defaultIgnore} from './shared/name-replacements.js';
|
|
14
14
|
import {
|
|
@@ -24,7 +24,6 @@ import {functionTypes} from './ast/index.js';
|
|
|
24
24
|
const MESSAGE_ID_REPLACE = 'replace';
|
|
25
25
|
const MESSAGE_ID_SUGGESTION = 'suggestion';
|
|
26
26
|
const MESSAGE_ID_RENAME = 'rename';
|
|
27
|
-
const {isIdentifierName} = helperValidatorIdentifier;
|
|
28
27
|
const anotherNameMessage = 'A more descriptive name will do too.';
|
|
29
28
|
const messages = {
|
|
30
29
|
[MESSAGE_ID_REPLACE]: `The {{nameTypeText}} \`{{discouragedName}}\` should be named \`{{replacement}}\`. ${anotherNameMessage}`,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {getFunctionHeadLocation, getFunctionNameWithKind, isOpeningParenToken} from '@eslint-community/eslint-utils';
|
|
3
|
-
import helperValidatorIdentifier from '@babel/helper-validator-identifier';
|
|
4
3
|
import {camelCase} from 'change-case';
|
|
5
4
|
import {
|
|
6
5
|
getClassHeadLocation,
|
|
@@ -9,11 +8,10 @@ import {
|
|
|
9
8
|
getAvailableVariableName,
|
|
10
9
|
upperFirst,
|
|
11
10
|
isVirtualFilename,
|
|
11
|
+
isIdentifierName,
|
|
12
12
|
} from './utils/index.js';
|
|
13
13
|
import {isMemberExpression} from './ast/index.js';
|
|
14
14
|
|
|
15
|
-
const {isIdentifierName} = helperValidatorIdentifier;
|
|
16
|
-
|
|
17
15
|
const MESSAGE_ID_ERROR = 'no-anonymous-default-export/error';
|
|
18
16
|
const MESSAGE_ID_SUGGESTION = 'no-anonymous-default-export/suggestion';
|
|
19
17
|
const messages = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import isIdentifier from 'is-identifier';
|
|
2
2
|
import {findVariable} from '@eslint-community/eslint-utils';
|
|
3
3
|
import typedArray from './shared/typed-array.js';
|
|
4
4
|
import {isMethodCall, isUndefined} from './ast/index.js';
|
|
@@ -22,11 +22,6 @@ const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';
|
|
|
22
22
|
const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name';
|
|
23
23
|
const REPLACE_WITH_NAME_MESSAGE_ID = 'replace-with-name';
|
|
24
24
|
const REPLACE_WITHOUT_NAME_MESSAGE_ID = 'replace-without-name';
|
|
25
|
-
const {
|
|
26
|
-
isIdentifierName,
|
|
27
|
-
isKeyword,
|
|
28
|
-
isStrictBindReservedWord,
|
|
29
|
-
} = helperValidatorIdentifier;
|
|
30
25
|
const messages = {
|
|
31
26
|
[ERROR_WITH_NAME_MESSAGE_ID]: 'Do not pass function `{{name}}` directly to `.{{method}}(…)`.',
|
|
32
27
|
[ERROR_WITHOUT_NAME_MESSAGE_ID]: 'Do not pass function directly to `.{{method}}(…)`.',
|
|
@@ -301,10 +296,7 @@ function isDefinitelyNotFunctionValue(node, context, visitedVariables = new Set(
|
|
|
301
296
|
: false;
|
|
302
297
|
}
|
|
303
298
|
|
|
304
|
-
const isValidParameterName =
|
|
305
|
-
isIdentifierName(name)
|
|
306
|
-
&& !isKeyword(name)
|
|
307
|
-
&& !isStrictBindReservedWord(name, true);
|
|
299
|
+
const isValidParameterName = isIdentifier;
|
|
308
300
|
|
|
309
301
|
function getSuggestionParameters(callExpression, callback, parameters) {
|
|
310
302
|
if (callback.type !== 'Identifier') {
|
|
@@ -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;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {hasSideEffect} from '@eslint-community/eslint-utils';
|
|
2
|
+
import {isBooleanLiteral} from './ast/index.js';
|
|
2
3
|
import {getParenthesizedText, isParenthesized, isBoolean} from './utils/index.js';
|
|
3
4
|
|
|
4
5
|
const MESSAGE_ID_ERROR = 'no-chained-comparison/error';
|
|
@@ -13,7 +14,6 @@ const equalityOperators = new Set(['===', '!==', '==', '!=']);
|
|
|
13
14
|
const comparisonOperators = orderingOperators.union(equalityOperators);
|
|
14
15
|
|
|
15
16
|
const isComparison = node => node.type === 'BinaryExpression' && comparisonOperators.has(node.operator);
|
|
16
|
-
const isBooleanLiteral = node => node.type === 'Literal' && typeof node.value === 'boolean';
|
|
17
17
|
|
|
18
18
|
// The `&&` rewrite is a `LogicalExpression`, which binds looser than any comparison. It can only
|
|
19
19
|
// replace `node` where a surrounding operator won't recapture it: at the top of an expression or
|
|
@@ -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
|
}
|
|
@@ -134,10 +134,7 @@ const create = context => {
|
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
context.on('BinaryExpression', node => {
|
|
137
|
-
if (
|
|
138
|
-
node.operator === 'in'
|
|
139
|
-
&& !isStaticPropertyKey(node.left)
|
|
140
|
-
)) {
|
|
137
|
+
if (node.operator !== 'in' || isStaticPropertyKey(node.left)) {
|
|
141
138
|
return;
|
|
142
139
|
}
|
|
143
140
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import {isCommentToken, hasSideEffect} from '@eslint-community/eslint-utils';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
containsSuspensionPoint,
|
|
4
|
+
getReferences,
|
|
5
|
+
isReactHookName,
|
|
6
|
+
trackBranchExits,
|
|
7
|
+
} from './utils/index.js';
|
|
3
8
|
|
|
4
9
|
const MESSAGE_ID = 'no-declarations-before-early-exit';
|
|
5
10
|
const messages = {
|
|
@@ -23,10 +28,6 @@ function isGuardStatement(node, branchAlwaysExits) {
|
|
|
23
28
|
return consequentExits !== alternateExits;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
|
|
27
|
-
const isReactHookName = name =>
|
|
28
|
-
name === 'use' || /^use[\dA-Z]/.test(name);
|
|
29
|
-
|
|
30
31
|
// Matches `useFoo(…)` and `Namespace.useFoo(…)` (for example, `React.useMemo(…)`).
|
|
31
32
|
const isReactHookCall = node => {
|
|
32
33
|
if (node?.type !== 'CallExpression') {
|
|
@@ -142,7 +142,7 @@ const getLocalUpdateFrame = knownErrorVariableFrames =>
|
|
|
142
142
|
?? knownErrorVariableFrames.at(-1);
|
|
143
143
|
|
|
144
144
|
const updateExistingKnownErrorVariable = (knownErrorVariableFrames, variable, constructorName) => {
|
|
145
|
-
let
|
|
145
|
+
let isCrossesFunctionBoundary = false;
|
|
146
146
|
|
|
147
147
|
for (const frame of knownErrorVariableFrames.toReversed()) {
|
|
148
148
|
if (frame.knownErrorVariables.has(variable)) {
|
|
@@ -151,7 +151,7 @@ const updateExistingKnownErrorVariable = (knownErrorVariableFrames, variable, co
|
|
|
151
151
|
return true;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
if (!
|
|
154
|
+
if (!isCrossesFunctionBoundary) {
|
|
155
155
|
frame.knownErrorVariables.set(variable, undefined);
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -160,7 +160,7 @@ const updateExistingKnownErrorVariable = (knownErrorVariableFrames, variable, co
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
if (frame.isFunctionBoundary) {
|
|
163
|
-
|
|
163
|
+
isCrossesFunctionBoundary = true;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
package/rules/no-for-loop.js
CHANGED
|
@@ -876,14 +876,14 @@ const create = context => {
|
|
|
876
876
|
|
|
877
877
|
let declarationElement = element;
|
|
878
878
|
let declarationType = 'const';
|
|
879
|
-
let
|
|
879
|
+
let isRemoveDeclaration = true;
|
|
880
880
|
|
|
881
881
|
if (elementNode) {
|
|
882
882
|
if (elementNode.id.type === 'ObjectPattern' || elementNode.id.type === 'ArrayPattern') {
|
|
883
|
-
|
|
883
|
+
isRemoveDeclaration = arrayReferences.length === 1;
|
|
884
884
|
}
|
|
885
885
|
|
|
886
|
-
if (
|
|
886
|
+
if (isRemoveDeclaration) {
|
|
887
887
|
declarationType = elementNode.parent.kind;
|
|
888
888
|
declarationElement = sourceCode.getText(elementNode.id);
|
|
889
889
|
}
|
|
@@ -911,7 +911,7 @@ const create = context => {
|
|
|
911
911
|
}
|
|
912
912
|
|
|
913
913
|
if (elementNode) {
|
|
914
|
-
yield
|
|
914
|
+
yield isRemoveDeclaration
|
|
915
915
|
? fixer.removeRange(getRemovalRange(elementNode, sourceCode))
|
|
916
916
|
: fixer.replaceText(elementNode.init, element);
|
|
917
917
|
}
|
|
@@ -90,12 +90,12 @@ function reportObjectPatternAndShouldSkipPropertyCheck(report, node, options) {
|
|
|
90
90
|
return true;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
const
|
|
93
|
+
const isAssignmentKeyEqualsValue = parent.key.name === parent.value.name;
|
|
94
94
|
|
|
95
95
|
const valueIsInvalid = parent.value.name && Boolean(keyword);
|
|
96
96
|
|
|
97
97
|
// Ignore destructuring if the option is set, unless a new identifier is created
|
|
98
|
-
if (valueIsInvalid && !
|
|
98
|
+
if (valueIsInvalid && !isAssignmentKeyEqualsValue) {
|
|
99
99
|
report(node, keyword);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -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)
|