eslint-plugin-unicorn 51.0.1 → 52.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/configs/recommended.js +3 -0
- package/package.json +4 -3
- package/readme.md +4 -1
- package/rules/filename-case.js +41 -8
- package/rules/import-style.js +6 -0
- package/rules/no-anonymous-default-export.js +212 -0
- package/rules/no-array-callback-reference.js +71 -42
- package/rules/no-await-in-promise-methods.js +68 -0
- package/rules/no-single-promise-in-promise-methods.js +167 -0
- package/rules/no-useless-fallback-in-spread.js +1 -2
- package/rules/prefer-array-find.js +28 -0
- package/rules/prefer-prototype-methods.js +128 -56
- package/rules/prefer-spread.js +6 -19
- package/rules/utils/avoid-capture.js +2 -1
- package/rules/utils/cartesian-product-samples.js +1 -1
- package/rules/utils/escape-string.js +1 -1
- package/rules/utils/escape-template-element-raw.js +2 -1
- package/rules/utils/get-documentation-url.js +1 -1
- package/rules/utils/get-variable-identifiers.js +2 -1
- package/rules/utils/has-same-range.js +2 -1
- package/rules/utils/index.js +2 -1
- package/rules/utils/is-node-value-not-function.js +0 -1
- package/rules/utils/is-object-method.js +1 -1
- package/rules/utils/is-value-not-usable.js +2 -1
- package/rules/utils/resolve-variable-name.js +1 -1
- package/rules/utils/rule.js +4 -0
- package/rules/utils/should-add-parentheses-to-await-expression-argument.js +21 -0
- package/rules/utils/should-add-parentheses-to-call-expression-callee.js +22 -0
- package/rules/utils/should-add-parentheses-to-spread-element-argument.js +0 -22
package/configs/recommended.js
CHANGED
|
@@ -14,12 +14,14 @@ module.exports = {
|
|
|
14
14
|
'unicorn/import-style': 'error',
|
|
15
15
|
'unicorn/new-for-builtins': 'error',
|
|
16
16
|
'unicorn/no-abusive-eslint-disable': 'error',
|
|
17
|
+
'unicorn/no-anonymous-default-export': 'error',
|
|
17
18
|
'unicorn/no-array-callback-reference': 'error',
|
|
18
19
|
'unicorn/no-array-for-each': 'error',
|
|
19
20
|
'unicorn/no-array-method-this-argument': 'error',
|
|
20
21
|
'unicorn/no-array-push-push': 'error',
|
|
21
22
|
'unicorn/no-array-reduce': 'error',
|
|
22
23
|
'unicorn/no-await-expression-member': 'error',
|
|
24
|
+
'unicorn/no-await-in-promise-methods': 'error',
|
|
23
25
|
'unicorn/no-console-spaces': 'error',
|
|
24
26
|
'unicorn/no-document-cookie': 'error',
|
|
25
27
|
'unicorn/no-empty-file': 'error',
|
|
@@ -38,6 +40,7 @@ module.exports = {
|
|
|
38
40
|
'unicorn/no-null': 'error',
|
|
39
41
|
'unicorn/no-object-as-default-parameter': 'error',
|
|
40
42
|
'unicorn/no-process-exit': 'error',
|
|
43
|
+
'unicorn/no-single-promise-in-promise-methods': 'error',
|
|
41
44
|
'unicorn/no-static-only-class': 'error',
|
|
42
45
|
'unicorn/no-thenable': 'error',
|
|
43
46
|
'unicorn/no-this-assignment': 'error',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-unicorn",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "52.0.0",
|
|
4
4
|
"description": "More than 100 powerful ESLint rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/eslint-plugin-unicorn",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"enquirer": "^2.4.1",
|
|
79
79
|
"eslint": "^8.56.0",
|
|
80
80
|
"eslint-ava-rule-tester": "^5.0.1",
|
|
81
|
-
"eslint-doc-generator": "^1.
|
|
81
|
+
"eslint-doc-generator": "^1.7.0",
|
|
82
82
|
"eslint-plugin-eslint-plugin": "^5.2.1",
|
|
83
83
|
"eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
|
|
84
84
|
"eslint-remote-tester": "^3.0.1",
|
|
@@ -134,7 +134,8 @@
|
|
|
134
134
|
]
|
|
135
135
|
}
|
|
136
136
|
],
|
|
137
|
-
"import/order": "off"
|
|
137
|
+
"import/order": "off",
|
|
138
|
+
"func-names": "off"
|
|
138
139
|
},
|
|
139
140
|
"overrides": [
|
|
140
141
|
{
|
package/readme.md
CHANGED
|
@@ -106,7 +106,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
106
106
|
💼 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs) enabled in.\
|
|
107
107
|
✅ Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs).\
|
|
108
108
|
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
|
109
|
-
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/
|
|
109
|
+
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
110
110
|
|
|
111
111
|
| Name | Description | 💼 | 🔧 | 💡 |
|
|
112
112
|
| :----------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
|
|
@@ -124,12 +124,14 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
124
124
|
| [import-style](docs/rules/import-style.md) | Enforce specific import styles per module. | ✅ | | |
|
|
125
125
|
| [new-for-builtins](docs/rules/new-for-builtins.md) | Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. | ✅ | 🔧 | |
|
|
126
126
|
| [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) | Enforce specifying rules to disable in `eslint-disable` comments. | ✅ | | |
|
|
127
|
+
| [no-anonymous-default-export](docs/rules/no-anonymous-default-export.md) | Disallow anonymous functions and classes as the default export. | ✅ | | 💡 |
|
|
127
128
|
| [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. | ✅ | | 💡 |
|
|
128
129
|
| [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `for…of` over the `forEach` method. | ✅ | 🔧 | 💡 |
|
|
129
130
|
| [no-array-method-this-argument](docs/rules/no-array-method-this-argument.md) | Disallow using the `this` argument in array methods. | ✅ | 🔧 | 💡 |
|
|
130
131
|
| [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. | ✅ | 🔧 | 💡 |
|
|
131
132
|
| [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. | ✅ | | |
|
|
132
133
|
| [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. | ✅ | 🔧 | |
|
|
134
|
+
| [no-await-in-promise-methods](docs/rules/no-await-in-promise-methods.md) | Disallow using `await` in `Promise` method parameters. | ✅ | | 💡 |
|
|
133
135
|
| [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. | ✅ | 🔧 | |
|
|
134
136
|
| [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. | ✅ | | |
|
|
135
137
|
| [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. | ✅ | | |
|
|
@@ -146,6 +148,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
146
148
|
| [no-null](docs/rules/no-null.md) | Disallow the use of the `null` literal. | ✅ | 🔧 | 💡 |
|
|
147
149
|
| [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) | Disallow the use of objects as default parameters. | ✅ | | |
|
|
148
150
|
| [no-process-exit](docs/rules/no-process-exit.md) | Disallow `process.exit()`. | ✅ | | |
|
|
151
|
+
| [no-single-promise-in-promise-methods](docs/rules/no-single-promise-in-promise-methods.md) | Disallow passing single-element arrays to `Promise` methods. | ✅ | 🔧 | 💡 |
|
|
149
152
|
| [no-static-only-class](docs/rules/no-static-only-class.md) | Disallow classes that only have static members. | ✅ | 🔧 | |
|
|
150
153
|
| [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | ✅ | | |
|
|
151
154
|
| [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | |
|
package/rules/filename-case.js
CHANGED
|
@@ -85,7 +85,7 @@ function validateFilename(words, caseFunctions) {
|
|
|
85
85
|
.every(({word}) => caseFunctions.some(caseFunction => caseFunction(word) === word));
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function fixFilename(words, caseFunctions, {leading,
|
|
88
|
+
function fixFilename(words, caseFunctions, {leading, trailing}) {
|
|
89
89
|
const replacements = words
|
|
90
90
|
.map(({word, ignored}) => ignored ? [word] : caseFunctions.map(caseFunction => caseFunction(word)));
|
|
91
91
|
|
|
@@ -93,7 +93,30 @@ function fixFilename(words, caseFunctions, {leading, extension}) {
|
|
|
93
93
|
samples: combinations,
|
|
94
94
|
} = cartesianProductSamples(replacements);
|
|
95
95
|
|
|
96
|
-
return [...new Set(combinations.map(parts => `${leading}${parts.join('')}${
|
|
96
|
+
return [...new Set(combinations.map(parts => `${leading}${parts.join('')}${trailing}`))];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getFilenameParts(filenameWithExtension, {multipleFileExtensions}) {
|
|
100
|
+
const extension = path.extname(filenameWithExtension);
|
|
101
|
+
const filename = path.basename(filenameWithExtension, extension);
|
|
102
|
+
const basename = filename + extension;
|
|
103
|
+
|
|
104
|
+
const parts = {
|
|
105
|
+
basename,
|
|
106
|
+
filename,
|
|
107
|
+
middle: '',
|
|
108
|
+
extension,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (multipleFileExtensions) {
|
|
112
|
+
const [firstPart] = filename.split('.');
|
|
113
|
+
Object.assign(parts, {
|
|
114
|
+
filename: firstPart,
|
|
115
|
+
middle: filename.slice(firstPart.length),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return parts;
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
const leadingUnderscoresRegex = /^(?<leading>_+)(?<tailing>.*)$/;
|
|
@@ -143,6 +166,7 @@ const create = context => {
|
|
|
143
166
|
|
|
144
167
|
return new RegExp(item, 'u');
|
|
145
168
|
});
|
|
169
|
+
const multipleFileExtensions = options.multipleFileExtensions !== false;
|
|
146
170
|
const chosenCasesFunctions = chosenCases.map(case_ => ignoreNumbers(cases[case_].fn));
|
|
147
171
|
const filenameWithExtension = context.physicalFilename;
|
|
148
172
|
|
|
@@ -152,11 +176,14 @@ const create = context => {
|
|
|
152
176
|
|
|
153
177
|
return {
|
|
154
178
|
Program() {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
const {
|
|
180
|
+
basename,
|
|
181
|
+
filename,
|
|
182
|
+
middle,
|
|
183
|
+
extension,
|
|
184
|
+
} = getFilenameParts(filenameWithExtension, {multipleFileExtensions});
|
|
158
185
|
|
|
159
|
-
if (ignoredByDefault.has(
|
|
186
|
+
if (ignoredByDefault.has(basename) || ignore.some(regexp => regexp.test(basename))) {
|
|
160
187
|
return;
|
|
161
188
|
}
|
|
162
189
|
|
|
@@ -168,7 +195,7 @@ const create = context => {
|
|
|
168
195
|
return {
|
|
169
196
|
loc: {column: 0, line: 1},
|
|
170
197
|
messageId: MESSAGE_ID_EXTENSION,
|
|
171
|
-
data: {filename: filename + extension.toLowerCase(), extension},
|
|
198
|
+
data: {filename: filename + middle + extension.toLowerCase(), extension},
|
|
172
199
|
};
|
|
173
200
|
}
|
|
174
201
|
|
|
@@ -177,7 +204,7 @@ const create = context => {
|
|
|
177
204
|
|
|
178
205
|
const renamedFilenames = fixFilename(words, chosenCasesFunctions, {
|
|
179
206
|
leading,
|
|
180
|
-
extension,
|
|
207
|
+
trailing: middle + extension.toLowerCase(),
|
|
181
208
|
});
|
|
182
209
|
|
|
183
210
|
return {
|
|
@@ -211,6 +238,9 @@ const schema = [
|
|
|
211
238
|
type: 'array',
|
|
212
239
|
uniqueItems: true,
|
|
213
240
|
},
|
|
241
|
+
multipleFileExtensions: {
|
|
242
|
+
type: 'boolean',
|
|
243
|
+
},
|
|
214
244
|
},
|
|
215
245
|
additionalProperties: false,
|
|
216
246
|
},
|
|
@@ -237,6 +267,9 @@ const schema = [
|
|
|
237
267
|
type: 'array',
|
|
238
268
|
uniqueItems: true,
|
|
239
269
|
},
|
|
270
|
+
multipleFileExtensions: {
|
|
271
|
+
type: 'boolean',
|
|
272
|
+
},
|
|
240
273
|
},
|
|
241
274
|
additionalProperties: false,
|
|
242
275
|
},
|
package/rules/import-style.js
CHANGED
|
@@ -117,9 +117,15 @@ const defaultStyles = {
|
|
|
117
117
|
path: {
|
|
118
118
|
default: true,
|
|
119
119
|
},
|
|
120
|
+
'node:path': {
|
|
121
|
+
default: true,
|
|
122
|
+
},
|
|
120
123
|
util: {
|
|
121
124
|
named: true,
|
|
122
125
|
},
|
|
126
|
+
'node:util': {
|
|
127
|
+
named: true,
|
|
128
|
+
},
|
|
123
129
|
};
|
|
124
130
|
|
|
125
131
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const {
|
|
5
|
+
getFunctionHeadLocation,
|
|
6
|
+
getFunctionNameWithKind,
|
|
7
|
+
isOpeningParenToken,
|
|
8
|
+
} = require('@eslint-community/eslint-utils');
|
|
9
|
+
const {
|
|
10
|
+
isIdentifierName,
|
|
11
|
+
} = require('@babel/helper-validator-identifier');
|
|
12
|
+
const getClassHeadLocation = require('./utils/get-class-head-location.js');
|
|
13
|
+
const {upperFirst, camelCase} = require('./utils/lodash.js');
|
|
14
|
+
const {getParenthesizedRange} = require('./utils/parentheses.js');
|
|
15
|
+
const {
|
|
16
|
+
getScopes,
|
|
17
|
+
avoidCapture,
|
|
18
|
+
} = require('./utils/index.js');
|
|
19
|
+
const {isMemberExpression} = require('./ast/index.js');
|
|
20
|
+
|
|
21
|
+
const MESSAGE_ID_ERROR = 'no-anonymous-default-export/error';
|
|
22
|
+
const MESSAGE_ID_SUGGESTION = 'no-anonymous-default-export/suggestion';
|
|
23
|
+
const messages = {
|
|
24
|
+
[MESSAGE_ID_ERROR]: 'The {{description}} should be named.',
|
|
25
|
+
[MESSAGE_ID_SUGGESTION]: 'Name it as `{{name}}`.',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const isClassKeywordToken = token => token.type === 'Keyword' && token.value === 'class';
|
|
29
|
+
const isAnonymousClassOrFunction = node =>
|
|
30
|
+
(
|
|
31
|
+
(
|
|
32
|
+
node.type === 'FunctionDeclaration'
|
|
33
|
+
|| node.type === 'FunctionExpression'
|
|
34
|
+
|| node.type === 'ClassDeclaration'
|
|
35
|
+
|| node.type === 'ClassExpression'
|
|
36
|
+
)
|
|
37
|
+
&& !node.id
|
|
38
|
+
)
|
|
39
|
+
|| node.type === 'ArrowFunctionExpression';
|
|
40
|
+
|
|
41
|
+
function getSuggestionName(node, filename, sourceCode) {
|
|
42
|
+
if (filename === '<input>' || filename === '<text>') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let [name] = path.basename(filename).split('.');
|
|
47
|
+
name = camelCase(name);
|
|
48
|
+
|
|
49
|
+
if (!isIdentifierName(name)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
name = node.type === 'ClassDeclaration' || node.type === 'ClassExpression' ? upperFirst(name) : name;
|
|
54
|
+
name = avoidCapture(name, getScopes(sourceCode.getScope(node)));
|
|
55
|
+
|
|
56
|
+
return name;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function addName(fixer, node, name, sourceCode) {
|
|
60
|
+
switch (node.type) {
|
|
61
|
+
case 'ClassDeclaration':
|
|
62
|
+
case 'ClassExpression': {
|
|
63
|
+
const lastDecorator = node.decorators?.at(-1);
|
|
64
|
+
const classToken = lastDecorator
|
|
65
|
+
? sourceCode.getTokenAfter(lastDecorator, isClassKeywordToken)
|
|
66
|
+
: sourceCode.getFirstToken(node, isClassKeywordToken);
|
|
67
|
+
return fixer.insertTextAfter(classToken, ` ${name}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
case 'FunctionDeclaration':
|
|
71
|
+
case 'FunctionExpression': {
|
|
72
|
+
const openingParenthesisToken = sourceCode.getFirstToken(
|
|
73
|
+
node,
|
|
74
|
+
isOpeningParenToken,
|
|
75
|
+
);
|
|
76
|
+
return fixer.insertTextBefore(
|
|
77
|
+
openingParenthesisToken,
|
|
78
|
+
`${sourceCode.text.charAt(openingParenthesisToken.range[0] - 1) === ' ' ? '' : ' '}${name} `,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'ArrowFunctionExpression': {
|
|
83
|
+
const [exportDeclarationStart, exportDeclarationEnd]
|
|
84
|
+
= node.parent.type === 'ExportDefaultDeclaration'
|
|
85
|
+
? node.parent.range
|
|
86
|
+
: node.parent.parent.range;
|
|
87
|
+
const [arrowFunctionStart, arrowFunctionEnd] = getParenthesizedRange(node, sourceCode);
|
|
88
|
+
|
|
89
|
+
let textBefore = sourceCode.text.slice(exportDeclarationStart, arrowFunctionStart);
|
|
90
|
+
let textAfter = sourceCode.text.slice(arrowFunctionEnd, exportDeclarationEnd);
|
|
91
|
+
|
|
92
|
+
textBefore = `\n${textBefore}`;
|
|
93
|
+
if (!/\s$/.test(textBefore)) {
|
|
94
|
+
textBefore = `${textBefore} `;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!textAfter.endsWith(';')) {
|
|
98
|
+
textAfter = `${textAfter};`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return [
|
|
102
|
+
fixer.replaceTextRange(
|
|
103
|
+
[exportDeclarationStart, arrowFunctionStart],
|
|
104
|
+
`const ${name} = `,
|
|
105
|
+
),
|
|
106
|
+
fixer.replaceTextRange(
|
|
107
|
+
[arrowFunctionEnd, exportDeclarationEnd],
|
|
108
|
+
';',
|
|
109
|
+
),
|
|
110
|
+
fixer.insertTextAfterRange(
|
|
111
|
+
[exportDeclarationEnd, exportDeclarationEnd],
|
|
112
|
+
`${textBefore}${name}${textAfter}`,
|
|
113
|
+
),
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// No default
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getProblem(node, context) {
|
|
122
|
+
const {sourceCode, physicalFilename} = context;
|
|
123
|
+
|
|
124
|
+
const suggestionName = getSuggestionName(node, physicalFilename, sourceCode);
|
|
125
|
+
|
|
126
|
+
let loc;
|
|
127
|
+
let description;
|
|
128
|
+
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
|
|
129
|
+
loc = getClassHeadLocation(node, sourceCode);
|
|
130
|
+
description = 'class';
|
|
131
|
+
} else {
|
|
132
|
+
loc = getFunctionHeadLocation(node, sourceCode);
|
|
133
|
+
// [TODO: @fisker]: Ask `@eslint-community/eslint-utils` to expose `getFunctionKind`
|
|
134
|
+
const nameWithKind = getFunctionNameWithKind(node);
|
|
135
|
+
description = nameWithKind.replace(/ '.*?'$/, '');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const problem = {
|
|
139
|
+
node,
|
|
140
|
+
loc,
|
|
141
|
+
messageId: MESSAGE_ID_ERROR,
|
|
142
|
+
data: {
|
|
143
|
+
description,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (!suggestionName) {
|
|
148
|
+
return problem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
problem.suggest = [
|
|
152
|
+
{
|
|
153
|
+
messageId: MESSAGE_ID_SUGGESTION,
|
|
154
|
+
data: {
|
|
155
|
+
name: suggestionName,
|
|
156
|
+
},
|
|
157
|
+
fix: fixer => addName(fixer, node, suggestionName, sourceCode),
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
return problem;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
165
|
+
const create = context => {
|
|
166
|
+
context.on('ExportDefaultDeclaration', node => {
|
|
167
|
+
if (!isAnonymousClassOrFunction(node.declaration)) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return getProblem(node.declaration, context);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
context.on('AssignmentExpression', node => {
|
|
175
|
+
if (
|
|
176
|
+
!isAnonymousClassOrFunction(node.right)
|
|
177
|
+
|| !(
|
|
178
|
+
node.parent.type === 'ExpressionStatement'
|
|
179
|
+
&& node.parent.expression === node
|
|
180
|
+
)
|
|
181
|
+
|| !(
|
|
182
|
+
isMemberExpression(node.left, {
|
|
183
|
+
object: 'module',
|
|
184
|
+
property: 'exports',
|
|
185
|
+
computed: false,
|
|
186
|
+
optional: false,
|
|
187
|
+
})
|
|
188
|
+
|| (
|
|
189
|
+
node.left.type === 'Identifier',
|
|
190
|
+
node.left.name === 'exports'
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return getProblem(node.right, context);
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
202
|
+
module.exports = {
|
|
203
|
+
create,
|
|
204
|
+
meta: {
|
|
205
|
+
type: 'suggestion',
|
|
206
|
+
docs: {
|
|
207
|
+
description: 'Disallow anonymous functions and classes as the default export.',
|
|
208
|
+
},
|
|
209
|
+
hasSuggestions: true,
|
|
210
|
+
messages,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const {isParenthesized} = require('@eslint-community/eslint-utils');
|
|
3
2
|
const {isMethodCall} = require('./ast/index.js');
|
|
4
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
isNodeMatches,
|
|
5
|
+
isNodeValueNotFunction,
|
|
6
|
+
isParenthesized,
|
|
7
|
+
getParenthesizedRange,
|
|
8
|
+
getParenthesizedText,
|
|
9
|
+
shouldAddParenthesesToCallExpressionCallee,
|
|
10
|
+
} = require('./utils/index.js');
|
|
5
11
|
|
|
6
12
|
const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';
|
|
7
13
|
const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name';
|
|
@@ -25,7 +31,7 @@ const iteratorMethods = new Map([
|
|
|
25
31
|
},
|
|
26
32
|
{
|
|
27
33
|
method: 'filter',
|
|
28
|
-
|
|
34
|
+
shouldIgnoreCallExpression: node => (node.callee.object.type === 'Identifier' && node.callee.object.name === 'Vue'),
|
|
29
35
|
ignore: [
|
|
30
36
|
'Boolean',
|
|
31
37
|
],
|
|
@@ -63,7 +69,7 @@ const iteratorMethods = new Map([
|
|
|
63
69
|
},
|
|
64
70
|
{
|
|
65
71
|
method: 'map',
|
|
66
|
-
|
|
72
|
+
shouldIgnoreCallExpression: node => (node.callee.object.type === 'Identifier' && node.callee.object.name === 'types'),
|
|
67
73
|
ignore: [
|
|
68
74
|
'String',
|
|
69
75
|
'Number',
|
|
@@ -104,35 +110,39 @@ const iteratorMethods = new Map([
|
|
|
104
110
|
ignore = [],
|
|
105
111
|
minParameters = 1,
|
|
106
112
|
returnsUndefined = false,
|
|
107
|
-
|
|
113
|
+
shouldIgnoreCallExpression,
|
|
108
114
|
}) => [method, {
|
|
109
115
|
minParameters,
|
|
110
116
|
parameters,
|
|
111
117
|
returnsUndefined,
|
|
112
|
-
|
|
118
|
+
shouldIgnoreCallExpression(callExpression) {
|
|
113
119
|
if (
|
|
114
120
|
method !== 'reduce'
|
|
115
121
|
&& method !== 'reduceRight'
|
|
116
|
-
&& isAwaitExpressionArgument(
|
|
122
|
+
&& isAwaitExpressionArgument(callExpression)
|
|
117
123
|
) {
|
|
118
|
-
return
|
|
124
|
+
return true;
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
if (isNodeMatches(
|
|
122
|
-
return
|
|
127
|
+
if (isNodeMatches(callExpression.callee.object, ignoredCallee)) {
|
|
128
|
+
return true;
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
if (
|
|
126
|
-
|
|
131
|
+
if (
|
|
132
|
+
callExpression.callee.object.type === 'CallExpression'
|
|
133
|
+
&& isNodeMatches(callExpression.callee.object.callee, ignoredCallee)
|
|
134
|
+
) {
|
|
135
|
+
return true;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
return shouldIgnoreCallExpression?.(callExpression) ?? false;
|
|
139
|
+
},
|
|
140
|
+
shouldIgnoreCallback(callback) {
|
|
131
141
|
if (callback.type === 'Identifier' && ignore.includes(callback.name)) {
|
|
132
|
-
return
|
|
142
|
+
return true;
|
|
133
143
|
}
|
|
134
144
|
|
|
135
|
-
return
|
|
145
|
+
return false;
|
|
136
146
|
},
|
|
137
147
|
}]));
|
|
138
148
|
|
|
@@ -163,9 +173,14 @@ function getProblem(context, node, method, options) {
|
|
|
163
173
|
name,
|
|
164
174
|
method,
|
|
165
175
|
},
|
|
166
|
-
suggest: [],
|
|
167
176
|
};
|
|
168
177
|
|
|
178
|
+
if (node.type === 'YieldExpression' || node.type === 'AwaitExpression') {
|
|
179
|
+
return problem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
problem.suggest = [];
|
|
183
|
+
|
|
169
184
|
const {parameters, minParameters, returnsUndefined} = options;
|
|
170
185
|
for (let parameterLength = minParameters; parameterLength <= parameters.length; parameterLength++) {
|
|
171
186
|
const suggestionParameters = parameters.slice(0, parameterLength).join(', ');
|
|
@@ -178,16 +193,20 @@ function getProblem(context, node, method, options) {
|
|
|
178
193
|
},
|
|
179
194
|
fix(fixer) {
|
|
180
195
|
const {sourceCode} = context;
|
|
181
|
-
let
|
|
182
|
-
|
|
183
|
-
|
|
196
|
+
let text = getParenthesizedText(node, sourceCode);
|
|
197
|
+
|
|
198
|
+
if (
|
|
199
|
+
!isParenthesized(node, sourceCode)
|
|
200
|
+
&& shouldAddParenthesesToCallExpressionCallee(node)
|
|
201
|
+
) {
|
|
202
|
+
text = `(${text})`;
|
|
184
203
|
}
|
|
185
204
|
|
|
186
|
-
return fixer.
|
|
187
|
-
node,
|
|
205
|
+
return fixer.replaceTextRange(
|
|
206
|
+
getParenthesizedRange(node, sourceCode),
|
|
188
207
|
returnsUndefined
|
|
189
|
-
? `(${suggestionParameters}) => { ${
|
|
190
|
-
: `(${suggestionParameters}) => ${
|
|
208
|
+
? `(${suggestionParameters}) => { ${text}(${suggestionParameters}); }`
|
|
209
|
+
: `(${suggestionParameters}) => ${text}(${suggestionParameters})`,
|
|
191
210
|
);
|
|
192
211
|
},
|
|
193
212
|
};
|
|
@@ -198,47 +217,57 @@ function getProblem(context, node, method, options) {
|
|
|
198
217
|
return problem;
|
|
199
218
|
}
|
|
200
219
|
|
|
220
|
+
function * getTernaryConsequentAndALternate(node) {
|
|
221
|
+
if (node.type === 'ConditionalExpression') {
|
|
222
|
+
yield * getTernaryConsequentAndALternate(node.consequent);
|
|
223
|
+
yield * getTernaryConsequentAndALternate(node.alternate);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
yield node;
|
|
228
|
+
}
|
|
229
|
+
|
|
201
230
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
202
231
|
const create = context => ({
|
|
203
|
-
CallExpression(
|
|
232
|
+
* CallExpression(callExpression) {
|
|
204
233
|
if (
|
|
205
|
-
!isMethodCall(
|
|
234
|
+
!isMethodCall(callExpression, {
|
|
206
235
|
minimumArguments: 1,
|
|
207
236
|
maximumArguments: 2,
|
|
208
237
|
optionalCall: false,
|
|
209
238
|
optionalMember: false,
|
|
210
239
|
computed: false,
|
|
211
240
|
})
|
|
212
|
-
||
|
|
241
|
+
|| callExpression.callee.property.type !== 'Identifier'
|
|
213
242
|
) {
|
|
214
243
|
return;
|
|
215
244
|
}
|
|
216
245
|
|
|
217
|
-
const methodNode =
|
|
246
|
+
const methodNode = callExpression.callee.property;
|
|
218
247
|
const methodName = methodNode.name;
|
|
219
248
|
if (!iteratorMethods.has(methodName)) {
|
|
220
249
|
return;
|
|
221
250
|
}
|
|
222
251
|
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
callback.type === 'FunctionExpression'
|
|
227
|
-
|| callback.type === 'ArrowFunctionExpression'
|
|
228
|
-
// Ignore all `CallExpression`s include `function.bind()`
|
|
229
|
-
|| callback.type === 'CallExpression'
|
|
230
|
-
|| isNodeValueNotFunction(callback)
|
|
231
|
-
) {
|
|
252
|
+
const options = iteratorMethods.get(methodName);
|
|
253
|
+
if (options.shouldIgnoreCallExpression(callExpression)) {
|
|
232
254
|
return;
|
|
233
255
|
}
|
|
234
256
|
|
|
235
|
-
const
|
|
257
|
+
for (const callback of getTernaryConsequentAndALternate(callExpression.arguments[0])) {
|
|
258
|
+
if (
|
|
259
|
+
callback.type === 'FunctionExpression'
|
|
260
|
+
|| callback.type === 'ArrowFunctionExpression'
|
|
261
|
+
// Ignore all `CallExpression`s include `function.bind()`
|
|
262
|
+
|| callback.type === 'CallExpression'
|
|
263
|
+
|| options.shouldIgnoreCallback(callback)
|
|
264
|
+
|| isNodeValueNotFunction(callback)
|
|
265
|
+
) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
236
268
|
|
|
237
|
-
|
|
238
|
-
return;
|
|
269
|
+
yield getProblem(context, callback, methodName, options);
|
|
239
270
|
}
|
|
240
|
-
|
|
241
|
-
return getProblem(context, callback, methodName, options);
|
|
242
271
|
},
|
|
243
272
|
});
|
|
244
273
|
|