eslint-plugin-unicorn 70.0.0 → 71.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/package.json +26 -25
  2. package/readme.md +5 -1
  3. package/rules/ast/call-or-new-expression.js +2 -8
  4. package/rules/ast/is-member-expression.js +2 -8
  5. package/rules/comment-content.js +4 -4
  6. package/rules/consistent-boolean-name.js +94 -10
  7. package/rules/consistent-compound-words.js +1 -2
  8. package/rules/consistent-json-file-read.js +2 -5
  9. package/rules/dom-node-dataset.js +3 -7
  10. package/rules/escape-case.js +4 -4
  11. package/rules/expiring-todo-comments.js +2 -2
  12. package/rules/explicit-length-check.js +3 -3
  13. package/rules/filename-case.js +4 -4
  14. package/rules/fix/remove-argument.js +34 -8
  15. package/rules/fix/remove-statement.js +1 -3
  16. package/rules/id-match.js +2 -2
  17. package/rules/isolated-functions.js +47 -7
  18. package/rules/name-replacements.js +1 -2
  19. package/rules/no-anonymous-default-export.js +1 -3
  20. package/rules/no-array-callback-reference.js +2 -10
  21. package/rules/no-chained-comparison.js +1 -1
  22. package/rules/no-computed-property-existence-check.js +1 -4
  23. package/rules/no-declarations-before-early-exit.js +6 -5
  24. package/rules/no-error-property-assignment.js +3 -3
  25. package/rules/no-for-loop.js +4 -4
  26. package/rules/no-keyword-prefix.js +2 -2
  27. package/rules/no-mismatched-map-key.js +13 -20
  28. package/rules/no-named-default.js +2 -5
  29. package/rules/no-negated-array-predicate.js +4 -11
  30. package/rules/no-negated-comparison.js +7 -10
  31. package/rules/no-negation-in-equality-check.js +2 -5
  32. package/rules/no-process-exit.js +6 -6
  33. package/rules/no-static-only-class.js +2 -8
  34. package/rules/no-thenable.js +9 -15
  35. package/rules/no-undeclared-class-members.js +1 -5
  36. package/rules/no-unnecessary-array-flat-map.js +6 -5
  37. package/rules/no-unnecessary-global-this.js +5 -9
  38. package/rules/no-unused-properties.js +0 -1
  39. package/rules/no-useless-coercion.js +1 -2
  40. package/rules/no-useless-undefined.js +2 -2
  41. package/rules/prefer-array-find.js +2 -5
  42. package/rules/prefer-bigint-literals.js +1 -0
  43. package/rules/prefer-boolean-return.js +20 -3
  44. package/rules/prefer-default-parameters.js +2 -2
  45. package/rules/prefer-else-if.js +5 -9
  46. package/rules/prefer-has-check.js +6 -3
  47. package/rules/prefer-https.js +3 -3
  48. package/rules/prefer-identifier-import-export-specifiers.js +1 -3
  49. package/rules/prefer-minimal-ternary.js +4 -4
  50. package/rules/prefer-object-define-properties.js +1 -3
  51. package/rules/prefer-observer-apis.js +2 -5
  52. package/rules/prefer-private-class-fields.js +1 -3
  53. package/rules/prefer-simple-sort-comparator.js +6 -6
  54. package/rules/prefer-single-object-destructuring.js +2 -5
  55. package/rules/prefer-single-replace.js +2 -2
  56. package/rules/prefer-spread.js +22 -32
  57. package/rules/prefer-string-pad-start-end.js +1 -6
  58. package/rules/prefer-string-repeat.js +16 -9
  59. package/rules/prefer-string-replace-all.js +1 -1
  60. package/rules/prefer-ternary.js +5 -5
  61. package/rules/prefer-toggle-attribute.js +3 -3
  62. package/rules/prefer-top-level-await.js +6 -1
  63. package/rules/prefer-uint8array-base64.js +3 -1
  64. package/rules/require-proxy-trap-boolean-return.js +2 -2
  65. package/rules/rule/to-eslint-create.js +1 -2
  66. package/rules/rule/to-eslint-listener.js +1 -3
  67. package/rules/rule/to-eslint-problem.js +1 -3
  68. package/rules/rule/to-eslint-rule-fixer.js +1 -3
  69. package/rules/rule/to-eslint-rule.js +1 -3
  70. package/rules/rule/to-eslint-rules.js +1 -3
  71. package/rules/rule/unicorn-context.js +1 -3
  72. package/rules/rule/unicorn-listeners.js +1 -3
  73. package/rules/shared/no-array-mutate-rule.js +1 -3
  74. package/rules/shared/regexp-escape.js +4 -4
  75. package/rules/utils/can-tokens-be-adjacent.js +95 -0
  76. package/rules/utils/escape-string.js +2 -8
  77. package/rules/utils/get-available-variable-name.js +2 -13
  78. package/rules/utils/get-precedence.js +112 -0
  79. package/rules/utils/get-token-store.js +1 -3
  80. package/rules/utils/global-reference-tracker.js +3 -6
  81. package/rules/utils/index.js +4 -0
  82. package/rules/utils/is-boolean.js +90 -32
  83. package/rules/utils/is-identifier-name.js +5 -0
  84. package/rules/utils/is-promise-type.js +1 -3
  85. package/rules/utils/is-react-hook-name.js +5 -0
  86. package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
  87. package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
  88. package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
  89. package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
  90. package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
  91. package/rules/utils/to-location.js +1 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "70.0.0",
