eslint-plugin-unicorn 41.0.1 β†’ 42.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.
@@ -52,12 +52,14 @@ module.exports = {
52
52
  'unicorn/no-thenable': 'error',
53
53
  'unicorn/no-this-assignment': 'error',
54
54
  'unicorn/no-unreadable-array-destructuring': 'error',
55
+ 'unicorn/no-unreadable-iife': 'error',
55
56
  'unicorn/no-unsafe-regex': 'off',
56
57
  'unicorn/no-unused-properties': 'off',
57
58
  'unicorn/no-useless-fallback-in-spread': 'error',
58
59
  'unicorn/no-useless-length-check': 'error',
59
60
  'unicorn/no-useless-promise-resolve-reject': 'error',
60
61
  'unicorn/no-useless-spread': 'error',
62
+ 'unicorn/no-useless-switch-case': 'error',
61
63
  'unicorn/no-useless-undefined': 'error',
62
64
  'unicorn/no-zero-fractions': 'error',
63
65
  'unicorn/number-literal-case': 'error',
@@ -79,11 +81,13 @@ module.exports = {
79
81
  'unicorn/prefer-dom-node-text-content': 'error',
80
82
  'unicorn/prefer-export-from': 'error',
81
83
  'unicorn/prefer-includes': 'error',
82
- 'unicorn/prefer-json-parse-buffer': 'error',
84
+ 'unicorn/prefer-json-parse-buffer': 'off',
83
85
  'unicorn/prefer-keyboard-event-key': 'error',
84
86
  'unicorn/prefer-math-trunc': 'error',
85
87
  'unicorn/prefer-modern-dom-apis': 'error',
88
+ 'unicorn/prefer-modern-math-apis': 'error',
86
89
  'unicorn/prefer-module': 'error',
90
+ 'unicorn/prefer-native-coercion-functions': 'error',
87
91
  'unicorn/prefer-negative-index': 'error',
88
92
  'unicorn/prefer-node-protocol': 'error',
89
93
  'unicorn/prefer-number-properties': 'error',
@@ -113,7 +117,7 @@ module.exports = {
113
117
  // See #1396
114
118
  'unicorn/require-post-message-target-origin': 'off',
115
119
  'unicorn/string-content': 'off',
116
- 'unicorn/template-indent': 'warn',
120
+ 'unicorn/template-indent': 'error',
117
121
  'unicorn/text-encoding-identifier-case': 'error',
118
122
  'unicorn/throw-new-error': 'error',
119
123
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "41.0.1",
3
+ "version": "42.0.0",
4
4
  "description": "Various awesome ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/eslint-plugin-unicorn",
@@ -161,12 +161,9 @@
161
161
  "files": [
162
162
  "rules/**/*.js"
163
163
  ],
164
- "plugins": [
165
- "internal-rules"
166
- ],
167
- "rules": {
168
- "internal-rules/prefer-negative-boolean-attribute": "error"
169
- }
164
+ "extends": [
165
+ "plugin:internal-rules/all"
166
+ ]
170
167
  }
171
168
  ]
172
169
  },
package/readme.md CHANGED
@@ -69,11 +69,11 @@ Each rule has emojis denoting:
69
69
  | [new-for-builtins](docs/rules/new-for-builtins.md) | Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. | βœ… | πŸ”§ | |
70
70
  | [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) | Enforce specifying rules to disable in `eslint-disable` comments. | βœ… | | |
