eslint-plugin-unicorn 48.0.0 → 49.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 (40) hide show
  1. package/package.json +22 -21
  2. package/rules/ast/is-method-call.js +7 -3
  3. package/rules/ast/is-reference-identifier.js +10 -0
  4. package/rules/better-regex.js +2 -3
  5. package/rules/catch-error-name.js +1 -1
  6. package/rules/consistent-destructuring.js +2 -2
  7. package/rules/custom-error-definition.js +1 -1
  8. package/rules/expiring-todo-comments.js +134 -111
  9. package/rules/filename-case.js +1 -1
  10. package/rules/fix/remove-argument.js +1 -1
  11. package/rules/import-style.js +1 -1
  12. package/rules/no-array-for-each.js +1 -1
  13. package/rules/no-array-method-this-argument.js +1 -1
  14. package/rules/no-console-spaces.js +1 -1
  15. package/rules/no-empty-file.js +1 -1
  16. package/rules/no-hex-escape.js +1 -1
  17. package/rules/no-unnecessary-await.js +1 -1
  18. package/rules/no-useless-switch-case.js +1 -1
  19. package/rules/no-useless-undefined.js +1 -1
  20. package/rules/no-zero-fractions.js +1 -1
  21. package/rules/numeric-separators-style.js +1 -1
  22. package/rules/prefer-default-parameters.js +2 -2
  23. package/rules/prefer-dom-node-dataset.js +1 -1
  24. package/rules/prefer-event-target.js +77 -1
  25. package/rules/prefer-export-from.js +1 -1
  26. package/rules/prefer-object-from-entries.js +1 -1
  27. package/rules/prefer-query-selector.js +1 -1
  28. package/rules/prefer-regexp-test.js +4 -0
  29. package/rules/prefer-spread.js +1 -1
  30. package/rules/prefer-string-replace-all.js +3 -2
  31. package/rules/prefer-switch.js +1 -1
  32. package/rules/prevent-abbreviations.js +1 -1
  33. package/rules/template-indent.js +1 -1
  34. package/rules/utils/escape-template-element-raw.js +1 -1
  35. package/rules/utils/get-ancestor.js +20 -0
  36. package/rules/utils/index.js +1 -0
  37. package/rules/utils/is-number.js +1 -1
  38. package/rules/utils/is-shadowed.js +1 -1
  39. package/rules/utils/lodash.js +1589 -0
  40. package/rules/utils/parentheses.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "48.0.0",
3
+ "version": "49.0.0",
4
4
  "description": "More than 100 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/eslint-plugin-unicorn",
@@ -26,6 +26,7 @@
26
26
  "lint:md": "markdownlint \"**/*.md\"",
27
27
  "lint:package-json": "npmPkgJsonLint .",
28
28
  "run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs",
29
+ "bundle-lodash": "echo \"export {defaultsDeep, camelCase, kebabCase, snakeCase, upperFirst, lowerFirst} from 'lodash-es';\" | npx esbuild --bundle --outfile=rules/utils/lodash.js --format=cjs",
29
30
  "smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.js",
30
31
  "test": "npm-run-all --continue-on-error lint test:*",
31
32
  "test:js": "c8 ava"
@@ -46,7 +47,7 @@
46
47
  "xo"
47
48
  ],
48
49
  "dependencies": {
49
- "@babel/helper-validator-identifier": "^7.22.5",
50
+ "@babel/helper-validator-identifier": "^7.22.20",
50
51
  "@eslint-community/eslint-utils": "^4.4.0",
51
52
  "ci-info": "^3.8.0",
52
53
  "clean-regexp": "^1.0.0",
@@ -54,7 +55,6 @@
54
55
  "indent-string": "^4.0.0",
55
56
  "is-builtin-module": "^3.2.1",
56
57
  "jsesc": "^3.0.2",
57
- "lodash": "^4.17.21",
58
58
  "pluralize": "^8.0.0",
59
59
  "read-pkg-up": "^7.0.1",
60
60
  "regexp-tree": "^0.1.27",
@@ -63,37 +63,37 @@
63
63
  "strip-indent": "^3.0.0"
64
64
  },