3
+ "version": "71.0.0",
4
4
  "description": "More than 300 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,58 +62,59 @@
62
62
  "xo"
63
63
  ],
64
64
  "dependencies": {
65
- "@babel/helper-validator-identifier": "^7.29.7",
66
65
  "@eslint-community/eslint-utils": "^4.9.1",
67
- "browserslist": "^4.28.2",
66
+ "browserslist": "^4.28.4",
68
67
  "change-case": "^5.4.4",
69
68
  "ci-info": "^4.4.0",
70
69
  "core-js-compat": "^3.49.0",
71
70
  "detect-indent": "^7.0.2",
72
71
  "find-up-simple": "^1.0.1",
73
- "globals": "^17.6.0",
72
+ "globals": "^17.7.0",
74
73
  "indent-string": "^5.0.0",
75
74
  "is-builtin-module": "^5.0.0",
76
- "jsesc": "^3.1.0",
75
+ "is-identifier": "^1.1.0",
77
76
  "pluralize": "^8.0.0",
78
- "regjsparser": "^0.13.1",
79
- "semver": "^7.8.4",
77
+ "quote-js-string": "^0.1.0",
78
+ "regjsparser": "^0.13.2",
79
+ "reserved-identifiers": "^1.2.0",
80
+ "semver": "^7.8.5",
80
81
  "strip-indent": "^4.1.1"
81
82
  },