71
71
  | [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. | βœ… | | πŸ’‘ |
72
- | [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `for…of` over `Array#forEach(…)`. | βœ… | πŸ”§ | |
72
+ | [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `for…of` over `Array#forEach(…)`. | βœ… | πŸ”§ | πŸ’‘ |
73
73
  | [no-array-method-this-argument](docs/rules/no-array-method-this-argument.md) | Disallow using the `this` argument in array methods. | βœ… | πŸ”§ | πŸ’‘ |
74
74
  | [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. | βœ… | πŸ”§ | πŸ’‘ |
75
75
  | [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. | βœ… | | |
76
- | [no-await-expression-member](docs/rules/no-await-expression-member.md) | Forbid member access from await expression. | βœ… | πŸ”§ | |
76
+ | [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. | βœ… | πŸ”§ | |
77
77
  | [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. | βœ… | πŸ”§ | |
78
78
  | [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. | βœ… | | |
79
79
  | [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. | βœ… | | |
@@ -89,16 +89,18 @@ Each rule has emojis denoting:
89
89
  | [no-null](docs/rules/no-null.md) | Disallow the use of the `null` literal. | βœ… | πŸ”§ | πŸ’‘ |
90
90
  | [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) | Disallow the use of objects as default parameters. | βœ… | | |
91
91
  | [no-process-exit](docs/rules/no-process-exit.md) | Disallow `process.exit()`. | βœ… | | |
92
- | [no-static-only-class](docs/rules/no-static-only-class.md) | Forbid classes that only have static members. | βœ… | πŸ”§ | |
92
+ | [no-static-only-class](docs/rules/no-static-only-class.md) | Disallow classes that only have static members. | βœ… | πŸ”§ | |
93
93
  | [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | βœ… | | |
94
94
  | [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | βœ… | | |
95
95
  | [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | βœ… | πŸ”§ | |
96
+ | [no-unreadable-iife](docs/rules/no-unreadable-iife.md) | Disallow unreadable IIFEs. | βœ… | | |
96
97
  | [no-unsafe-regex](docs/rules/no-unsafe-regex.md) | Disallow unsafe regular expressions. | | | |
97
98
  | [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | |
98
- | [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) | Forbid useless fallback when spreading in object literals. | βœ… | πŸ”§ | |
99
+ | [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) | Disallow useless fallback when spreading in object literals. | βœ… | πŸ”§ | |
99
100
  | [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. | βœ… | πŸ”§ | |
100
101
  | [no-useless-promise-resolve-reject](docs/rules/no-useless-promise-resolve-reject.md) | Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks | βœ… | πŸ”§ | |
101
102
  | [no-useless-spread](docs/rules/no-useless-spread.md) | Disallow unnecessary spread. | βœ… | πŸ”§ | |
103
+ | [no-useless-switch-case](docs/rules/no-useless-switch-case.md) | Disallow useless case in switch statements. | βœ… | | πŸ’‘ |
102
104
  | [no-useless-undefined](docs/rules/no-useless-undefined.md) | Disallow useless `undefined`. | βœ… | πŸ”§ | |
103
105
  | [no-zero-fractions](docs/rules/no-zero-fractions.md) | Disallow number literals with zero fractions or dangling dots. | βœ… | πŸ”§ | |
104
106
  | [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | βœ… | πŸ”§ | |
@@ -119,11 +121,13 @@ Each rule has emojis denoting:
119
121
  | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | βœ… | | πŸ’‘ |
120
122
  | [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | βœ… | πŸ”§ | πŸ’‘ |
121
123
  | [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ |
122
- | [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | βœ… | πŸ”§ | |
124
+ | [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | πŸ”§ | |
123
125
  | [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | βœ… | πŸ”§ | |
124
126
  | [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | βœ… | πŸ”§ | πŸ’‘ |
125
127
  | [prefer-modern-dom-apis](docs/rules/prefer-modern-dom-apis.md) | Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`. | βœ… | πŸ”§ | |
128
+ | [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | βœ… | πŸ”§ | |
126
129
  | [prefer-module](docs/rules/prefer-module.md) | Prefer JavaScript modules (ESM) over CommonJS. | βœ… | πŸ”§ | πŸ’‘ |
130
+ | [prefer-native-coercion-functions](docs/rules/prefer-native-coercion-functions.md) | Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly. | βœ… | πŸ”§ | |
127
131
  | [prefer-negative-index](docs/rules/prefer-negative-index.md) | Prefer negative index over `.length - index` for `{String,Array,TypedArray}#slice()`, `Array#splice()` and `Array#at()`. | βœ… | πŸ”§ | |
128
132
  | [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | Prefer using the `node:` protocol when importing Node.js builtin modules. | βœ… | πŸ”§ | |
129
133
  | [prefer-number-properties](docs/rules/prefer-number-properties.md) | Prefer `Number` static properties over global ones. | βœ… | πŸ”§ | πŸ’‘ |
@@ -150,7 +154,7 @@ Each rule has emojis denoting:
150
154
  | [require-post-message-target-origin](docs/rules/require-post-message-target-origin.md) | Enforce using the `targetOrigin` argument with `window.postMessage()`. | | | πŸ’‘ |
151
155
  | [string-content](docs/rules/string-content.md) | Enforce better string content. | | πŸ”§ | πŸ’‘ |
152
156
  | [template-indent](docs/rules/template-indent.md) | Fix whitespace-insensitive template indentation. | βœ… | πŸ”§ | |
153
- | [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | βœ… | | πŸ’‘ |
157
+ | [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | βœ… | πŸ”§ | πŸ’‘ |
154
158
  | [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | βœ… | πŸ”§ | |
155
159
  <!-- /RULES_TABLE -->
156
160
 
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+ module.exports = {
3
+ isEmptyNode: require('./is-empty-node.js'),
4
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+ function isEmptyNode(node, additionalEmpty) {
3
+ const {type} = node;
4
+
5
+ if (type === 'BlockStatement') {
6
+ return node.body.every(currentNode => isEmptyNode(currentNode, additionalEmpty));
7
+ }
8
+
9
+ if (type === 'EmptyStatement') {
10
+ return true;
11
+ }
12
+
13
+ if (additionalEmpty && additionalEmpty(node)) {
14
+ return true;
15
+ }
16
+
17
+ return false;
18
+ }
19
+
20
+ module.exports = isEmptyNode;
@@ -6,20 +6,24 @@ const {
6
6
  isSemicolonToken,
7
7
  isClosingParenToken,
8
8
  findVariable,
9
+ hasSideEffect,
9
10
  } = require('eslint-utils');
10
11
  const {methodCallSelector, referenceIdentifierSelector} = require('./selectors/index.js');
11
12
  const {extendFixRange} = require('./fix/index.js');
12
13
  const needsSemicolon = require('./utils/needs-semicolon.js');
13
14
  const shouldAddParenthesesToExpressionStatementExpression = require('./utils/should-add-parentheses-to-expression-statement-expression.js');
15
+ const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
14
16
  const {getParentheses} = require('./utils/parentheses.js');
15
17
  const isFunctionSelfUsedInside = require('./utils/is-function-self-used-inside.js');
16
18
  const {isNodeMatches} = require('./utils/is-node-matches.js');
17
19
  const assertToken = require('./utils/assert-token.js');
18
- const {fixSpaceAroundKeyword} = require('./fix/index.js');
20
+ const {fixSpaceAroundKeyword, removeParentheses} = require('./fix/index.js');
19
21
 
20
- const MESSAGE_ID = 'no-array-for-each';
22
+ const MESSAGE_ID_ERROR = 'no-array-for-each/error';
23
+ const MESSAGE_ID_SUGGESTION = 'no-array-for-each/suggestion';
21
24
  const messages = {
22
- [MESSAGE_ID]: 'Use `for…of` instead of `Array#forEach(…)`.',
25
+ [MESSAGE_ID_ERROR]: 'Use `for…of` instead of `Array#forEach(…)`.',
26
+ [MESSAGE_ID_SUGGESTION]: 'Switch to `for…of`.',
23
27
  };
24
28
 
25
29
  const arrayForEachCallSelector = methodCallSelector({
@@ -36,6 +40,11 @@ const continueAbleNodeTypes = new Set([
36
40
  'ForInStatement',
37
41
  ]);
38
42
 
43
+ const stripChainExpression = node =>
44
+ (node.parent.type === 'ChainExpression' && node.parent.expression === node)
45
+ ? node.parent
46
+ : node;
47
+
39
48
  function isReturnStatementInContinueAbleNodes(returnStatement, callbackFunction) {
40
49
  for (let node = returnStatement; node && node !== callbackFunction; node = node.parent) {
41
50
  if (continueAbleNodeTypes.has(node.type)) {
@@ -73,6 +82,9 @@ function getFixFunction(callExpression, functionInfo, context) {
73
82
  const parameters = callback.params;
74
83
  const array = callExpression.callee.object;
75
84
  const {returnStatements} = functionInfo.get(callback);
85
+ const isOptionalArray = callExpression.callee.optional;
86
+ const expressionStatement = stripChainExpression(callExpression).parent;
87
+ const arrayText = sourceCode.getText(array);
76
88
 
77
89
  const getForOfLoopHeadText = () => {
78
90
  const [elementText, indexText] = parameters.map(parameter => sourceCode.getText(parameter));
@@ -84,12 +96,16 @@ function getFixFunction(callExpression, functionInfo, context) {
84
96
  text += useEntries ? `[${indexText}, ${elementText}]` : elementText;
85
97
  text += ' of ';
86
98
 
87
- let arrayText = sourceCode.getText(array);
88
- if (isParenthesized(array, sourceCode)) {
89
- arrayText = `(${arrayText})`;
90
- }
99
+ const shouldAddParenthesesToArray
100
+ = isParenthesized(array, sourceCode)
101
+ || (
102
+ // `1?.forEach()` -> `(1).entries()`
103
+ isOptionalArray
104
+ && useEntries
105
+ && shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
106
+ );
91
107
 
92
- text += arrayText;
108
+ text += shouldAddParenthesesToArray ? `(${arrayText})` : arrayText;
93
109
 
94
110
  if (useEntries) {
95
111
  text += '.entries()';
@@ -193,6 +209,9 @@ function getFixFunction(callExpression, functionInfo, context) {
193
209
  }
194
210
 
195
211
  return function * (fixer) {
212
+ // `(( foo.forEach(bar => bar) ))`
213
+ yield * removeParentheses(callExpression, fixer, sourceCode);
214
+
196
215
  // Replace these with `for (const … of …) `
197
216
  // foo.forEach(bar => bar)
198
217
  // ^^^^^^^^^^^^^^^^^^ (space after `=>` didn't included)
@@ -228,7 +247,7 @@ function getFixFunction(callExpression, functionInfo, context) {
228
247
  yield * replaceReturnStatement(returnStatement, fixer);
229
248
  }
230
249
 
231
- const expressionStatementLastToken = sourceCode.getLastToken(callExpression.parent);
250
+ const expressionStatementLastToken = sourceCode.getLastToken(expressionStatement);
232
251
  // Remove semicolon if it's not needed anymore
233
252
  // foo.forEach(bar => {});
234
253
  // ^
@@ -238,6 +257,10 @@ function getFixFunction(callExpression, functionInfo, context) {
238
257
 
239
258
  yield * fixSpaceAroundKeyword(fixer, callExpression.parent, sourceCode);
240
259
 
260
+ if (isOptionalArray) {
261
+ yield fixer.insertTextBefore(callExpression, `if (${arrayText}) `);
262
+ }
263
+
241
264
  // Prevent possible variable conflicts
242
265
  yield * extendFixRange(fixer, callExpression.parent.range);
243
266
  };
@@ -303,25 +326,13 @@ function isFunctionParameterVariableReassigned(callbackFunction, context) {
303
326
  }
304
327
 
305
328
  function isFixable(callExpression, {scope, functionInfo, allIdentifiers, context}) {
306
- const sourceCode = context.getSourceCode();
307
329
  // Check `CallExpression`
308
- if (
309
- callExpression.optional
310
- || isParenthesized(callExpression, sourceCode)
311
- || callExpression.arguments.length !== 1
312
- ) {
330
+ if (callExpression.optional || callExpression.arguments.length !== 1) {
313
331
  return false;
314
332
  }
315
333
 
316
- // Check `CallExpression.parent`
317
- if (callExpression.parent.type !== 'ExpressionStatement') {
318
- return false;
319
- }
320
-
321
- // Check `CallExpression.callee`
322
- // Because of `ChainExpression` wrapper, `foo?.forEach()` is already failed on previous check keep this just for safety
323
- /* c8 ignore next 3 */
324
- if (callExpression.callee.optional) {
334
+ // Check ancestors, we only fix `ExpressionStatement`
335
+ if (stripChainExpression(callExpression).parent.type !== 'ExpressionStatement') {
325
336
  return false;
326
337
  }
327
338
 
@@ -373,6 +384,7 @@ const create = context => {
373
384
  const callExpressions = [];
374
385
  const allIdentifiers = [];
375
386
  const functionInfo = new Map();
387
+ const sourceCode = context.getSourceCode();
376
388
 
377
389
  return {
378
390
  ':function'(node) {
@@ -405,13 +417,31 @@ const create = context => {
405
417
  },
406
418
  * 'Program:exit'() {
407
419
  for (const {node, scope} of callExpressions) {
420
+ // TODO: Rename this to iteratable
421
+ const array = node.callee;
422
+
408
423
  const problem = {
409
- node: node.callee.property,
410
- messageId: MESSAGE_ID,
424
+ node: array.property,
425
+ messageId: MESSAGE_ID_ERROR,
411
426
  };
412
427
 
413
- if (isFixable(node, {scope, allIdentifiers, functionInfo, context})) {
414
- problem.fix = getFixFunction(node, functionInfo, context);
428
+ if (!isFixable(node, {scope, allIdentifiers, functionInfo, context})) {
429
+ yield problem;
430
+ continue;
431
+ }
432
+
433
+ const shouldUseSuggestion = array.optional && hasSideEffect(array, sourceCode);
434
+ const fix = getFixFunction(node, functionInfo, context);
435
+
436
+ if (shouldUseSuggestion) {
437
+ problem.suggest = [
438
+ {
439
+ messageId: MESSAGE_ID_SUGGESTION,
440
+ fix,
441
+ },
442
+ ];
443
+ } else {
444
+ problem.fix = fix;
415
445
  }
416
446
 
417
447
  yield problem;
@@ -429,6 +459,7 @@ module.exports = {
429
459
  description: 'Prefer `for…of` over `Array#forEach(…)`.',
430
460
  },
431
461
  fixable: 'code',
462
+ hasSuggestions: true,
432
463
  messages,
433
464
  },
434
465
  };
@@ -76,7 +76,7 @@ module.exports = {
76
76
  meta: {
77
77
  type: 'suggestion',
78
78
  docs: {
79
- description: 'Forbid member access from await expression.',
79
+ description: 'Disallow member access from await expression.',
80
80
  },
81
81
  fixable: 'code',
82
82
  messages,
@@ -1,17 +1,13 @@
1
1
  'use strict';
2
+ const {isEmptyNode} = require('./ast/index.js');
2
3
 
3
4
  const MESSAGE_ID = 'no-empty-file';
4
5
  const messages = {
5
6
  [MESSAGE_ID]: 'Empty files are not allowed.',
6
7
  };
7
8
 
8
- const isEmpty = node =>
9
- (
10
- (node.type === 'Program' || node.type === 'BlockStatement')
11
- && node.body.every(currentNode => isEmpty(currentNode))
12
- )
13
- || node.type === 'EmptyStatement'
14
- || (node.type === 'ExpressionStatement' && 'directive' in node);
9
+ const isDirective = node => node.type === 'ExpressionStatement' && 'directive' in node;
10
+ const isEmpty = node => isEmptyNode(node, isDirective);
15
11
 
16
12
  const isTripleSlashDirective = node =>
17
13
  node.type === 'Line' && node.value.startsWith('/');
@@ -29,7 +25,7 @@ const create = context => {
29
25
 
30
26
  return {
31
27
  Program(node) {
32
- if (!isEmpty(node)) {
28
+ if (node.body.some(node => !isEmpty(node))) {
33
29
  return;
34
30
  }
35
31
 
@@ -221,7 +221,7 @@ module.exports = {
221
221
  meta: {
222
222
  type: 'suggestion',
223
223
  docs: {
224
- description: 'Forbid classes that only have static members.',
224
+ description: 'Disallow classes that only have static members.',
225
225
  },
226
226
  fixable: 'code',
227
227
  messages,
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+ const {
3
+ isParenthesized,
4
+ getParenthesizedRange,
5
+ } = require('./utils/parentheses.js');
6
+ const toLocation = require('./utils/to-location.js');
7
+
8
+ const MESSAGE_ID_ERROR = 'no-unreadable-iife';
9
+ const messages = {
10
+ [MESSAGE_ID_ERROR]: 'IIFE with parenthesized arrow function body is considered unreadable.',
11
+ };
12
+
13
+ const selector = [
14
+ 'CallExpression',
15
+ ' > ',
16
+ 'ArrowFunctionExpression.callee',
17
+ ' > ',
18
+ ':not(BlockStatement).body',
19
+ ].join('');
20
+
21
+ /** @param {import('eslint').Rule.RuleContext} context */
22
+ const create = context => ({
23
+ [selector](node) {
24
+ const sourceCode = context.getSourceCode();
25
+ if (!isParenthesized(node, sourceCode)) {
26
+ return;
27
+ }
28
+
29
+ return {
30
+ node,
31
+ loc: toLocation(getParenthesizedRange(node, sourceCode), sourceCode),
32
+ messageId: MESSAGE_ID_ERROR,
33
+ };
34
+ },
35
+ });
36
+
37
+ /** @type {import('eslint').Rule.RuleModule} */
38
+ module.exports = {
39
+ create,
40
+ meta: {
41
+ type: 'suggestion',
42
+ docs: {
43
+ description: 'Disallow unreadable IIFEs.',
44
+ },
45
+ hasSuggestions: false,
46
+ messages,
47
+ },
48
+ };
@@ -62,7 +62,7 @@ module.exports = {
62
62
  meta: {
63
63
  type: 'suggestion',
64
64
  docs: {
65
- description: 'Forbid useless fallback when spreading in object literals.',
65
+ description: 'Disallow useless fallback when spreading in object literals.',
66
66
  },
67
67
  fixable: 'code',
68
68
  messages,
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+ const {isEmptyNode} = require('./ast/index.js');
3
+ const getSwitchCaseHeadLocation = require('./utils/get-switch-case-head-location.js');
4
+
5
+ const MESSAGE_ID_ERROR = 'no-useless-switch-case/error';
6
+ const MESSAGE_ID_SUGGESTION = 'no-useless-switch-case/suggestion';
7
+ const messages = {
8
+ [MESSAGE_ID_ERROR]: 'Useless case in switch statement.',
9
+ [MESSAGE_ID_SUGGESTION]: 'Remove this case.',
10
+ };
11
+
12
+ const isEmptySwitchCase = node => node.consequent.every(node => isEmptyNode(node));
13
+
14
+ /** @param {import('eslint').Rule.RuleContext} context */
15
+ const create = context => ({
16
+ * 'SwitchStatement[cases.length>1]'(switchStatement) {
17
+ const {cases} = switchStatement;
18
+
19
+ // TypeScript allows multiple `default` cases
20
+ const defaultCases = cases.filter(switchCase => switchCase.test === null);
21
+ if (defaultCases.length !== 1) {
22
+ return;
23
+ }
24
+
25
+ const [defaultCase] = defaultCases;
26
+
27
+ // We only check cases where the last case is the `default` case
28
+ if (defaultCase !== cases[cases.length - 1]) {
29
+ return;
30
+ }
31
+
32
+ const uselessCases = [];
33
+
34
+ for (let index = cases.length - 2; index >= 0; index--) {
35
+ const node = cases[index];
36
+ if (isEmptySwitchCase(node)) {
37
+ uselessCases.unshift(node);
38
+ } else {
39
+ break;
40
+ }
41
+ }
42
+
43
+ for (const node of uselessCases) {
44
+ yield {
45
+ node,
46
+ loc: getSwitchCaseHeadLocation(node, context.getSourceCode()),
47
+ messageId: MESSAGE_ID_ERROR,
48
+ suggest: [
49
+ {
50
+ messageId: MESSAGE_ID_SUGGESTION,
51
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
52
+ fix: fixer => fixer.remove(node),
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ },
58
+ });
59
+
60
+ /** @type {import('eslint').Rule.RuleModule} */
61
+ module.exports = {
62
+ create,
63
+ meta: {
64
+ type: 'suggestion',
65
+ docs: {
66
+ description: 'Disallow useless case in switch statements.',
67
+ },
68
+ hasSuggestions: true,
69
+ messages,
70
+ },
71
+ };
@@ -91,6 +91,13 @@ const getFunction = scope => {
91
91
  }
92
92
  };
93
93
 
94
+ const isFunctionBindCall = node =>
95
+ !node.optional
96
+ && node.callee.type === 'MemberExpression'
97
+ && !node.callee.computed
98
+ && node.callee.property.type === 'Identifier'
99
+ && node.callee.property.name === 'bind';
100
+
94
101
  /** @param {import('eslint').Rule.RuleContext} context */
95
102
  const create = context => {
96
103
  const listener = (fix, checkFunctionReturnType) => node => {
@@ -142,6 +149,12 @@ const create = context => {
142
149
  }
143
150
 
144
151
  const argumentNodes = node.arguments;
152
+
153
+ // Ignore arguments in `Function#bind()`, but not `this` argument
154
+ if (isFunctionBindCall(node) && argumentNodes.length !== 1) {
155
+ return;
156
+ }
157
+
145
158
  const undefinedArguments = [];
146
159
  for (let index = argumentNodes.length - 1; index >= 0; index--) {
147
160
  const node = argumentNodes[index];
@@ -23,7 +23,7 @@ const replaceChildOrInsertBeforeSelector = [
23
23
  '[callee.object.type="Identifier"]',
24
24
  ].join('');
25
25
 
26
- const forbiddenMethods = new Map([
26
+ const disallowedMethods = new Map([
27
27
  ['replaceChild', 'replaceWith'],
28
28
  ['insertBefore', 'before'],
29
29
  ]);
@@ -32,7 +32,7 @@ const checkForReplaceChildOrInsertBefore = (context, node) => {
32
32
  const method = node.callee.property.name;
33
33
  const parentNode = node.callee.object.name;
34
34
  const [newChildNode, oldChildNode] = node.arguments.map(({name}) => name);
35
- const preferredMethod = forbiddenMethods.get(method);
35
+ const preferredMethod = disallowedMethods.get(method);
36
36
 
37
37
  const fix = isValueNotUsable(node)
38
38
  ? fixer => fixer.replaceText(
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+ const {getParenthesizedText} = require('./utils/parentheses.js');
3
+
4
+ const MESSAGE_ID = 'prefer-modern-math-apis';
5
+ const messages = {
6
+ [MESSAGE_ID]: 'Prefer `{{replacement}}` over `{{description}}`.',
7
+ };
8
+
9
+ const isMathProperty = (node, property) =>
10
+ node.type === 'MemberExpression'
11
+ && !node.optional
12
+ && !node.computed
13
+ && node.object.type === 'Identifier'
14
+ && node.object.name === 'Math'
15
+ && node.property.type === 'Identifier'
16
+ && node.property.name === property;
17
+
18
+ const isMathMethodCall = (node, method) =>
19
+ node.type === 'CallExpression'
20
+ && !node.optional
21
+ && isMathProperty(node.callee, method)
22
+ && node.arguments.length === 1
23
+ && node.arguments[0].type !== 'SpreadElement';
24
+
25
+ // `Math.log(x) * Math.LOG10E` -> `Math.log10(x)`
26
+ // `Math.LOG10E * Math.log(x)` -> `Math.log10(x)`
27
+ // `Math.log(x) * Math.LOG2E` -> `Math.log2(x)`
28
+ // `Math.LOG2E * Math.log(x)` -> `Math.log2(x)`
29
+ function createLogCallTimesConstantCheck({constantName, replacementMethod}) {
30
+ const replacement = `Math.${replacementMethod}(…)`;
31
+
32
+ return function (node, context) {
33
+ if (!(node.type === 'BinaryExpression' && node.operator === '*')) {
34
+ return;
35
+ }
36
+
37
+ let mathLogCall;
38
+ let description;
39
+ if (isMathMethodCall(node.left, 'log') && isMathProperty(node.right, constantName)) {
40
+ mathLogCall = node.left;
41
+ description = `Math.log(…) * Math.${constantName}`;
42
+ } else if (isMathMethodCall(node.right, 'log') && isMathProperty(node.left, constantName)) {
43
+ mathLogCall = node.right;
44
+ description = `Math.${constantName} * Math.log(…)`;
45
+ }
46
+
47
+ if (!mathLogCall) {
48
+ return;
49
+ }
50
+
51
+ const [valueNode] = mathLogCall.arguments;
52
+
53
+ return {
54
+ node,
55
+ messageId: MESSAGE_ID,
56
+ data: {
57
+ replacement,
58
+ description,
59
+ },
60
+ fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
61
+ };
62
+ };
63
+ }
64
+
65
+ // `Math.log(x) / Math.LN10` -> `Math.log10(x)`
66
+ // `Math.log(x) / Math.LN2` -> `Math.log2(x)`
67
+ function createLogCallDivideConstantCheck({constantName, replacementMethod}) {
68
+ const message = {
69
+ messageId: MESSAGE_ID,
70
+ data: {
71
+ replacement: `Math.${replacementMethod}(…)`,
72
+ description: `Math.log(…) / Math.${constantName}`,
73
+ },
74
+ };
75
+
76
+ return function (node, context) {
77
+ if (
78
+ !(
79
+ node.type === 'BinaryExpression'
80
+ && node.operator === '/'
81
+ && isMathMethodCall(node.left, 'log')
82
+ && isMathProperty(node.right, constantName)
83
+ )
84
+ ) {
85
+ return;
86
+ }
87
+
88
+ const [valueNode] = node.left.arguments;
89
+
90
+ return {
91
+ ...message,
92
+ node,
93
+ fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
94
+ };
95
+ };
96
+ }
97
+
98
+ const checkFunctions = [
99
+ createLogCallTimesConstantCheck({constantName: 'LOG10E', replacementMethod: 'log10'}),
100
+ createLogCallTimesConstantCheck({constantName: 'LOG2E', replacementMethod: 'log2'}),
101
+ createLogCallDivideConstantCheck({constantName: 'LN10', replacementMethod: 'log10'}),
102
+ createLogCallDivideConstantCheck({constantName: 'LN2', replacementMethod: 'log2'}),
103
+ ];
104
+
105
+ /** @param {import('eslint').Rule.RuleContext} context */
106
+ const create = context => {
107
+ const nodes = [];
108
+
109
+ return {
110
+ BinaryExpression(node) {
111
+ nodes.push(node);
112
+ },
113
+ * 'Program:exit'() {
114
+ for (const node of nodes) {
115
+ for (const getProblem of checkFunctions) {
116
+ const problem = getProblem(node, context);
117
+
118
+ if (problem) {
119
+ yield problem;
120
+ }
121
+ }
122
+ }
123
+ },
124
+ };
125
+ };
126
+
127
+ /** @type {import('eslint').Rule.RuleModule} */
128
+ module.exports = {
129
+ create,
130
+ meta: {
131
+ type: 'suggestion',
132
+ docs: {
133
+ description: 'Prefer modern `Math` APIs over legacy patterns.',
134
+ },
135
+ fixable: 'code',
136
+ messages,
137
+ },
138
+ };
@@ -0,0 +1,182 @@
1
+ 'use strict';
2
+ const {getFunctionHeadLocation, getFunctionNameWithKind} = require('eslint-utils');
3
+ const {not} = require('./selectors/index.js');
4
+
5
+ const MESSAGE_ID = 'prefer-native-coercion-functions';
6
+ const messages = {
7
+ [MESSAGE_ID]: '{{functionNameWithKind}} is equivalent to `{{replacementFunction}}`. Use `{{replacementFunction}}` directly.',
8
+ };
9
+
10
+ const nativeCoercionFunctionNames = new Set(['String', 'Number', 'BigInt', 'Boolean', 'Symbol']);
11
+ const arrayMethodsWithBooleanCallback = new Set(['every', 'filter', 'find', 'findIndex', 'some']);
12
+
13
+ const isNativeCoercionFunctionCall = (node, firstArgumentName) =>
14
+ node
15
+ && node.type === 'CallExpression'
16
+ && !node.optional
17
+ && node.callee.type === 'Identifier'
18
+ && nativeCoercionFunctionNames.has(node.callee.name)
19
+ && node.arguments[0]
20
+ && node.arguments[0].type === 'Identifier'
21
+ && node.arguments[0].name === firstArgumentName;
22
+
23
+ const isIdentityFunction = node =>
24
+ (
25
+ // `v => v`
26
+ node.type === 'ArrowFunctionExpression'
27
+ && node.body.type === 'Identifier'
28
+ && node.body.name === node.params[0].name
29
+ )
30
+ || (
31
+ // `(v) => {return v;}`
32
+ // `function (v) {return v;}`
33
+ node.body.type === 'BlockStatement'
34
+ && node.body.body.length === 1
35
+ && node.body.body[0].type === 'ReturnStatement'
36
+ && node.body.body[0].argument
37
+ && node.body.body[0].argument.type === 'Identifier'
38
+ && node.body.body[0].argument.name === node.params[0].name
39
+ );
40
+
41
+ const isArrayIdentityCallback = node =>
42
+ isIdentityFunction(node)
43
+ && node.parent.type === 'CallExpression'
44
+ && !node.parent.optional
45
+ && node.parent.arguments[0] === node
46
+ && node.parent.callee.type === 'MemberExpression'
47
+ && !node.parent.callee.computed
48
+ && !node.parent.callee.optional
49
+ && node.parent.callee.property.type === 'Identifier'
50
+ && arrayMethodsWithBooleanCallback.has(node.parent.callee.property.name);
51
+
52
+ function getCallExpression(node) {
53
+ const firstParameterName = node.params[0].name;
54
+
55
+ // `(v) => String(v)`
56
+ if (
57
+ node.type === 'ArrowFunctionExpression'
58
+ && isNativeCoercionFunctionCall(node.body, firstParameterName)
59
+ ) {
60
+ return node.body;
61
+ }
62
+
63
+ // `(v) => {return String(v);}`
64
+ // `function (v) {return String(v);}`
65
+ if (
66
+ node.body.type === 'BlockStatement'
67
+ && node.body.body.length === 1
68
+ && node.body.body[0].type === 'ReturnStatement'
69
+ && isNativeCoercionFunctionCall(node.body.body[0].argument, firstParameterName)
70
+ ) {
71
+ return node.body.body[0].argument;
72
+ }
73
+ }
74
+
75
+ const functionsSelector = [
76
+ ':function',
77
+ '[async!=true]',
78
+ '[generator!=true]',
79
+ '[params.length>0]',
80
+ '[params.0.type="Identifier"]',
81
+ not([
82
+ 'MethodDefinition[kind="constructor"] > .value',
83
+ 'MethodDefinition[kind="set"] > .value',
84
+ 'Property[kind="set"] > .value',
85
+ ]),
86
+ ].join('');
87
+
88
+ function getArrayCallbackProblem(node) {
89
+ if (!isArrayIdentityCallback(node)) {
90
+ return;
91
+ }
92
+
93
+ return {
94
+ replacementFunction: 'Boolean',
95
+ fix: fixer => fixer.replaceText(node, 'Boolean'),
96
+ };
97
+ }
98
+
99
+ function getCoercionFunctionProblem(node) {
100
+ const callExpression = getCallExpression(node);
101
+
102
+ if (!callExpression) {
103
+ return;
104
+ }
105
+
106
+ const {name} = callExpression.callee;
107
+
108
+ const problem = {replacementFunction: name};
109
+
110
+ if (node.type === 'FunctionDeclaration' || callExpression.arguments.length !== 1) {
111
+ return problem;
112
+ }
113
+
114
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
115
+ problem.fix = fixer => {
116
+ let text = name;
117
+
118
+ if (
119
+ node.parent.type === 'Property'
120
+ && node.parent.method
121
+ && node.parent.value === node
122
+ ) {
123
+ text = `: ${text}`;
124
+ } else if (node.parent.type === 'MethodDefinition') {
125
+ text = ` = ${text};`;
126
+ }
127
+
128
+ return fixer.replaceText(node, text);
129
+ };
130
+
131
+ return problem;
132
+ }
133
+
134
+ /** @param {import('eslint').Rule.RuleContext} context */
135
+ const create = context => ({
136
+ [functionsSelector](node) {
137
+ let problem = getArrayCallbackProblem(node) || getCoercionFunctionProblem(node);
138
+
139
+ if (!problem) {
140
+ return;
141
+ }
142
+
143
+ const sourceCode = context.getSourceCode();
144
+ const {replacementFunction, fix} = problem;
145
+
146
+ problem = {
147
+ node,
148
+ loc: getFunctionHeadLocation(node, sourceCode),
149
+ messageId: MESSAGE_ID,
150
+ data: {
151
+ functionNameWithKind: getFunctionNameWithKind(node, sourceCode),
152
+ replacementFunction,
153
+ },
154
+ };
155
+
156
+ /*
157
+ We do not fix if there are:
158
+ - Comments: No proper place to put them.
159
+ - Extra parameters: Removing them may break types.
160
+ */
161
+ if (!fix || node.params.length !== 1 || sourceCode.getCommentsInside(node).length > 0) {
162
+ return problem;
163
+ }
164
+
165
+ problem.fix = fix;
166
+
167
+ return problem;
168
+ },
169
+ });
170
+
171
+ /** @type {import('eslint').Rule.RuleModule} */
172
+ module.exports = {
173
+ create,
174
+ meta: {
175
+ type: 'suggestion',
176
+ docs: {
177
+ description: 'Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly.',
178
+ },
179
+ fixable: 'code',
180
+ messages,
181
+ },
182
+ };
@@ -183,10 +183,6 @@ function create(context) {
183
183
 
184
184
  for (const {selector, test, getProperty} of fixableArrayReduceCases) {
185
185
  listeners[selector] = node => {
186
- // If this listener exits without adding a fix, the `arrayReduceWithEmptyObject` listener
187
- // should still add it into the `arrayReduce` map. To be safer, add it here too.
188
- arrayReduce.set(node, undefined);
189
-
190
186
  const [callbackFunction] = node.arguments;
191
187
  if (!test(callbackFunction)) {
192
188
  return;
@@ -211,12 +207,6 @@ function create(context) {
211
207
  };
212
208
  }
213
209
 
214
- listeners[arrayReduceWithEmptyObject] = node => {
215
- if (!arrayReduce.has(node)) {
216
- arrayReduce.set(node, undefined);
217
- }
218
- };
219
-
220
210
  listeners['Program:exit'] = () => {
221
211
  for (const [node, fix] of arrayReduce.entries()) {
222
212
  context.report({
@@ -14,7 +14,7 @@ const selector = [
14
14
  notDomNodeSelector('callee.object'),
15
15
  ].join('');
16
16
 
17
- const forbiddenIdentifierNames = new Map([
17
+ const disallowedIdentifierNames = new Map([
18
18
  ['getElementById', 'querySelector'],
19
19
  ['getElementsByClassName', 'querySelectorAll'],
20
20
  ['getElementsByTagName', 'querySelectorAll'],
@@ -103,7 +103,7 @@ const fix = (node, identifierName, preferredSelector) => {
103
103
  const create = () => ({
104
104
  [selector](node) {
105
105
  const method = node.callee.property.name;
106
- const preferredSelector = forbiddenIdentifierNames.get(method);
106
+ const preferredSelector = disallowedIdentifierNames.get(method);
107
107
 
108
108
  const problem = {
109
109
  node: node.callee.property,
@@ -10,6 +10,7 @@ const selector = [
10
10
  methodCallSelector({
11
11
  methods: ['trimLeft', 'trimRight'],
12
12
  argumentsLength: 0,
13
+ includeOptionalMember: true,
13
14
  }),
14
15
  ' > .callee',
15
16
  ' > .property',
@@ -194,61 +194,65 @@ const create = context => {
194
194
  return;
195
195
  }
196
196
 
197
- const scope = context.getScope();
198
-
199
- return {
200
- node,
201
- messageId,
202
- * fix(fixer) {
203
- const testText = getText(node.test);
204
- const consequentText = typeof result.consequent === 'string'
205
- ? result.consequent
206
- : getText(result.consequent);
207
- const alternateText = typeof result.alternate === 'string'
208
- ? result.alternate
209
- : getText(result.alternate);
210
-
211
- let {type, before, after} = result;
212
-
213
- let generateNewVariables = false;
214
- if (type === 'ThrowStatement') {
215
- const scopes = getScopes(scope);
216
- const errorName = avoidCapture('error', scopes, isSafeName);
217
-
218
- for (const scope of scopes) {
219
- if (!scopeToNamesGeneratedByFixer.has(scope)) {
220
- scopeToNamesGeneratedByFixer.set(scope, new Set());
221
- }
222
-
223
- const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
224
- generatedNames.add(errorName);
225
- }
197
+ const problem = {node, messageId};
226
198
 
227
- const indentString = getIndentString(node, sourceCode);
199
+ // Don't fix if there are comments
200
+ if (sourceCode.getCommentsInside(node).length > 0) {
201
+ return problem;
202
+ }
228
203
 
229
- after = after
230
- .replace('{{INDENT_STRING}}', indentString)
231
- .replace('{{ERROR_NAME}}', errorName);
232
- before = before
233
- .replace('{{INDENT_STRING}}', indentString)
234
- .replace('{{ERROR_NAME}}', errorName);
235
- generateNewVariables = true;
236
- }
204
+ const scope = context.getScope();
205
+ problem.fix = function * (fixer) {
206
+ const testText = getText(node.test);
207
+ const consequentText = typeof result.consequent === 'string'
208
+ ? result.consequent
209
+ : getText(result.consequent);
210
+ const alternateText = typeof result.alternate === 'string'
211
+ ? result.alternate
212
+ : getText(result.alternate);
213
+
214
+ let {type, before, after} = result;
215
+
216
+ let generateNewVariables = false;
217
+ if (type === 'ThrowStatement') {
218
+ const scopes = getScopes(scope);
219
+ const errorName = avoidCapture('error', scopes, isSafeName);
220
+
221
+ for (const scope of scopes) {
222
+ if (!scopeToNamesGeneratedByFixer.has(scope)) {
223
+ scopeToNamesGeneratedByFixer.set(scope, new Set());
224
+ }
237
225
 
238
- let fixed = `${before}${testText} ? ${consequentText} : ${alternateText}${after}`;
239
- const tokenBefore = sourceCode.getTokenBefore(node);
240
- const shouldAddSemicolonBefore = needsSemicolon(tokenBefore, sourceCode, fixed);
241
- if (shouldAddSemicolonBefore) {
242
- fixed = `;${fixed}`;
226
+ const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
227
+ generatedNames.add(errorName);
243
228
  }
244
229
 
245
- yield fixer.replaceText(node, fixed);
246
-
247
- if (generateNewVariables) {
248
- yield * extendFixRange(fixer, sourceCode.ast.range);
249
- }
250
- },
230
+ const indentString = getIndentString(node, sourceCode);
231
+
232
+ after = after
233
+ .replace('{{INDENT_STRING}}', indentString)
234
+ .replace('{{ERROR_NAME}}', errorName);
235
+ before = before
236
+ .replace('{{INDENT_STRING}}', indentString)
237
+ .replace('{{ERROR_NAME}}', errorName);
238
+ generateNewVariables = true;
239
+ }
240
+
241
+ let fixed = `${before}${testText} ? ${consequentText} : ${alternateText}${after}`;
242
+ const tokenBefore = sourceCode.getTokenBefore(node);
243
+ const shouldAddSemicolonBefore = needsSemicolon(tokenBefore, sourceCode, fixed);
244
+ if (shouldAddSemicolonBefore) {
245
+ fixed = `;${fixed}`;
246
+ }
247
+
248
+ yield fixer.replaceText(node, fixed);
249
+
250
+ if (generateNewVariables) {
251
+ yield * extendFixRange(fixer, sourceCode.ast.range);
252
+ }
251
253
  };
254
+
255
+ return problem;
252
256
  },
253
257
  };
254
258
  };
@@ -59,6 +59,9 @@ module.exports.defaultReplacements = {
59
59
  dirs: {
60
60
  directories: true,
61
61
  },
62
+ dist: {
63
+ distribution: true,
64
+ },
62
65
  doc: {
63
66
  document: true,
64
67
  },
@@ -66,6 +69,11 @@ module.exports.defaultReplacements = {
66
69
  documentation: true,
67
70
  documents: true,
68
71
  },
72
+ dst: {
73
+ daylightSavingTime: true,
74
+ destination: true,
75
+ distribution: true,
76
+ },
69
77
  e: {
70
78
  error: true,
71
79
  event: true,
@@ -19,6 +19,19 @@ const getReplacement = encoding => {
19
19
  }
20
20
  };
21
21
 
22
+ // `fs.{readFile,readFileSync}()`
23
+ const isFsReadFileEncoding = node =>
24
+ node.parent.type === 'CallExpression'
25
+ && !node.parent.optional
26
+ && node.parent.arguments[1] === node
27
+ && node.parent.arguments[0]
28
+ && node.parent.arguments[0].type !== 'SpreadElement'
29
+ && node.parent.callee.type === 'MemberExpression'
30
+ && !node.parent.callee.optional
31
+ && !node.parent.callee.computed
32
+ && node.parent.callee.property.type === 'Identifier'
33
+ && (node.parent.callee.property.name === 'readFile' || node.parent.callee.property.name === 'readFileSync');
34
+
22
35
  /** @param {import('eslint').Rule.RuleContext} context */
23
36
  const create = () => ({
24
37
  Literal(node) {
@@ -39,19 +52,29 @@ const create = () => ({
39
52
  replacement,
40
53
  };
41
54
 
42
- return {
55
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
56
+ const fix = fixer => replaceStringLiteral(fixer, node, replacement);
57
+
58
+ const problem = {
43
59
  node,
44
60
  messageId: MESSAGE_ID_ERROR,
45
61
  data: messageData,
46
- suggest: [
47
- {
48
- messageId: MESSAGE_ID_SUGGESTION,
49
- data: messageData,
50
- /** @param {import('eslint').Rule.RuleFixer} fixer */
51
- fix: fixer => replaceStringLiteral(fixer, node, replacement),
52
- },
53
- ],
54
62
  };
63
+
64
+ if (isFsReadFileEncoding(node)) {
65
+ problem.fix = fix;
66
+ return problem;
67
+ }
68
+
69
+ problem.suggest = [
70
+ {
71
+ messageId: MESSAGE_ID_SUGGESTION,
72
+ data: messageData,
73
+ fix: fixer => replaceStringLiteral(fixer, node, replacement),
74
+ },
75
+ ];
76
+
77
+ return problem;
55
78
  },
56
79
  });
57
80
 
@@ -63,6 +86,7 @@ module.exports = {
63
86
  docs: {
64
87
  description: 'Enforce consistent case for text encoding identifiers.',
65
88
  },
89
+ fixable: 'code',
66
90
  hasSuggestions: true,
67
91
  messages,
68
92
  },
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const {isColonToken} = require('eslint-utils');
4
+
5
+ /**
6
+ @typedef {line: number, column: number} Position
7
+
8
+ Get the location of the given `SwitchCase` node for reporting.
9
+
10
+ @param {Node} node - The `SwitchCase` node to get.
11
+ @param {SourceCode} sourceCode - The source code object to get tokens from.
12
+ @returns {{start: Position, end: Position}} The location of the class node for reporting.
13
+ */
14
+ function getSwitchCaseHeadLocation(node, sourceCode) {
15
+ const startToken = node.test || sourceCode.getFirstToken(node);
16
+ const colonToken = sourceCode.getTokenAfter(startToken, isColonToken);
17
+
18
+ return {start: node.loc.start, end: colonToken.loc.end};
19
+ }
20
+
21
+ module.exports = getSwitchCaseHeadLocation;