65
65
  "devDependencies": {
66
- "@babel/code-frame": "^7.22.5",
67
- "@babel/core": "^7.22.8",
68
- "@babel/eslint-parser": "^7.22.7",
66
+ "@babel/code-frame": "^7.22.13",
67
+ "@babel/core": "^7.23.2",
68
+ "@babel/eslint-parser": "^7.22.15",
69
69
  "@lubien/fixture-beta-package": "^1.0.0-beta.1",
70
- "@typescript-eslint/parser": "^5.61.0",
70
+ "@typescript-eslint/parser": "^6.9.0",
71
71
  "ava": "^3.15.0",
72
- "c8": "^8.0.0",
72
+ "c8": "^8.0.1",
73
73
  "chalk": "^5.3.0",
74
- "enquirer": "^2.3.6",
75
- "eslint": "^8.44.0",
74
+ "enquirer": "^2.4.1",
75
+ "eslint": "^8.52.0",
76
76
  "eslint-ava-rule-tester": "^4.0.0",
77
- "eslint-doc-generator": "^1.4.3",
78
- "eslint-plugin-eslint-plugin": "^5.1.0",
77
+ "eslint-doc-generator": "^1.5.2",
78
+ "eslint-plugin-eslint-plugin": "^5.1.1",
79
79
  "eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
80
- "eslint-remote-tester": "^3.0.0",
80
+ "eslint-remote-tester": "^3.0.1",
81
81
  "eslint-remote-tester-repositories": "^1.0.1",
82
- "execa": "^7.1.1",
82
+ "execa": "^8.0.1",
83
83
  "listr": "^0.14.3",
84
84
  "lodash-es": "^4.17.21",
85
- "markdownlint-cli": "^0.35.0",
85
+ "markdownlint-cli": "^0.37.0",
86
86
  "mem": "^9.0.2",
87
87
  "npm-package-json-lint": "^7.0.0",
88
- "npm-run-all": "^4.1.5",
88
+ "npm-run-all2": "^6.1.1",
89
89
  "outdent": "^0.8.0",
90
- "typescript": "^5.1.6",
91
- "vue-eslint-parser": "^9.3.1",
92
- "xo": "^0.54.2",
93
- "yaml": "^2.3.1"
90
+ "typescript": "^5.2.2",
91
+ "vue-eslint-parser": "^9.3.2",
92
+ "xo": "^0.56.0",
93
+ "yaml": "^2.3.3"
94
94
  },
95
95
  "peerDependencies": {
96
- "eslint": ">=8.44.0"
96
+ "eslint": ">=8.52.0"
97
97
  },