82
83
  "devDependencies": {
83
- "@babel/code-frame": "^8.0.0-rc.6",
84
- "@eslint/css": "^1.3.0",
84
+ "@babel/code-frame": "^8.0.0",
85
+ "@eslint/css": "^1.4.0",
85
86
  "@eslint/eslintrc": "^3.3.5",
86
- "@eslint/json": "^2.0.0",
87
- "@eslint/markdown": "^8.0.2",
88
- "@html-eslint/eslint-plugin": "^0.62.0",
89
- "@html-eslint/parser": "^0.62.0",
87
+ "@eslint/json": "^2.0.1",
88
+ "@eslint/markdown": "^8.0.3",
89
+ "@html-eslint/eslint-plugin": "^0.63.0",
90
+ "@html-eslint/parser": "^0.63.0",
90
91
  "@lubien/fixture-beta-package": "^1.0.0-beta.1",
91
- "@typescript-eslint/parser": "^8.61.0",
92
- "@typescript-eslint/types": "^8.61.0",
92
+ "@typescript-eslint/parser": "^8.62.1",
93
+ "@typescript-eslint/types": "^8.62.1",
93
94
  "ava": "^8.0.1",
94
95
  "c8": "^11.0.0",
95
96
  "enquirer": "^2.4.1",
96
- "eslint": "^10.4.1",
97
+ "eslint": "^10.6.0",
97
98
  "eslint-ava-rule-tester": "^5.0.1",
98
- "eslint-config-xo": "^0.52.0",
99
+ "eslint-config-xo": "^0.56.0",
99
100
  "eslint-doc-generator": "^3.6.0",
100
- "eslint-plugin-eslint-plugin": "^7.4.0",
101
- "eslint-plugin-jsdoc": "^63.0.2",
102
- "eslint-plugin-unicorn": "^65.0.1",
101
+ "eslint-plugin-eslint-plugin": "^7.4.1",
102
+ "eslint-plugin-jsdoc": "^63.0.11",
103
+ "eslint-plugin-unicorn": "^70.0.0",
103
104
  "eslint-remote-tester": "^4.0.4",
104
105
  "eslint-remote-tester-repositories": "^2.0.2",
105
106
  "eslint-scope": "^9.1.2",
106
107
  "espree": "^11.2.0",
107
- "listr2": "^10.2.1",
108
- "markdownlint-cli": "^0.48.0",
108
+ "listr2": "^10.2.2",
109
+ "markdownlint-cli": "^0.49.0",
109
110
  "nano-spawn": "^2.1.0",
110
111
  "node-style-text": "^2.1.2",
111
- "npm-package-json-lint": "^10.4.0",
112
- "npm-run-all2": "^9.0.1",
112
+ "npm-package-json-lint": "^10.4.1",
113
+ "npm-run-all2": "^9.0.2",
113
114
  "open-editor": "^6.0.0",
114
115
  "outdent": "^0.8.0",
115
116
  "pretty-ms": "^9.3.0",
116
- "svelte": "^5.56.3",
117
+ "svelte": "^5.56.4",
117
118
  "svelte-eslint-parser": "^1.8.0",
118
119
  "typescript": "^6.0.3",
119
120
  "vue-eslint-parser": "^10.4.1",
package/readme.md CHANGED
@@ -391,7 +391,7 @@ export default defineConfig([
391
391
  | [prefer-top-level-await](docs/rules/prefer-top-level-await.md) | Prefer top-level await over top-level promises and async function calls. | ✅ ☑️ | | 💡 | |
392
392
  | [prefer-type-error](docs/rules/prefer-type-error.md) | Enforce throwing `TypeError` in type checking conditions. | ✅ ☑️ | 🔧 | | |
393
393
  | [prefer-type-literal-last](docs/rules/prefer-type-literal-last.md) | Require type literals to be last in union types. | ✅ | 🔧 | | |
394
- | [prefer-uint8array-base64](docs/rules/prefer-uint8array-base64.md) | Prefer `Uint8Array#toBase64()` and `Uint8Array.fromBase64()` over `atob()`, `btoa()`, and `Buffer` base64 conversions. | ✅ ☑️ | | 💡 | |
394
+ | [prefer-uint8array-base64](docs/rules/prefer-uint8array-base64.md) | Prefer `Uint8Array#toBase64()` and `Uint8Array.fromBase64()` over `atob()`, `btoa()`, and `Buffer` base64 conversions. | | | 💡 | |
395
395
  | [prefer-unary-minus](docs/rules/prefer-unary-minus.md) | Prefer the unary minus operator over multiplying or dividing by `-1`. | ✅ ☑️ | 🔧 | | |
396
396
  | [prefer-unicode-code-point-escapes](docs/rules/prefer-unicode-code-point-escapes.md) | Prefer Unicode code point escapes over legacy escape sequences. | ✅ ☑️ | 🔧 | 💡 | |
397
397
  | [prefer-url-can-parse](docs/rules/prefer-url-can-parse.md) | Prefer `URL.canParse()` over constructing a `URL` in a try/catch for validation. | ✅ ☑️ | 🔧 | | |
@@ -566,3 +566,7 @@ export default defineConfig([
566
566
 
567
567
  - [Sindre Sorhus](https://github.com/sindresorhus)
568
568
  - [Fisker Cheung](https://github.com/fisker)
569
+
570
+ ## Related
571
+
572
+ - [eslint-node-test](https://github.com/sindresorhus/eslint-node-test) — ESLint rules for the Node.js built-in test runner.
@@ -79,18 +79,12 @@ function create(node, options, types) {
79
79
  }
80
80
  }
81
81
 
82
- if (
83
- Array.isArray(names)
82
+ return !(Array.isArray(names)
84
83
  && names.length > 0
85
84
  && (
86
85
  node.callee.type !== 'Identifier'
87
86
  || !names.includes(node.callee.name)
88
- )
89
- ) {
90
- return false;
91
- }
92
-
93
- return true;
87
+ ));
94
88
  }
95
89
 
96
90
  /**
@@ -83,16 +83,10 @@ export default function isMemberExpression(node, options) {
83
83
  return false;
84
84
  }
85
85
 
86
- if (
87
- Array.isArray(objects)
86
+ return !(Array.isArray(objects)
88
87
  && objects.length > 0
89
88
  && (
90
89
  node.object.type !== 'Identifier'
91
90
  || !objects.includes(node.object.name)
92
- )
93
- ) {
94
- return false;
95
- }
96
-
97
- return true;
91
+ ));
98
92
  }
@@ -346,7 +346,7 @@ const pathTerminatorCharacters = '"\'`<>';
346
346
  const packageSpecifierTerminatorCharacters = '"\'`()[]{}<>,';
347
347
  const domainLeadingPunctuation = '([{<';
348
348
  const domainTrailingPunctuation = '.,;:!?)]}>';
349
- const maskCharacter = '\uFFFF';
349
+ const maskCharacter = '\u{FFFF}';
350
350
  const openBrackets = '([{';
351
351
  const closeBrackets = ')]}';
352
352
  const quoteCharacters = '"\'`';
@@ -1062,14 +1062,14 @@ const create = context => {
1062
1062
  return;
1063
1063
  }
1064
1064
 
1065
- let checked = false;
1065
+ let isChecked = false;
1066
1066
 
1067
1067
  onRoot(context, function * (node) {
1068
- if (checked) {
1068
+ if (isChecked) {
1069
1069
  return;
1070
1070
  }
1071
1071
 
1072
- checked = true;
1072
+ isChecked = true;
1073
1073
 
1074
1074
  const {sourceCode} = context;
1075
1075
  const comments = getRuleComments(context);
@@ -6,9 +6,11 @@ import {
6
6
  getAvailableVariableName,
7
7
  getScopes,
8
8
  getVariableIdentifiers,
9
+ isReactHookName,
9
10
  isNullishType,
10
11
  isTypeParameterType,
11
12
  isUnknownType,
13
+ lowerFirst,
12
14
  upperFirst,
13
15
  } from './utils/index.js';
14
16
  import {
@@ -17,6 +19,7 @@ import {
17
19
  isBooleanFunctionReference,
18
20
  isBooleanFunctionTypeAnnotation,
19
21
  isBooleanTypeAnnotation,
22
+ isFunctionTypeAnnotation,
20
23
  } from './utils/is-boolean.js';
21
24
 
22
25
  const MESSAGE_ID = 'consistent-boolean-name';
@@ -98,6 +101,7 @@ const typeScriptExpressionWrapperTypes = new Set([
98
101
  ]);
99
102
  const isUpperCase = string => string === string.toUpperCase();
100
103
  const stripLeadingUnderscores = name => name.replace(/^_+/, '');
104
+ const isScreamingCase = name => /^[A-Z][\dA-Z_]*$/.test(stripLeadingUnderscores(name));
101
105
 
102
106
  const isFunction = node => [
103
107
  'ArrowFunctionExpression',
@@ -180,6 +184,18 @@ function getReplacementName(name, prefix) {
180
184
  : `${leadingUnderscores}${prefix}${upperFirst(nameWithoutLeadingUnderscores)}`;
181
185
  }
182
186
 
187
+ function getReactHookReplacementName({name, nameForPrefixCheck}, prefix) {
188
+ if (name === nameForPrefixCheck) {
189
+ return getReplacementName(name, prefix);
190
+ }
191
+
192
+ const hookName = isScreamingCase(nameForPrefixCheck)
193
+ ? getReplacementName(nameForPrefixCheck, prefix)
194
+ : `${prefix}${upperFirst(nameForPrefixCheck)}`;
195
+
196
+ return `use${upperFirst(hookName)}`;
197
+ }
198
+
183
199
  const isExportedIdentifier = identifier => {
184
200
  if (
185
201
  identifier.parent.type === 'VariableDeclarator'
@@ -223,6 +239,23 @@ const shouldSuggestRename = variable => getVariableIdentifiers(variable)
223
239
  && identifier.type !== 'JSXIdentifier',
224
240
  );
225
241
 
242
+ function isDestructuredDefinition(definition) {
243
+ for (let node = definition.name; node && node !== definition.node; node = node.parent) {
244
+ if (
245
+ node.type === 'ObjectPattern'
246
+ || node.type === 'ArrayPattern'
247
+ ) {
248
+ return true;
249
+ }
250
+ }
251
+
252
+ return false;
253
+ }
254
+
255
+ const isDestructuredVariable = variable =>
256
+ variable.defs.length > 0
257
+ && variable.defs.every(definition => isDestructuredDefinition(definition));
258
+
226
259
  const isBooleanFunctionDefinition = (definition, context) =>
227
260
  definition.type === 'FunctionName'
228
261
  && isFunction(definition.node)
@@ -264,6 +297,41 @@ function getFunctionDefinitions(variable) {
264
297
  return overloadDefinitions.length > 0 ? overloadDefinitions : variable.defs;
265
298
  }
266
299
 
300
+ function isReactHookFunctionBinding(variable, context) {
301
+ if (
302
+ variable.name === 'use'
303
+ || !isReactHookName(variable.name)
304
+ ) {
305
+ return false;
306
+ }
307
+
308
+ if (getFunctionDefinitions(variable)) {
309
+ return true;
310
+ }
311
+
312
+ const definition = getSupportedVariableDefinition(variable);
313
+
314
+ if (definition?.type === 'FunctionName') {
315
+ return isFunction(definition.node);
316
+ }
317
+
318
+ return definition?.type === 'Variable'
319
+ && (
320
+ isFunction(definition.node.init)
321
+ || isFunctionTypeAnnotation(definition.name.typeAnnotation, context, context.sourceCode.getScope(definition.name))
322
+ );
323
+ }
324
+
325
+ function getNameForPrefixCheck(variable, context) {
326
+ if (!isReactHookFunctionBinding(variable, context)) {
327
+ return variable.name;
328
+ }
329
+
330
+ const hookName = variable.name.slice(3);
331
+
332
+ return isScreamingCase(hookName) ? hookName : lowerFirst(hookName);
333
+ }
334
+
267
335
  function combineBooleanStates(states) {
268
336
  if (
269
337
  states.length === 0
@@ -888,7 +956,7 @@ function getPropertyBooleanState(node, context) {
888
956
  return state === unknown && isBooleanProperty(node, context) ? boolean : state;
889
957
  }
890
958
 
891
- function getSuggestions(variable, prefixes, context) {
959
+ function getSuggestions(variable, prefixes, context, nameForPrefixCheck) {
892
960
  if (
893
961
  !shouldSuggestRename(variable)
894
962
  || variable.references.some(reference => reference.vueUsedInTemplate)
@@ -904,7 +972,7 @@ function getSuggestions(variable, prefixes, context) {
904
972
  const suggestions = [];
905
973
 
906
974
  for (const prefix of prefixes) {
907
- const replacement = getAvailableVariableName(getReplacementName(variable.name, prefix), scopes);
975
+ const replacement = getAvailableVariableName(getReactHookReplacementName({name: variable.name, nameForPrefixCheck}, prefix), scopes);
908
976
 
909
977
  if (!replacement || usedReplacements.has(replacement)) {
910
978
  continue;
@@ -954,7 +1022,13 @@ function isInDeclareContext(node) {
954
1022
  return false;
955
1023
  }
956
1024
 
957
- function getAutofix(variable, prefixes, context, suggestions) {
1025
+ function getAutofix({
1026
+ variable,
1027
+ prefixes,
1028
+ context,
1029
+ suggestions,
1030
+ nameForPrefixCheck,
1031
+ }) {
958
1032
  if (
959
1033
  !suggestions
960
1034
  || !isAutofixableVariable(variable, context)
@@ -963,7 +1037,7 @@ function getAutofix(variable, prefixes, context, suggestions) {
963
1037
  }
964
1038
 
965
1039
  const [prefix] = prefixes;
966
- const replacement = getReplacementName(variable.name, prefix);
1040
+ const replacement = getReactHookReplacementName({name: variable.name, nameForPrefixCheck}, prefix);
967
1041
  const suggestion = suggestions.find(suggestion => suggestion.data.replacement === replacement);
968
1042
 
969
1043
  return suggestion?.fix;
@@ -978,11 +1052,15 @@ const create = context => {
978
1052
  }
979
1053
 
980
1054
  const checkVariable = variable => {
981
- if (isIgnoredName(variable.name, ignore)) {
1055
+ if (
1056
+ isIgnoredName(variable.name, ignore)
1057
+ || isDestructuredVariable(variable)
1058
+ ) {
982
1059
  return;
983
1060
  }
984
1061
 
985
- const booleanPrefix = getBooleanPrefix(variable.name, prefixes);
1062
+ const nameForPrefixCheck = getNameForPrefixCheck(variable, context);
1063
+ const booleanPrefix = getBooleanPrefix(nameForPrefixCheck, prefixes);
986
1064
  if (booleanPrefix) {
987
1065
  if (getVariableBooleanState(variable, context) === nonBoolean) {
988
1066
  const [definition] = variable.defs;
@@ -991,7 +1069,7 @@ const create = context => {
991
1069
  node: definition.name,
992
1070
  messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
993
1071
  data: {
994
- name: variable.name,
1072
+ name: nameForPrefixCheck,
995
1073
  prefix: booleanPrefix,
996
1074
  },
997
1075
  });
@@ -1008,16 +1086,22 @@ const create = context => {
1008
1086
  }
1009
1087
 
1010
1088
  const [definition] = variable.defs;
1011
- const suggest = getSuggestions(variable, prefixes, context);
1089
+ const suggest = getSuggestions(variable, prefixes, context, nameForPrefixCheck);
1012
1090
 
1013
1091
  context.report({
1014
1092
  node: definition.name,
1015
1093
  messageId: MESSAGE_ID,
1016
1094
  data: {
1017
- name: variable.name,
1095
+ name: nameForPrefixCheck,
1018
1096
  prefixes: formatPrefixes(prefixes),
1019
1097
  },
1020
- fix: getAutofix(variable, prefixes, context, suggest),
1098
+ fix: getAutofix({
1099
+ variable,
1100
+ prefixes,
1101
+ context,
1102
+ suggestions: suggest,
1103
+ nameForPrefixCheck,
1104
+ }),
1021
1105
  suggest,
1022
1106
  });
1023
1107
  };
@@ -1,10 +1,10 @@
1
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
2
1
  import {
3
2
  getAvailableVariableName,
4
3
  getScopes,
5
4
  isShorthandPropertyValue,
6
5
  lowerFirst,
7
6
  upperFirst,
7
+ isIdentifierName,
8
8
  } from './utils/index.js';
9
9
  import {
10
10
  isClassVariable,
@@ -17,7 +17,6 @@ import {renameVariable} from './fix/index.js';
17
17
 
18
18
  const MESSAGE_ID_ERROR = 'consistent-compound-words/error';
19
19
  const MESSAGE_ID_RENAME = 'consistent-compound-words/rename';
20
- const {isIdentifierName} = helperValidatorIdentifier;
21
20
 
22
21
  const messages = {
23
22
  [MESSAGE_ID_ERROR]: 'Prefer `{{replacement}}` over `{{name}}`.',
@@ -113,15 +113,12 @@ function isBufferEncoding(node, scope) {
113
113
 
114
114
  function isJsonReadFileCall(node, scope) {
115
115
  if (
116
- !(
117
- node
116
+ !(node
118
117
  && node.type === 'CallExpression'
119
118
  && !node.optional
120
119
  && (node.arguments.length === 1 || node.arguments.length === 2)
121
120
  && node.arguments.every(node => node.type !== 'SpreadElement')
122
- && node.callee.type === 'MemberExpression'
123
- && !node.callee.optional
124
- )
121
+ && node.callee.type === 'MemberExpression') || node.callee.optional
125
122
  ) {
126
123
  return false;
127
124
  }
@@ -1,4 +1,3 @@
1
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
2
1
  import {findVariable} from '@eslint-community/eslint-utils';
3
2
  import {
4
3
  escapeString,
@@ -12,6 +11,7 @@ import {
12
11
  needsSemicolon,
13
12
  shouldAddParenthesesToMemberExpressionObject,
14
13
  wouldRemoveComments,
14
+ isIdentifierName,
15
15
  } from './utils/index.js';
16
16
  import {removeStatement} from './fix/index.js';
17
17
  import {
@@ -21,7 +21,6 @@ import {
21
21
  isExpressionStatement,
22
22
  } from './ast/index.js';
23
23
 
24
- const {isIdentifierName} = helperValidatorIdentifier;
25
24
  const MESSAGE_ID = 'prefer-dataset';
26
25
  const INVERSE_MESSAGE_ID = 'prefer-attributes';
27
26
  const messages = {
@@ -106,11 +105,8 @@ function getDatasetVariableReadMembers(variable) {
106
105
 
107
106
  const {identifier} = reference;
108
107
  const member = identifier.parent;
109
- if (!(
110
- member.type === 'MemberExpression'
111
- && member.object === identifier
112
- && !member.optional
113
- )) {
108
+ if (!(member.type === 'MemberExpression'
109
+ && member.object === identifier) || member.optional) {
114
110
  return;
115
111
  }
116
112
 
@@ -24,14 +24,14 @@ const getProblem = ({node, original, regex = escapeCase, lowercase, fix}) => {
24
24
 
25
25
  /** @param {import('eslint').Rule.RuleContext} context */
26
26
  const create = context => {
27
- const lowercase = context.options[0] === 'lowercase';
27
+ const isLowercase = context.options[0] === 'lowercase';
28
28
 
29
29
  context.on('Literal', node => {
30
30
  if (isStringLiteral(node)) {
31
31
  return getProblem({
32
32
  node,
33
33
  original: node.raw,
34
- lowercase,
34
+ lowercase: isLowercase,
35
35
  });
36
36
  }
37
37
  });
@@ -42,7 +42,7 @@ const create = context => {
42
42
  node,
43
43
  original: node.raw,
44
44
  regex: escapePatternCase,
45
- lowercase,
45
+ lowercase: isLowercase,
46
46
  });
47
47
  }
48
48
  });
@@ -55,7 +55,7 @@ const create = context => {
55
55
  return getProblem({
56
56
  node,
57
57
  original: node.value.raw,
58
- lowercase,
58
+ lowercase: isLowercase,
59
59
  fix: (fixer, fixed) => replaceTemplateElement(node, fixed, context, fixer),
60
60
  });
61
61
  });
@@ -191,8 +191,8 @@ function getPackageHelpers(dirname) {
191
191
 
192
192
  // Check if have to skip colon
193
193
  // @example "TODO [...]: message here"
194
- const dropColon = afterArguments[0] === ':';
195
- if (dropColon) {
194
+ const isDropColon = afterArguments[0] === ':';
195
+ if (isDropColon) {
196
196
  return afterArguments.slice(1).trim();
197
197
  }
198
198
 
@@ -250,7 +250,7 @@ function create(context) {
250
250
  }
251
251
 
252
252
  let node;
253
- let autoFix = true;
253
+ let isAutoFix = true;
254
254
  let {isZeroLengthCheck, node: lengthCheckNode} = getLengthCheckNode(lengthNode);
255
255
  if (lengthCheckNode) {
256
256
  const {isNegative, node: ancestor} = getBooleanAncestor(lengthCheckNode, context);
@@ -266,7 +266,7 @@ function create(context) {
266
266
  } else if (isLogicalExpression(lengthNode.parent) && lengthNode.parent.operator === '&&') {
267
267
  isZeroLengthCheck = isNegative;
268
268
  node = lengthNode;
269
- autoFix = false;
269
+ isAutoFix = false;
270
270
  }
271
271
  }
272
272
 
@@ -287,7 +287,7 @@ function create(context) {
287
287
  node,
288
288
  isZeroLengthCheck,
289
289
  lengthNode,
290
- autoFix: autoFix && !isUnsafeNegationInBinaryExpression,
290
+ autoFix: isAutoFix && !isUnsafeNegationInBinaryExpression,
291
291
  shouldSuggest: !isUnsafeNegationInBinaryExpression,
292
292
  });
293
293
  }
@@ -280,8 +280,8 @@ const create = context => {
280
280
 
281
281
  return new RegExp(item, 'u');
282
282
  });
