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.
@@ -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": "51.0.1",
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.6.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/developer-guide/working-with-rules#providing-suggestions).
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. | ✅ | | |
@@ -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, extension}) {
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('')}${extension.toLowerCase()}`))];
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 extension = path.extname(filenameWithExtension);
156
- const filename = path.basename(filenameWithExtension, extension);
157
- const base = filename + extension;
179
+ const {
180
+ basename,
181
+ filename,
182
+ middle,
183
+ extension,
184
+ } = getFilenameParts(filenameWithExtension, {multipleFileExtensions});
158
185
 
159
- if (ignoredByDefault.has(base) || ignore.some(regexp => regexp.test(base))) {
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
  },
@@ -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 {isNodeMatches, isNodeValueNotFunction} = require('./utils/index.js');
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
- test: node => !(node.callee.object.type === 'Identifier' && node.callee.object.name === 'Vue'),
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
- test: node => !(node.callee.object.type === 'Identifier' && node.callee.object.name === 'types'),
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
- test,
113
+ shouldIgnoreCallExpression,
108
114
  }) => [method, {
109
115
  minParameters,
110
116
  parameters,
111
117
  returnsUndefined,
112
- test(node) {
118
+ shouldIgnoreCallExpression(callExpression) {
113
119
  if (
114
120
  method !== 'reduce'
115
121
  && method !== 'reduceRight'
116
- && isAwaitExpressionArgument(node)
122
+ && isAwaitExpressionArgument(callExpression)
117
123
  ) {
118
- return false;
124
+ return true;
119
125
  }
120
126
 
121
- if (isNodeMatches(node.callee.object, ignoredCallee)) {
122
- return false;
127
+ if (isNodeMatches(callExpression.callee.object, ignoredCallee)) {
128
+ return true;
123
129
  }
124
130
 
125
- if (node.callee.object.type === 'CallExpression' && isNodeMatches(node.callee.object.callee, ignoredCallee)) {
126
- return false;
131
+ if (
132
+ callExpression.callee.object.type === 'CallExpression'
133
+ && isNodeMatches(callExpression.callee.object.callee, ignoredCallee)
134
+ ) {
135
+ return true;
127
136
  }
128
137
 
129
- const [callback] = node.arguments;
130
-
138
+ return shouldIgnoreCallExpression?.(callExpression) ?? false;
139
+ },
140
+ shouldIgnoreCallback(callback) {
131
141
  if (callback.type === 'Identifier' && ignore.includes(callback.name)) {
132
- return false;
142
+ return true;
133
143
  }
134
144
 
135
- return !test || test(node);
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 nodeText = sourceCode.getText(node);
182
- if (isParenthesized(node, sourceCode) || type === 'ConditionalExpression') {
183
- nodeText = `(${nodeText})`;
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.replaceText(
187
- node,
205
+ return fixer.replaceTextRange(
206
+ getParenthesizedRange(node, sourceCode),
188
207
  returnsUndefined
189
- ? `(${suggestionParameters}) => { ${nodeText}(${suggestionParameters}); }`
190
- : `(${suggestionParameters}) => ${nodeText}(${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(node) {
232
+ * CallExpression(callExpression) {
204
233
  if (
205
- !isMethodCall(node, {
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
- || node.callee.property.type !== 'Identifier'
241
+ || callExpression.callee.property.type !== 'Identifier'
213
242
  ) {
214
243
  return;
215
244
  }
216
245
 
217
- const methodNode = node.callee.property;
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 [callback] = node.arguments;
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 options = iteratorMethods.get(methodName);
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
- if (!options.test(node)) {
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