eslint-plugin-unicorn 43.0.2 → 44.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.
Files changed (72) hide show
  1. package/configs/recommended.js +2 -0
  2. package/package.json +16 -15
  3. package/readme.md +6 -4
  4. package/rules/ast/is-empty-node.js +1 -1
  5. package/rules/ast/is-static-require.js +1 -3
  6. package/rules/better-regex.js +19 -2
  7. package/rules/consistent-function-scoping.js +5 -8
  8. package/rules/custom-error-definition.js +3 -3
  9. package/rules/explicit-length-check.js +0 -1
  10. package/rules/filename-case.js +2 -2
  11. package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
  12. package/rules/fix/index.js +1 -0
  13. package/rules/fix/remove-spaces-after.js +4 -4
  14. package/rules/fix/switch-new-expression-to-call-expression.js +14 -34
  15. package/rules/new-for-builtins.js +49 -48
  16. package/rules/no-array-callback-reference.js +16 -0
  17. package/rules/no-array-for-each.js +26 -10
  18. package/rules/no-array-method-this-argument.js +2 -0
  19. package/rules/no-await-expression-member.js +2 -0
  20. package/rules/no-document-cookie.js +6 -22
  21. package/rules/no-empty-file.js +1 -1
  22. package/rules/no-for-loop.js +4 -4
  23. package/rules/no-keyword-prefix.js +3 -7
  24. package/rules/no-new-buffer.js +4 -4
  25. package/rules/no-thenable.js +2 -5
  26. package/rules/no-unnecessary-await.js +107 -0
  27. package/rules/no-unsafe-regex.js +2 -2
  28. package/rules/no-unused-properties.js +2 -4
  29. package/rules/no-useless-promise-resolve-reject.js +3 -3
  30. package/rules/no-useless-spread.js +11 -4
  31. package/rules/no-useless-undefined.js +1 -2
  32. package/rules/prefer-array-find.js +83 -6
  33. package/rules/prefer-array-index-of.js +15 -4
  34. package/rules/prefer-array-some.js +15 -14
  35. package/rules/prefer-at.js +19 -0
  36. package/rules/prefer-dom-node-dataset.js +2 -1
  37. package/rules/prefer-export-from.js +15 -7
  38. package/rules/prefer-json-parse-buffer.js +2 -4
  39. package/rules/prefer-keyboard-event-key.js +10 -18
  40. package/rules/prefer-logical-operator-over-ternary.js +8 -4
  41. package/rules/prefer-math-trunc.js +0 -4
  42. package/rules/prefer-module.js +1 -1
  43. package/rules/prefer-native-coercion-functions.js +4 -7
  44. package/rules/prefer-negative-index.js +3 -2
  45. package/rules/prefer-number-properties.js +49 -58
  46. package/rules/prefer-prototype-methods.js +2 -5
  47. package/rules/prefer-set-has.js +1 -10
  48. package/rules/prefer-string-slice.js +1 -1
  49. package/rules/prefer-string-starts-ends-with.js +10 -5
  50. package/rules/prefer-switch.js +9 -4
  51. package/rules/prefer-ternary.js +1 -1
  52. package/rules/prefer-type-error.js +20 -7
  53. package/rules/prevent-abbreviations.js +7 -1
  54. package/rules/relative-url-style.js +2 -4
  55. package/rules/require-post-message-target-origin.js +10 -18
  56. package/rules/selectors/matches-any.js +8 -3
  57. package/rules/selectors/prototype-method-selector.js +5 -2
  58. package/rules/string-content.js +5 -7
  59. package/rules/switch-case-braces.js +109 -0
  60. package/rules/template-indent.js +4 -2
  61. package/rules/text-encoding-identifier-case.js +9 -10
  62. package/rules/utils/assert-token.js +1 -1
  63. package/rules/utils/avoid-capture.js +1 -1
  64. package/rules/utils/boolean.js +1 -1
  65. package/rules/utils/global-reference-tracker.js +72 -0
  66. package/rules/utils/is-number.js +5 -5
  67. package/rules/utils/is-on-same-line.js +7 -0
  68. package/rules/utils/is-same-reference.js +16 -8
  69. package/rules/utils/is-shadowed.js +1 -2
  70. package/rules/utils/rule.js +20 -10
  71. package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +8 -3
  72. package/rules/utils/should-add-parentheses-to-member-expression-object.js +8 -3