283
- const multipleFileExtensions = options.multipleFileExtensions !== false;
284
- const checkDirectories = options.checkDirectories !== false;
283
+ const isMultipleFileExtensions = options.multipleFileExtensions !== false;
284
+ const isCheckDirectories = options.checkDirectories !== false;
285
285
  const chosenCasesFunctions = chosenCases.map(case_ => cases[case_].fn);
286
286
 
287
287
  onRoot(context, () => {
@@ -292,13 +292,13 @@ const create = context => {
292
292
  filename,
293
293
  middle,
294
294
  extension,
295
- } = getFilenameParts(basenameWithExtension, {multipleFileExtensions});
295
+ } = getFilenameParts(basenameWithExtension, {multipleFileExtensions: isMultipleFileExtensions});
296
296
 
297
297
  if (pathSegments.some(segment => ignore.some(regexp => regexp.test(segment)))) {
298
298
  return;
299
299
  }
300
300
 
301
- if (checkDirectories) {
301
+ if (isCheckDirectories) {
302
302
  for (const directory of pathSegments.slice(0, -1)) {
303
303
  const report = getInvalidDirectoryReport(directory, chosenCases, chosenCasesFunctions);
304
304
 
@@ -1,5 +1,5 @@
1
1
  import {isCommaToken} from '@eslint-community/eslint-utils';
2
- import {getParentheses} from '../utils/index.js';
2
+ import {getParentheses, hasCommentInRange} from '../utils/index.js';
3
3
 
4
4
  /**
5
5
  @import {TSESTree as ESTree} from '@typescript-eslint/types';
@@ -22,17 +22,22 @@ function getArgumentRemovalRange(node, context) {
22
22
  let [start] = sourceCode.getRange(firstToken);
23
23
  let [, end] = sourceCode.getRange(lastToken);
24
24
 
25
- if (index !== 0) {
26
- const commaToken = sourceCode.getTokenBefore(firstToken);
27
- [start] = sourceCode.getRange(commaToken);
28
- }
29
-
30
- // If the removed argument is the only argument, the trailing comma must be removed too
31
25
  if (callOrNewExpression.arguments.length === 1) {
26
+ // The only argument: also drop a dangling trailing comma if present (`fn(a,)`).
32
27
  const tokenAfter = sourceCode.getTokenAfter(lastToken);
33
28
  if (isCommaToken(tokenAfter)) {
34
29
  [, end] = sourceCode.getRange(tokenAfter);
35
30
  }
31
+ } else if (index === 0) {
32
+ // First of several: remove it through the following comma and the gap after it, so
33
+ // `fn(a, b)` becomes `fn(b)` rather than `fn( b)`.
34
+ const commaToken = sourceCode.getTokenAfter(lastToken);
35
+ const tokenAfterComma = sourceCode.getTokenAfter(commaToken, {includeComments: true});
36
+ [end] = sourceCode.getRange(tokenAfterComma);
37
+ } else {
38
+ // Otherwise remove the comma that precedes it.
39
+ const commaToken = sourceCode.getTokenBefore(firstToken);
40
+ [start] = sourceCode.getRange(commaToken);
36
41
  }
37
42
 
38
43
  return [start, end];
@@ -45,7 +50,28 @@ function getArgumentRemovalRange(node, context) {
45
50
  @returns {ESLint.Rule.Fix}
46
51
  */
47
52
  export default function removeArgument(fixer, node, context) {
48
- return fixer.removeRange(getArgumentRemovalRange(node, context));
53
+ const removalRange = getArgumentRemovalRange(node, context);
54
+ const callOrNewExpression = node.parent;
55
+ const index = callOrNewExpression.arguments.indexOf(node);
56
+
57
+ if (
58
+ index === 0
59
+ && callOrNewExpression.arguments.length > 1
60
+ && hasCommentInRange(context, removalRange)
61
+ ) {
62
+ const {sourceCode} = context;
63
+ const parentheses = getParentheses(node, context);
64
+ const lastToken = parentheses.at(-1) || node;
65
+ const [, argumentEnd] = sourceCode.getRange(lastToken);
66
+ const commaToken = sourceCode.getTokenAfter(lastToken);
67
+ const [commaStart, commaEnd] = sourceCode.getRange(commaToken);
68
+ const rangeText = sourceCode.text.slice(...removalRange);
69
+ const replacement = rangeText.slice(argumentEnd - removalRange[0], commaStart - removalRange[0]) + rangeText.slice(commaEnd - removalRange[0]);
70
+
71
+ return fixer.replaceTextRange(removalRange, replacement);
72
+ }
73
+
74
+ return fixer.removeRange(removalRange);
49
75
  }
50
76
 
51
77
  export {
@@ -3,7 +3,7 @@ import {isSemicolonToken} from '@eslint-community/eslint-utils';
3
3
  const isWhitespaceOnly = text => /^\s*$/.test(text);
4
4
 
5
5
  // Removes a statement node along with its surrounding whitespace, while preserving comments.
6
- function removeStatement(statement, context, fixer, preserveSemiColon = false) {
6
+ export default function removeStatement(statement, context, fixer, preserveSemiColon = false) {
7
7
  const {sourceCode} = context;
8
8
  const {lines} = sourceCode;
9
9
  let endToken = statement;
@@ -44,5 +44,3 @@ function removeStatement(statement, context, fixer, preserveSemiColon = false) {
44
44
 
45
45
  return fixer.removeRange([start, end]);
46
46
  }
47
-
48
- export default removeStatement;
package/rules/id-match.js CHANGED
@@ -26,11 +26,11 @@ const shouldIgnoreNamedSpecifierReport = (problem, checkNamedSpecifiers) =>
26
26
  @param {import('eslint').Rule.RuleContext} context
27
27
  */
28
28
  const create = context => {
29
- const checkNamedSpecifiers = context.options[1]?.checkNamedSpecifiers !== false;
29
+ const isCheckNamedSpecifiers = context.options[1]?.checkNamedSpecifiers !== false;
30
30
  const fakeContext = Object.create(context, {
31
31
  report: {
32
32
  value(problem) {
33
- if (shouldIgnoreNamedSpecifierReport(problem, checkNamedSpecifiers)) {
33
+ if (shouldIgnoreNamedSpecifierReport(problem, isCheckNamedSpecifiers)) {
34
34
  return;
35
35
  }
36
36