98
98
  "ava": {
99
99
  "files": [
@@ -114,6 +114,7 @@
114
114
  "ignores": [
115
115
  ".cache-eslint-remote-tester",
116
116
  "eslint-remote-tester-results",
117
+ "rules/utils/lodash.js",
117
118
  "test/integration/{fixtures,fixtures-local}/**"
118
119
  ],
119
120
  "rules": {
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
- const {pick} = require('lodash');
3
2
  const isMemberExpression = require('./is-member-expression.js');
4
3
  const {isCallExpression} = require('./call-or-new-expression.js');
5
4
 
@@ -46,11 +45,16 @@ function isMethodCall(node, options) {
46
45
 
47
46
  return (
48
47
  isCallExpression(node, {
49
- ...pick(options, ['argumentsLength', 'minimumArguments', 'maximumArguments', 'allowSpreadElement']),
48
+ argumentsLength: options.argumentsLength,
49
+ minimumArguments: options.minimumArguments,
50
+ maximumArguments: options.maximumArguments,
51
+ allowSpreadElement: options.allowSpreadElement,
50
52
  optional: optionalCall,
51
53
  })
52
54
  && isMemberExpression(node.callee, {
53
- ...pick(options, ['object', 'objects', 'computed']),
55
+ object: options.object,
56
+ objects: options.objects,
57
+ computed: options.computed,
54
58
  property: method,
55
59
  properties: methods,
56
60
  optional: optionalMember,
@@ -115,6 +115,16 @@ function isNotReference(node) {
115
115
  return parent.id === node;
116
116
  }
117
117
 
118
+ // `type Foo = { [Identifier: string]: string }`
119
+ case 'TSIndexSignature': {
120
+ return parent.parameters.includes(node);
121
+ }
122
+
123
+ // `type Identifier = Foo`
124
+ case 'TSTypeAliasDeclaration': {
125
+ return parent.id === node;
126
+ }
127
+
118
128
  case 'TSPropertySignature': {
119
129
  return parent.key === node;
120
130
  }
@@ -28,10 +28,9 @@ const create = context => {
28
28
  }
29
29
 
30
30
  const {raw: original, regex} = node;
31
-
32
- // Regular Expressions with `u` flag are not well handled by `regexp-tree`
31
+ // Regular Expressions with `u` and `v` flag are not well handled by `regexp-tree`
33
32
  // https://github.com/DmitrySoshnikov/regexp-tree/issues/162
34
- if (regex.flags.includes('u')) {
33
+ if (regex.flags.includes('u') || regex.flags.includes('v')) {
35
34
  return;
36
35
  }
37
36
 
@@ -62,7 +62,7 @@ const create = context => {
62
62
 
63
63
  if (
64
64
  isNameAllowed(originalName)
65
- || isNameAllowed(originalName.replace(/_+$/g, ''))
65
+ || isNameAllowed(originalName.replaceAll(/_+$/g, ''))
66
66
  ) {
67
67
  return;
68
68
  }
@@ -89,7 +89,7 @@ const create = context => {
89
89
  && property.key.type === 'Identifier'
90
90
  && property.value.type === 'Identifier',
91
91
  );
92
- const lastProperty = objectPattern.properties[objectPattern.properties.length - 1];
92
+ const lastProperty = objectPattern.properties.at(-1);
93
93
 
94
94
  const hasRest = lastProperty && lastProperty.type === 'RestElement';
95
95
 
@@ -134,7 +134,7 @@ const create = context => {
134
134
  },
135
135
  * fix(fixer) {
136
136
  const {properties} = objectPattern;
137
- const lastProperty = properties[properties.length - 1];
137
+ const lastProperty = properties.at(-1);
138
138
 
139
139
  yield fixer.replaceText(node, newMember);
140
140
 
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- const {upperFirst} = require('lodash');
2
+ const {upperFirst} = require('./utils/lodash.js');
3
3
 
4
4
  const MESSAGE_ID_INVALID_EXPORT = 'invalidExport';
5
5
  const messages = {
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const path = require('node:path');
2
3
  const readPkgUp = require('read-pkg-up');
3
4
  const semver = require('semver');
4
5
  const ci = require('ci-info');
@@ -47,146 +48,160 @@ const messages = {
47
48
  'Unexpected \'{{matchedTerm}}\' comment without any conditions: \'{{comment}}\'.',
48
49
  };
49
50
 
50
- // We don't need to normalize the package.json data, because we are only using 2 properties and those 2 properties
51
- // aren't validated by the normalization. But when this plugin is used in a monorepo, the name field in the
52
- // package.json is invalid and would make this plugin throw an error. See also #1871
53
- const packageResult = readPkgUp.sync({normalize: false});
54
- const hasPackage = Boolean(packageResult);
55
- const packageJson = hasPackage ? packageResult.packageJson : {};
56
-
57
- const packageDependencies = {
58
- ...packageJson.dependencies,
59
- ...packageJson.devDependencies,
60
- };
61
-
62
- const DEPENDENCY_INCLUSION_RE = /^[+-]\s*@?\S+\/?\S+/;
63
- const VERSION_COMPARISON_RE = /^(?<name>@?\S\/?\S+)@(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)/i;
64
- const PKG_VERSION_RE = /^(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)\s*$/;
65
- const ISO8601_DATE = /\d{4}-\d{2}-\d{2}/;
66
-
67
- function parseTodoWithArguments(string, {terms}) {
68
- const lowerCaseString = string.toLowerCase();
69
- const lowerCaseTerms = terms.map(term => term.toLowerCase());
70
- const hasTerm = lowerCaseTerms.some(term => lowerCaseString.includes(term));
71
-
72
- if (!hasTerm) {
73
- return false;
51
+ /** @param {string} dirname */
52
+ function getPackageHelpers(dirname) {
53
+ // We don't need to normalize the package.json data, because we are only using 2 properties and those 2 properties
54
+ // aren't validated by the normalization. But when this plugin is used in a monorepo, the name field in the
55
+ // package.json can be invalid and would make this plugin throw an error. See also #1871
56
+ /** @type {readPkgUp.ReadResult | undefined} */
57
+ let packageResult;
58
+ try {
59
+ packageResult = readPkgUp.sync({normalize: false, cwd: dirname});
60
+ } catch {
61
+ // This can happen if package.json files have comments in them etc.
62
+ packageResult = undefined;
74
63
  }
75
64
 
76
- const TODO_ARGUMENT_RE = /\[(?<rawArguments>[^}]+)]/i;
77
- const result = TODO_ARGUMENT_RE.exec(string);
65
+ const hasPackage = Boolean(packageResult);
66
+ const packageJson = packageResult ? packageResult.packageJson : {};
78
67
 
79
- if (!result) {
80
- return false;
81
- }
68
+ const packageDependencies = {
69
+ ...packageJson.dependencies,
70
+ ...packageJson.devDependencies,
71
+ };
82
72
 
83
- const {rawArguments} = result.groups;
73
+ function parseTodoWithArguments(string, {terms}) {
74
+ const lowerCaseString = string.toLowerCase();
75
+ const lowerCaseTerms = terms.map(term => term.toLowerCase());
76
+ const hasTerm = lowerCaseTerms.some(term => lowerCaseString.includes(term));
84
77
 
85
- const parsedArguments = rawArguments
86
- .split(',')
87
- .map(argument => parseArgument(argument.trim()));
78
+ if (!hasTerm) {
79
+ return false;
80
+ }
88
81
 
89
- return createArgumentGroup(parsedArguments);
90
- }
82
+ const TODO_ARGUMENT_RE = /\[(?<rawArguments>[^}]+)]/i;
83
+ const result = TODO_ARGUMENT_RE.exec(string);
91
84
 
92
- function createArgumentGroup(arguments_) {
93
- const groups = {};
94
- for (const {value, type} of arguments_) {
95
- groups[type] = groups[type] || [];
96
- groups[type].push(value);
97
- }
85
+ if (!result) {
86
+ return false;
87
+ }
98
88
 
99
- return groups;
100
- }
89
+ const {rawArguments} = result.groups;
101
90
 
102
- function parseArgument(argumentString) {
103
- if (ISO8601_DATE.test(argumentString)) {
104
- return {
105
- type: 'dates',
106
- value: argumentString,
107
- };
108
- }
91
+ const parsedArguments = rawArguments
92
+ .split(',')
93
+ .map(argument => parseArgument(argument.trim()));
109
94
 
110
- if (hasPackage && DEPENDENCY_INCLUSION_RE.test(argumentString)) {
111
- const condition = argumentString[0] === '+' ? 'in' : 'out';
112
- const name = argumentString.slice(1).trim();
113
-
114
- return {
115
- type: 'dependencies',
116
- value: {
117
- name,
118
- condition,
119
- },
120
- };
95
+ return createArgumentGroup(parsedArguments);
121
96
  }
122
97
 
123
- if (hasPackage && VERSION_COMPARISON_RE.test(argumentString)) {
124
- const {groups} = VERSION_COMPARISON_RE.exec(argumentString);
125
- const name = groups.name.trim();
126
- const condition = groups.condition.trim();
127
- const version = groups.version.trim();
98
+ function parseArgument(argumentString, dirname) {
99
+ const {hasPackage} = getPackageHelpers(dirname);
100
+ if (ISO8601_DATE.test(argumentString)) {
101
+ return {
102
+ type: 'dates',
103
+ value: argumentString,
104
+ };
105
+ }
128
106
 
129
- const hasEngineKeyword = name.indexOf('engine:') === 0;
130
- const isNodeEngine = hasEngineKeyword && name === 'engine:node';
107
+ if (hasPackage && DEPENDENCY_INCLUSION_RE.test(argumentString)) {
108
+ const condition = argumentString[0] === '+' ? 'in' : 'out';
109
+ const name = argumentString.slice(1).trim();
131
110
 
132
- if (hasEngineKeyword && isNodeEngine) {
133
111
  return {
134
- type: 'engines',
112
+ type: 'dependencies',
135
113
  value: {
114
+ name,
136
115
  condition,
137
- version,
138
116
  },
139
117
  };
140
118
  }
141
119
 
142
- if (!hasEngineKeyword) {
120
+ if (hasPackage && VERSION_COMPARISON_RE.test(argumentString)) {
121
+ const {groups} = VERSION_COMPARISON_RE.exec(argumentString);
122
+ const name = groups.name.trim();
123
+ const condition = groups.condition.trim();
124
+ const version = groups.version.trim();
125
+
126
+ const hasEngineKeyword = name.indexOf('engine:') === 0;
127
+ const isNodeEngine = hasEngineKeyword && name === 'engine:node';
128
+
129
+ if (hasEngineKeyword && isNodeEngine) {
130
+ return {
131
+ type: 'engines',
132
+ value: {
133
+ condition,
134
+ version,
135
+ },
136
+ };
137
+ }
138
+
139
+ if (!hasEngineKeyword) {
140
+ return {
141
+ type: 'dependencies',
142
+ value: {
143
+ name,
144
+ condition,
145
+ version,
146
+ },
147
+ };
148
+ }
149
+ }
150
+
151
+ if (hasPackage && PKG_VERSION_RE.test(argumentString)) {
152
+ const result = PKG_VERSION_RE.exec(argumentString);
153
+ const {condition, version} = result.groups;
154
+
143
155
  return {
144
- type: 'dependencies',
156
+ type: 'packageVersions',
145
157
  value: {
146
- name,
147
- condition,
148
- version,
158
+ condition: condition.trim(),
159
+ version: version.trim(),
149
160
  },
150
161
  };
151
162
  }
152
- }
153
-
154
- if (hasPackage && PKG_VERSION_RE.test(argumentString)) {
155
- const result = PKG_VERSION_RE.exec(argumentString);
156
- const {condition, version} = result.groups;
157
163
 
164
+ // Currently being ignored as integration tests pointed
165
+ // some TODO comments have `[random data like this]`
158
166
  return {
159
- type: 'packageVersions',
160
- value: {
161
- condition: condition.trim(),
162
- version: version.trim(),
163
- },
167
+ type: 'unknowns',
168
+ value: argumentString,
164
169
  };
165
170
  }
166
171
 
167
- // Currently being ignored as integration tests pointed
168
- // some TODO comments have `[random data like this]`
169
- return {
170
- type: 'unknowns',
171
- value: argumentString,
172
- };
173
- }
172
+ function parseTodoMessage(todoString) {
173
+ // @example "TODO [...]: message here"
174
+ // @example "TODO [...] message here"
175
+ const argumentsEnd = todoString.indexOf(']');
176
+
177
+ const afterArguments = todoString.slice(argumentsEnd + 1).trim();
174
178
 
175
- function parseTodoMessage(todoString) {
176
- // @example "TODO [...]: message here"
177
- // @example "TODO [...] message here"
178
- const argumentsEnd = todoString.indexOf(']');
179
+ // Check if have to skip colon
180
+ // @example "TODO [...]: message here"
181
+ const dropColon = afterArguments[0] === ':';
182
+ if (dropColon) {
183
+ return afterArguments.slice(1).trim();
184
+ }
185
+
186
+ return afterArguments;
187
+ }
188
+
189
+ return {packageResult, hasPackage, packageJson, packageDependencies, parseArgument, parseTodoMessage, parseTodoWithArguments};
190
+ }
179
191
 
180
- const afterArguments = todoString.slice(argumentsEnd + 1).trim();
192
+ const DEPENDENCY_INCLUSION_RE = /^[+-]\s*@?\S+\/?\S+/;
193
+ const VERSION_COMPARISON_RE = /^(?<name>@?\S\/?\S+)@(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)/i;
194
+ const PKG_VERSION_RE = /^(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)\s*$/;
195
+ const ISO8601_DATE = /\d{4}-\d{2}-\d{2}/;
181
196
 
182
- // Check if have to skip colon
183
- // @example "TODO [...]: message here"
184
- const dropColon = afterArguments[0] === ':';
185
- if (dropColon) {
186
- return afterArguments.slice(1).trim();
197
+ function createArgumentGroup(arguments_) {
198
+ const groups = {};
199
+ for (const {value, type} of arguments_) {
200
+ groups[type] = groups[type] || [];
201
+ groups[type].push(value);
187
202
  }
188
203
 
189
- return afterArguments;
204
+ return groups;
190
205
  }
191
206
 
192
207
  function reachedDate(past, now) {
@@ -263,6 +278,9 @@ const create = context => {
263
278
  pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
264
279
  );
265
280
 
281
+ const dirname = path.dirname(context.filename);
282
+ const {packageJson, packageDependencies, parseArgument, parseTodoMessage, parseTodoWithArguments} = getPackageHelpers(dirname);
283
+
266
284
  const {sourceCode} = context;
267
285
  const comments = sourceCode.getAllComments();
268
286
  const unusedComments = comments
@@ -284,13 +302,18 @@ const create = context => {
284
302
  // This is highly dependable on ESLint's `no-warning-comments` implementation.
285
303
  // What we do is patch the parts we know the rule will use, `getAllComments`.
286
304
  // Since we have priority, we leave only the comments that we didn't use.
287
- const fakeContext = {
288
- ...context,
289
- sourceCode: {
290
- ...sourceCode,
291
- getAllComments: () => options.allowWarningComments ? [] : unusedComments,
305
+ const fakeContext = new Proxy(context, {
306
+ get(target, property, receiver) {
307
+ if (property === 'sourceCode') {
308
+ return {
309
+ ...sourceCode,
310
+ getAllComments: () => options.allowWarningComments ? [] : unusedComments,
311
+ };
312
+ }
313
+
314
+ return Reflect.get(target, property, receiver);
292
315
  },
293
- };
316
+ });
294
317
  const rules = baseRule.create(fakeContext);
295
318
 
296
319
  function processComment(comment) {
@@ -485,7 +508,7 @@ const create = context => {
485
508
  }
486
509
  }
487
510
 
488
- const withoutWhitespace = unknown.replace(/ /g, '');
511
+ const withoutWhitespace = unknown.replaceAll(' ', '');
489
512
 
490
513
  if (parseArgument(withoutWhitespace).type !== 'unknowns') {
491
514
  uses++;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  const path = require('node:path');
3
- const {camelCase, kebabCase, snakeCase, upperFirst} = require('lodash');
3
+ const {camelCase, kebabCase, snakeCase, upperFirst} = require('./utils/lodash.js');
4
4
  const cartesianProductSamples = require('./utils/cartesian-product-samples.js');
5
5
 
6
6
  const MESSAGE_ID = 'filename-case';
@@ -7,7 +7,7 @@ function removeArgument(fixer, node, sourceCode) {
7
7
  const index = callExpression.arguments.indexOf(node);
8
8
  const parentheses = getParentheses(node, sourceCode);
9
9
  const firstToken = parentheses[0] || node;
10
- const lastToken = parentheses[parentheses.length - 1] || node;
10
+ const lastToken = parentheses.at(-1) || node;
11
11
 
12
12
  let [start] = firstToken.range;
13
13
  let [, end] = lastToken.range;
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- const {defaultsDeep} = require('lodash');
2
+ const {defaultsDeep} = require('./utils/lodash.js');
3
3
  const {getStringIfConstant} = require('@eslint-community/eslint-utils');
4
4
  const {isCallExpression} = require('./ast/index.js');
5
5
 
@@ -400,7 +400,7 @@ const create = context => {
400
400
  });
401
401
 
402
402
  context.on('ReturnStatement', node => {
403
- const currentFunction = functionStack[functionStack.length - 1];
403
+ const currentFunction = functionStack.at(-1);
404
404
  if (!currentFunction) {
405
405
  return;
406
406
  }
@@ -83,7 +83,7 @@ function useBoundFunction(callExpression, sourceCode) {
83
83
  const callbackParentheses = getParentheses(callback, sourceCode);
84
84
  const isParenthesized = callbackParentheses.length > 0;
85
85
  const callbackLastToken = isParenthesized
86
- ? callbackParentheses[callbackParentheses.length - 1]
86
+ ? callbackParentheses.at(-1)
87
87
  : callback;
88
88
  if (
89
89
  !isParenthesized
@@ -11,7 +11,7 @@ const messages = {
11
11
  const hasLeadingSpace = value => value.length > 1 && value.charAt(0) === ' ' && value.charAt(1) !== ' ';
12
12
 
13
13
  // Find exactly one trailing space, allow exactly one space
14
- const hasTrailingSpace = value => value.length > 1 && value.charAt(value.length - 1) === ' ' && value.charAt(value.length - 2) !== ' ';
14
+ const hasTrailingSpace = value => value.length > 1 && value.at(-1) === ' ' && value.at(-2) !== ' ';
15
15
 
16
16
  /** @param {import('eslint').Rule.RuleContext} context */
17
17
  const create = context => {
@@ -6,7 +6,7 @@ const messages = {
6
6
  [MESSAGE_ID]: 'Empty files are not allowed.',
7
7
  };
8
8
 
9
- const isDirective = node => node.type === 'ExpressionStatement' && 'directive' in node;
9
+ const isDirective = node => node.type === 'ExpressionStatement' && typeof node.directive === 'string';
10
10
  const isEmpty = node => isEmptyNode(node, isDirective);
11
11
 
12
12
  const isTripleSlashDirective = node =>
@@ -8,7 +8,7 @@ const messages = {
8
8
  };
9
9
 
10
10
  function checkEscape(context, node, value) {
11
- const fixedValue = value.replace(/(?<=(?:^|[^\\])(?:\\\\)*\\)x/g, 'u00');
11
+ const fixedValue = value.replaceAll(/(?<=(?:^|[^\\])(?:\\\\)*\\)x/g, 'u00');
12
12
 
13
13
  if (value !== fixedValue) {
14
14
  return {
@@ -30,7 +30,7 @@ function notPromise(node) {
30
30
  }
31
31
 
32
32
  case 'SequenceExpression': {
33
- return notPromise(node.expressions[node.expressions.length - 1]);
33
+ return notPromise(node.expressions.at(-1));
34
34
  }
35
35
 
36
36
  // No default
@@ -25,7 +25,7 @@ const create = context => ({
25
25
  const [defaultCase] = defaultCases;
26
26
 
27
27
  // We only check cases where the last case is the `default` case
28
- if (defaultCase !== cases[cases.length - 1]) {
28
+ if (defaultCase !== cases.at(-1)) {
29
29
  return;
30
30
  }
31
31
 
@@ -236,7 +236,7 @@ const create = context => {
236
236
  }
237
237
 
238
238
  const firstUndefined = undefinedArguments[0];
239
- const lastUndefined = undefinedArguments[undefinedArguments.length - 1];
239
+ const lastUndefined = undefinedArguments.at(-1);
240
240
 
241
241
  return {
242
242
  messageId,
@@ -28,7 +28,7 @@ const create = context => ({
28
28
  }
29
29
 
30
30
  const {before, dotAndFractions, after} = match.groups;
31
- const fixedDotAndFractions = dotAndFractions.replace(/[.0_]+$/g, '');
31
+ const fixedDotAndFractions = dotAndFractions.replaceAll(/[.0_]+$/g, '');
32
32
  const formatted = ((before + fixedDotAndFractions) || '0') + after;
33
33
 
34
34
  if (formatted === raw) {
@@ -111,7 +111,7 @@ const create = context => {
111
111
  suffix = 'n';
112
112
  }
113
113
 
114
- const strippedNumber = number.replace(/_/g, '');
114
+ const strippedNumber = number.replaceAll('_', '');
115
115
  const {prefix, data} = numeric.getPrefix(strippedNumber);
116
116
 
117
117
  const {onlyIfContainsSeparator} = options[prefix.toLowerCase()];
@@ -72,7 +72,7 @@ const hasExtraReferences = (assignment, references, left) => {
72
72
  };
73
73
 
74
74
  const isLastParameter = (parameters, parameter) => {
75
- const lastParameter = parameters[parameters.length - 1];
75
+ const lastParameter = parameters.at(-1);
76
76
 
77
77
  // See 'default-param-last' rule
78
78
  return parameter && parameter === lastParameter;
@@ -122,7 +122,7 @@ const create = context => {
122
122
  const functionStack = [];
123
123
 
124
124
  const checkExpression = (node, left, right, assignment) => {
125
- const currentFunction = functionStack[functionStack.length - 1];
125
+ const currentFunction = functionStack.at(-1);
126
126
 
127
127
  if (!currentFunction || !isDefaultExpression(left, right)) {
128
128
  return;