@@ -50,6 +50,7 @@ module.exports = {
50
50
  'unicorn/no-static-only-class': 'error',
51
51
  'unicorn/no-thenable': 'error',
52
52
  'unicorn/no-this-assignment': 'error',
53
+ 'unicorn/no-unnecessary-await': 'error',
53
54
  'unicorn/no-unreadable-array-destructuring': 'error',
54
55
  'unicorn/no-unreadable-iife': 'error',
55
56
  'unicorn/no-unsafe-regex': 'off',
@@ -118,6 +119,7 @@ module.exports = {
118
119
  // See #1396
119
120
  'unicorn/require-post-message-target-origin': 'off',
120
121
  'unicorn/string-content': 'off',
122
+ 'unicorn/switch-case-braces': 'error',
121
123
  'unicorn/template-indent': 'error',
122
124
  'unicorn/text-encoding-identifier-case': 'error',
123
125
  'unicorn/throw-new-error': 'error',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "43.0.2",
3
+ "version": "44.0.0",
4
4
  "description": "More than 100 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/eslint-plugin-unicorn",
@@ -46,13 +46,13 @@
46
46
  "xo"
47
47
  ],
48
48
  "dependencies": {
49
- "@babel/helper-validator-identifier": "^7.18.6",
50
- "ci-info": "^3.3.2",
49
+ "@babel/helper-validator-identifier": "^7.19.1",
50
+ "ci-info": "^3.4.0",
51
51
  "clean-regexp": "^1.0.0",
52
52
  "eslint-utils": "^3.0.0",
53
53
  "esquery": "^1.4.0",
54
54
  "indent-string": "^4.0.0",
55
- "is-builtin-module": "^3.1.0",
55
+ "is-builtin-module": "^3.2.0",
56
56
  "lodash": "^4.17.21",
57
57
  "pluralize": "^8.0.0",
58
58
  "read-pkg-up": "^7.0.1",
@@ -63,34 +63,35 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@babel/code-frame": "^7.18.6",
66
- "@babel/core": "^7.18.6",
67
- "@babel/eslint-parser": "^7.18.2",
66
+ "@babel/core": "^7.19.1",
67
+ "@babel/eslint-parser": "^7.19.1",
68
68
  "@lubien/fixture-beta-package": "^1.0.0-beta.1",
69
- "@typescript-eslint/parser": "^5.30.0",
69
+ "@typescript-eslint/parser": "^5.37.0",
70
70
  "ava": "^3.15.0",
71
- "c8": "^7.11.3",
71
+ "c8": "^7.12.0",
72
72
  "chalk": "^5.0.1",
73
73
  "enquirer": "^2.3.6",
74
- "eslint": "^8.18.0",
74
+ "eslint": "^8.23.1",
75
75
  "eslint-ava-rule-tester": "^4.0.0",
76
- "eslint-plugin-eslint-plugin": "^5.0.0",
76
+ "eslint-plugin-eslint-plugin": "^5.0.6",
77
77
  "eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
78
78
  "eslint-remote-tester": "^3.0.0",
79
79
  "eslint-remote-tester-repositories": "^0.0.6",
80
80
  "execa": "^6.1.0",
81
81
  "listr": "^0.14.3",
82
82
  "lodash-es": "^4.17.21",
83
- "markdownlint-cli": "^0.31.1",
83
+ "markdownlint-cli": "^0.32.2",
84
84
  "mem": "^9.0.2",
85
85
  "npm-package-json-lint": "^6.3.0",
86
86
  "npm-run-all": "^4.1.5",
87
87
  "outdent": "^0.8.0",
88
- "typescript": "^4.7.4",
89
- "vue-eslint-parser": "^9.0.3",
90
- "xo": "^0.50.0"
88
+ "typescript": "^4.8.3",
89
+ "vue-eslint-parser": "^9.1.0",
90
+ "xo": "^0.52.3",
91
+ "yaml": "^1.10.2"
91
92
  },
92
93
  "peerDependencies": {
93
- "eslint": ">=8.18.0"
94
+ "eslint": ">=8.23.1"
94
95
  },
95
96
  "ava": {
96
97
  "files": [
package/readme.md CHANGED
@@ -91,6 +91,7 @@ Each rule has emojis denoting:
91
91
  | [no-static-only-class](docs/rules/no-static-only-class.md) | Disallow classes that only have static members. | ✅ | 🔧 | |
92
92
  | [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | ✅ | | |
93
93
  | [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | |
94
+ | [no-unnecessary-await](docs/rules/no-unnecessary-await.md) | Disallow awaiting non-promise values. | ✅ | 🔧 | |
94
95
  | [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | ✅ | 🔧 | |
95
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. | | | |
@@ -105,11 +106,11 @@ Each rule has emojis denoting:
105
106
  | [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | ✅ | 🔧 | |
106
107
  | [numeric-separators-style](docs/rules/numeric-separators-style.md) | Enforce the style of numeric separators by correctly grouping digits. | ✅ | 🔧 | |
107
108
  | [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) | Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. | ✅ | 🔧 | |
108
- | [prefer-array-find](docs/rules/prefer-array-find.md) | Prefer `.find(…)` over the first element from `.filter(…)`. | ✅ | 🔧 | 💡 |
109
+ | [prefer-array-find](docs/rules/prefer-array-find.md) | Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)`. | ✅ | 🔧 | 💡 |
109
110
  | [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. | ✅ | 🔧 | |
110
111
  | [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()`. | ✅ | 🔧 | |
111
- | [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#indexOf()` over `Array#findIndex()` when looking for the index of an item. | ✅ | 🔧 | 💡 |
112
- | [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`. | ✅ | 🔧 | 💡 |
112
+ | [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item. | ✅ | 🔧 | 💡 |
113
+ | [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`. | ✅ | 🔧 | 💡 |
113
114
  | [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | | 🔧 | 💡 |
114
115
  | [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. | ✅ | | 💡 |
115
116
  | [prefer-date-now](docs/rules/prefer-date-now.md) | Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. | ✅ | 🔧 | |
@@ -129,7 +130,7 @@ Each rule has emojis denoting:
129
130
  | [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | ✅ | 🔧 | |
130
131
  | [prefer-module](docs/rules/prefer-module.md) | Prefer JavaScript modules (ESM) over CommonJS. | ✅ | 🔧 | 💡 |
131
132
  | [prefer-native-coercion-functions](docs/rules/prefer-native-coercion-functions.md) | Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly. | ✅ | 🔧 | |
132
- | [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()`. | ✅ | 🔧 | |
133
+ | [prefer-negative-index](docs/rules/prefer-negative-index.md) | Prefer negative index over `.length - index` for `{String,Array,TypedArray}#{slice,at}()` and `Array#splice()`. | ✅ | 🔧 | |
133
134
  | [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | Prefer using the `node:` protocol when importing Node.js builtin modules. | ✅ | 🔧 | |
134
135
  | [prefer-number-properties](docs/rules/prefer-number-properties.md) | Prefer `Number` static properties over global ones. | ✅ | 🔧 | 💡 |
135
136
  | [prefer-object-from-entries](docs/rules/prefer-object-from-entries.md) | Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object. | ✅ | 🔧 | |
@@ -154,6 +155,7 @@ Each rule has emojis denoting:
154
155
  | [require-number-to-fixed-digits-argument](docs/rules/require-number-to-fixed-digits-argument.md) | Enforce using the digits argument with `Number#toFixed()`. | ✅ | 🔧 | |
155
156
  | [require-post-message-target-origin](docs/rules/require-post-message-target-origin.md) | Enforce using the `targetOrigin` argument with `window.postMessage()`. | | | 💡 |
156
157
  | [string-content](docs/rules/string-content.md) | Enforce better string content. | | 🔧 | 💡 |
158
+ | [switch-case-braces](docs/rules/switch-case-braces.md) | Enforce consistent brace style for `case` clauses. | ✅ | 🔧 | |
157
159
  | [template-indent](docs/rules/template-indent.md) | Fix whitespace-insensitive template indentation. | ✅ | 🔧 | |
158
160
  | [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | ✅ | 🔧 | 💡 |
159
161
  | [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | ✅ | 🔧 | |
@@ -10,7 +10,7 @@ function isEmptyNode(node, additionalEmpty) {
10
10
  return true;
11
11
  }
12
12
 
13
- if (additionalEmpty && additionalEmpty(node)) {
13
+ if (additionalEmpty?.(node)) {
14
14
  return true;
15
15
  }
16
16
 
@@ -3,9 +3,7 @@
3
3
  const {isStringLiteral} = require('./literal.js');
4
4
 
5
5
  const isStaticRequire = node => Boolean(
6
- node
7
- && node.type === 'CallExpression'
8
- && node.callee
6
+ node?.type === 'CallExpression'
9
7
  && node.callee.type === 'Identifier'
10
8
  && node.callee.name === 'require'
11
9
  && !node.optional
@@ -51,15 +51,32 @@ const create = context => {
51
51
  return;
52
52
  }
53
53
 
54
- return {
54
+ const problem = {
55
55
  node,
56
56
  messageId: MESSAGE_ID,
57
57
  data: {
58
58
  original,
59
59
  optimized,
60
60
  },
61
- fix: fixer => fixer.replaceText(node, optimized),
62
61
  };
62
+
63
+ if (
64
+ node.parent.type === 'MemberExpression'
65
+ && node.parent.object === node
66
+ && !node.parent.optional
67
+ && !node.parent.computed
68
+ && node.parent.property.type === 'Identifier'
69
+ && (
70
+ node.parent.property.name === 'toString'
71
+ || node.parent.property.name === 'source'
72
+ )
73
+ ) {
74
+ return problem;
75
+ }
76
+
77
+ return Object.assign(problem, {
78
+ fix: fixer => fixer.replaceText(node, optimized),
79
+ });
63
80
  },
64
81
  [newRegExp](node) {
65
82
  const [patternNode, flagsNode] = node.arguments;
@@ -21,7 +21,7 @@ function checkReferences(scope, parent, scopeManager) {
21
21
  const [definition] = resolved.defs;
22
22
 
23
23
  // Skip recursive function name
24
- if (definition && definition.type === 'FunctionName' && resolved.name === definition.name.name) {
24
+ if (definition?.type === 'FunctionName' && resolved.name === definition.name.name) {
25
25
  return false;
26
26
  }
27
27
 
@@ -92,23 +92,20 @@ const reactHooks = [
92
92
  ].flatMap(hookName => [hookName, `React.${hookName}`]);
93
93
 
94
94
  const isReactHook = scope =>
95
- scope.block
96
- && scope.block.parent
97
- && scope.block.parent.callee
95
+ scope.block?.parent?.callee
98
96
  && isNodeMatches(scope.block.parent.callee, reactHooks);
99
97
 
100
98
  const isArrowFunctionWithThis = scope =>
101
99
  scope.type === 'function'
102
- && scope.block
103
- && scope.block.type === 'ArrowFunctionExpression'
100
+ && scope.block?.type === 'ArrowFunctionExpression'
104
101
  && (scope.thisFound || scope.childScopes.some(scope => isArrowFunctionWithThis(scope)));
105
102
 
106
103
  const iifeFunctionTypes = new Set([
107
104
  'FunctionExpression',
108
105
  'ArrowFunctionExpression',
109
106
  ]);
110
- const isIife = node => node
111
- && iifeFunctionTypes.has(node.type)
107
+ const isIife = node =>
108
+ iifeFunctionTypes.has(node.type)
112
109
  && node.parent.type === 'CallExpression'
113
110
  && node.parent.callee === node;
114
111
 
@@ -137,15 +137,15 @@ function * customErrorDefinition(context, node) {
137
137
  if (!nameExpression) {
138
138
  const nameProperty = body.find(node => isPropertyDefinition(node, 'name'));
139
139
 
140
- if (!nameProperty || !nameProperty.value || nameProperty.value.value !== name) {
140
+ if (!nameProperty?.value || nameProperty.value.value !== name) {
141
141
  yield {
142
- node: nameProperty && nameProperty.value ? nameProperty.value : constructorBodyNode,
142
+ node: nameProperty?.value ?? constructorBodyNode,
143
143
  message: `The \`name\` property should be set to \`${name}\`.`,
144
144
  };
145
145
  }
146
146
  } else if (nameExpression.expression.right.value !== name) {
147
147
  yield {
148
- node: nameExpression ? nameExpression.expression.right : constructorBodyNode,
148
+ node: nameExpression?.expression.right ?? constructorBodyNode,
149
149
  message: `The \`name\` property should be set to \`${name}\`.`,
150
150
  };
151
151
  }
@@ -133,7 +133,6 @@ function create(context) {
133
133
  problem.suggest = [
134
134
  {
135
135
  messageId: MESSAGE_ID_SUGGESTION,
136
- data: problem.data,
137
136
  fix,
138
137
  },
139
138
  ];
@@ -107,7 +107,7 @@ function splitFilename(filename) {
107
107
  for (const char of tailing) {
108
108
  const isIgnored = isIgnoredChar(char);
109
109
 
110
- if (lastWord && lastWord.ignored === isIgnored) {
110
+ if (lastWord?.ignored === isIgnored) {
111
111
  lastWord.word += char;
112
112
  } else {
113
113
  lastWord = {
@@ -147,7 +147,7 @@ const create = context => {
147
147
  const filenameWithExtension = context.getPhysicalFilename();
148
148
 
149
149
  if (filenameWithExtension === '<input>' || filenameWithExtension === '<text>') {
150
- return {};
150
+ return;
151
151
  }
152
152
 
153
153
  return {
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+ const {isSemicolonToken} = require('eslint-utils');
3
+
4
+ function * addParenthesizesToReturnOrThrowExpression(fixer, node, sourceCode) {
5
+ if (node.type !== 'ReturnStatement' && node.type !== 'ThrowStatement') {
6
+ return;
7
+ }
8
+
9
+ const returnOrThrowToken = sourceCode.getFirstToken(node);
10
+ yield fixer.insertTextAfter(returnOrThrowToken, ' (');
11
+
12
+ const lastToken = sourceCode.getLastToken(node);
13
+ if (!isSemicolonToken(lastToken)) {
14
+ yield fixer.insertTextAfter(node, ')');
15
+ return;
16
+ }
17
+
18
+ yield fixer.insertTextBefore(lastToken, ')');
19
+ }
20
+
21
+ module.exports = addParenthesizesToReturnOrThrowExpression;
@@ -19,4 +19,5 @@ module.exports = {
19
19
  removeSpacesAfter: require('./remove-spaces-after.js'),
20
20
  fixSpaceAroundKeyword: require('./fix-space-around-keywords.js'),
21
21
  replaceStringLiteral: require('./replace-string-literal.js'),
22
+ addParenthesizesToReturnOrThrowExpression: require('./add-parenthesizes-to-return-or-throw-expression.js'),
22
23
  };
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- function removeSpacesAfter(indexOrNode, sourceCode, fixer) {
4
- let index = indexOrNode;
5
- if (typeof indexOrNode === 'object' && Array.isArray(indexOrNode.range)) {
6
- index = indexOrNode.range[1];
3
+ function removeSpacesAfter(indexOrNodeOrToken, sourceCode, fixer) {
4
+ let index = indexOrNodeOrToken;
5
+ if (typeof indexOrNodeOrToken === 'object' && Array.isArray(indexOrNodeOrToken.range)) {
6
+ index = indexOrNodeOrToken.range[1];
7
7
  }
8
8
 
9
9
  const textAfter = sourceCode.text.slice(index);
@@ -1,41 +1,17 @@
1
1
  'use strict';
2
2
  const isNewExpressionWithParentheses = require('../utils/is-new-expression-with-parentheses.js');
3
3
  const {isParenthesized} = require('../utils/parentheses.js');
4
+ const isOnSameLine = require('../utils/is-on-same-line.js');
5
+ const addParenthesizesToReturnOrThrowExpression = require('./add-parenthesizes-to-return-or-throw-expression.js');
6
+ const removeSpaceAfter = require('./remove-spaces-after.js');
4
7
 
5
- function * fixReturnOrThrowStatementArgument(newExpression, sourceCode, fixer) {
6
- const {parent} = newExpression;
7
- if (
8
- (parent.type !== 'ReturnStatement' && parent.type !== 'ThrowStatement')
9
- || parent.argument !== newExpression
10
- || isParenthesized(newExpression, sourceCode)
11
- ) {
12
- return;
13
- }
14
-
15
- const returnStatement = parent;
16
- const returnToken = sourceCode.getFirstToken(returnStatement);
17
- const classNode = newExpression.callee;
18
-
19
- // Ideally, we should use first parenthesis of the `callee`, and should check spaces after the `new` token
20
- // But adding extra parentheses is harmless, no need to be too complicated
21
- if (returnToken.loc.start.line === classNode.loc.start.line) {
22
- return;
23
- }
8
+ function * switchNewExpressionToCallExpression(newExpression, sourceCode, fixer) {
9
+ const newToken = sourceCode.getFirstToken(newExpression);
10
+ yield fixer.remove(newToken);
11
+ yield removeSpaceAfter(newToken, sourceCode, fixer);
24
12
 
25
- yield fixer.insertTextAfter(returnToken, ' (');
26
- yield fixer.insertTextAfter(newExpression, ')');
27
- }
28
-
29
- function * switchNewExpressionToCallExpression(node, sourceCode, fixer) {
30
- const [start] = node.range;
31
- let end = start + 3; // `3` = length of `new`
32
- const textAfter = sourceCode.text.slice(end);
33
- const [leadingSpaces] = textAfter.match(/^\s*/);
34
- end += leadingSpaces.length;
35
- yield fixer.removeRange([start, end]);
36
-
37
- if (!isNewExpressionWithParentheses(node, sourceCode)) {
38
- yield fixer.insertTextAfter(node, '()');
13
+ if (!isNewExpressionWithParentheses(newExpression, sourceCode)) {
14
+ yield fixer.insertTextAfter(newExpression, '()');
39
15
  }
40
16
 
41
17
  /*
@@ -48,7 +24,11 @@ function * switchNewExpressionToCallExpression(node, sourceCode, fixer) {
48
24
  }
49
25
  ```
50
26
  */
51
- yield * fixReturnOrThrowStatementArgument(node, sourceCode, fixer);
27
+ if (!isOnSameLine(newToken, newExpression.callee) && !isParenthesized(newExpression, sourceCode)) {
28
+ // Ideally, we should use first parenthesis of the `callee`, and should check spaces after the `new` token
29
+ // But adding extra parentheses is harmless, no need to be too complicated
30
+ yield * addParenthesizesToReturnOrThrowExpression(fixer, newExpression.parent, sourceCode);
31
+ }
52
32
  }
53
33
 
54
34
  module.exports = switchNewExpressionToCallExpression;
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- const {ReferenceTracker} = require('eslint-utils');
2
+ const {GlobalReferenceTracker} = require('./utils/global-reference-tracker.js');
3
3
  const builtins = require('./utils/builtins.js');
4
4
  const {
5
5
  switchCallExpressionToNewExpression,
@@ -11,64 +11,65 @@ const messages = {
11
11
  disallow: 'Use `{{name}}()` instead of `new {{name}}()`.',
12
12
  };
13
13
 
14
- function * enforceNewExpression({sourceCode, tracker}) {
15
- const traceMap = Object.fromEntries(
16
- builtins.enforceNew.map(name => [name, {[ReferenceTracker.CALL]: true}]),
17
- );
18
-
19
- for (const {node, path: [name]} of tracker.iterateGlobalReferences(traceMap)) {
20
- if (name === 'Object') {
21
- const {parent} = node;
22
- if (
23
- parent.type === 'BinaryExpression'
24
- && (parent.operator === '===' || parent.operator === '!==')
25
- && (parent.left === node || parent.right === node)
26
- ) {
27
- continue;
28
- }
14
+ function enforceNewExpression({node, path: [name]}, sourceCode) {
15
+ if (name === 'Object') {
16
+ const {parent} = node;
17
+ if (
18
+ parent.type === 'BinaryExpression'
19
+ && (parent.operator === '===' || parent.operator === '!==')
20
+ && (parent.left === node || parent.right === node)
21
+ ) {
22
+ return;
29
23
  }
30
-
31
- yield {
32
- node,
33
- messageId: 'enforce',
34
- data: {name},
35
- fix: fixer => switchCallExpressionToNewExpression(node, sourceCode, fixer),
36
- };
37
24
  }
25
+
26
+ return {
27
+ node,
28
+ messageId: 'enforce',
29
+ data: {name},
30
+ fix: fixer => switchCallExpressionToNewExpression(node, sourceCode, fixer),
31
+ };
38
32
  }
39
33
 
40
- function * enforceCallExpression({sourceCode, tracker}) {
41
- const traceMap = Object.fromEntries(
42
- builtins.disallowNew.map(name => [name, {[ReferenceTracker.CONSTRUCT]: true}]),
43
- );
34
+ function enforceCallExpression({node, path: [name]}, sourceCode) {
35
+ const problem = {
36
+ node,
37
+ messageId: 'disallow',
38
+ data: {name},
39
+ };
44
40
 
45
- for (const {node, path: [name]} of tracker.iterateGlobalReferences(traceMap)) {
46
- const problem = {
47
- node,
48
- messageId: 'disallow',
49
- data: {name},
41
+ if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
42
+ problem.fix = function * (fixer) {
43
+ yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
50
44
  };
51
-
52
- if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
53
- problem.fix = function * (fixer) {
54
- yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
55
- };
56
- }
57
-
58
- yield problem;
59
45
  }
46
+
47
+ return problem;
60
48
  }
61
49
 
62
50
  /** @param {import('eslint').Rule.RuleContext} context */
63
- const create = context => ({
64
- * 'Program:exit'() {
65
- const sourceCode = context.getSourceCode();
66
- const tracker = new ReferenceTracker(context.getScope());
51
+ const create = context => {
52
+ const sourceCode = context.getSourceCode();
53
+ const newExpressionTracker = new GlobalReferenceTracker({
54
+ objects: builtins.disallowNew,
55
+ type: GlobalReferenceTracker.CONSTRUCT,
56
+ handle: reference => enforceCallExpression(reference, sourceCode),
57
+ });
58
+ const callExpressionTracker = new GlobalReferenceTracker({
59
+ objects: builtins.enforceNew,
60
+ type: GlobalReferenceTracker.CALL,
61
+ handle: reference => enforceNewExpression(reference, sourceCode),
62
+ });
67
63
 
68
- yield * enforceNewExpression({sourceCode, tracker});
69
- yield * enforceCallExpression({sourceCode, tracker});
70
- },
71
- });
64
+ return {
65
+ * 'Program:exit'() {
66
+ const scope = context.getScope();
67
+
68
+ yield * newExpressionTracker.track(scope);
69
+ yield * callExpressionTracker.track(scope);
70
+ },
71
+ };
72
+ };
72
73
 
73
74
  /** @type {import('eslint').Rule.RuleModule} */
74
75
  module.exports = {
@@ -39,6 +39,14 @@ const iteratorMethods = [
39
39
  ],
40
40
  },
41
41
  ],
42
+ [
43
+ 'findLast',
44
+ {
45
+ ignore: [
46
+ 'Boolean',
47
+ ],
48
+ },
49
+ ],
42
50
  [
43
51
  'findIndex',
44
52
  {
@@ -47,6 +55,14 @@ const iteratorMethods = [
47
55
  ],
48
56
  },
49
57
  ],
58
+ [
59
+ 'findLastIndex',
60
+ {
61
+ ignore: [
62
+ 'Boolean',
63
+ ],
64
+ },
65
+ ],
50
66
  [
51
67
  'flatMap',
52
68
  ],
@@ -59,8 +59,9 @@ function shouldSwitchReturnStatementToBlockStatement(returnStatement) {
59
59
  const {parent} = returnStatement;
60
60
 
61
61
  switch (parent.type) {
62
- case 'IfStatement':
62
+ case 'IfStatement': {
63
63
  return parent.consequent === returnStatement || parent.alternate === returnStatement;
64
+ }
64
65
 
65
66
  // These parent's body need switch to `BlockStatement` too, but since they are "continueAble", won't fix
66
67
  // case 'ForStatement':
@@ -68,11 +69,13 @@ function shouldSwitchReturnStatementToBlockStatement(returnStatement) {
68
69
  // case 'ForOfStatement':
69
70
  // case 'WhileStatement':
70
71
  // case 'DoWhileStatement':
71
- case 'WithStatement':
72
+ case 'WithStatement': {
72
73
  return parent.body === returnStatement;
74
+ }
73
75
 
74
- default:
76
+ default: {
75
77
  return false;
78
+ }
76
79
  }
77
80
  }
78
81
 
@@ -315,20 +318,33 @@ function isAssignmentLeftHandSide(node) {
315
318
  switch (parent.type) {
316
319
  case 'AssignmentExpression':
317
320
  case 'ForInStatement':
318
- case 'ForOfStatement':
321
+ case 'ForOfStatement': {
319
322
  return parent.left === node;
320
- case 'UpdateExpression':
323
+ }
324
+
325
+ case 'UpdateExpression': {
321
326
  return parent.argument === node;
322
- case 'Property':
327
+ }
328
+
329
+ case 'Property': {
323
330
  return parent.value === node && isAssignmentLeftHandSide(parent);
324
- case 'AssignmentPattern':
331
+ }
332
+
333
+ case 'AssignmentPattern': {
325
334
  return parent.left === node && isAssignmentLeftHandSide(parent);
326
- case 'ArrayPattern':
335
+ }
336
+
337
+ case 'ArrayPattern': {
327
338
  return parent.elements.includes(node) && isAssignmentLeftHandSide(parent);
328
- case 'ObjectPattern':
339
+ }
340
+
341
+ case 'ObjectPattern': {
329
342
  return parent.properties.includes(node) && isAssignmentLeftHandSide(parent);
330
- default:
343
+ }
344
+
345
+ default: {
331
346
  return false;
347
+ }
332
348
  }
333
349
  }
334
350
 
@@ -65,7 +65,9 @@ const selector = [
65
65
  'every',
66
66
  'filter',
67
67
  'find',
68
+ 'findLast',
68
69
  'findIndex',
70
+ 'findLastIndex',
69
71
  'flatMap',
70
72
  'forEach',
71
73
  'map',
@@ -30,6 +30,7 @@ const create = context => {
30
30
  && memberExpression.parent.type === 'VariableDeclarator'
31
31
  && memberExpression.parent.init === memberExpression
32
32
  && memberExpression.parent.id.type === 'Identifier'
33
+ && !memberExpression.parent.id.typeAnnotation
33
34
  ) {
34
35
  problem.fix = function * (fixer) {
35
36
  const variable = memberExpression.parent.id;
@@ -52,6 +53,7 @@ const create = context => {
52
53
  && memberExpression.parent.init === memberExpression
53
54
  && memberExpression.parent.id.type === 'Identifier'
54
55
  && memberExpression.parent.id.name === property.name
56
+ && !memberExpression.parent.id.typeAnnotation
55
57
  ) {
56
58
  problem.fix = function * (fixer) {
57
59
  const variable = memberExpression.parent.id;