eslint-plugin-unicorn 58.0.0 β†’ 59.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.
package/index.js CHANGED
@@ -9,6 +9,14 @@ const deprecatedRules = createDeprecatedRules({
9
9
  message: 'Replaced by `unicorn/no-instanceof-builtins` which covers more cases.',
10
10
  replacedBy: ['unicorn/no-instanceof-builtins'],
11
11
  },
12
+ 'no-length-as-slice-end': {
13
+ message: 'Replaced by `unicorn/no-unnecessary-slice-end` which covers more cases.',
14
+ replacedBy: ['unicorn/no-unnecessary-slice-end'],
15
+ },
16
+ 'no-array-push-push': {
17
+ message: 'Replaced by `unicorn/prefer-single-call` which covers more cases.',
18
+ replacedBy: ['unicorn/prefer-single-call'],
19
+ },
12
20
  });
13
21
 
14
22
  const externalRules = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "58.0.0",
3
+ "version": "59.0.0",
4
4
  "description": "More than 100 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/eslint-plugin-unicorn",
@@ -64,12 +64,12 @@
64
64
  "clean-regexp": "^1.0.0",
65
65
  "core-js-compat": "^3.41.0",
66
66
  "esquery": "^1.6.0",
67
+ "find-up-simple": "^1.0.1",
67
68
  "globals": "^16.0.0",
68
69
  "indent-string": "^5.0.0",
69
70
  "is-builtin-module": "^5.0.0",
70
71
  "jsesc": "^3.1.0",
71
72
  "pluralize": "^8.0.0",
72
- "read-package-up": "^11.0.0",
73
73
  "regexp-tree": "^0.1.27",
74
74
  "regjsparser": "^0.12.0",
75
75
  "semver": "^7.7.1",
package/readme.md CHANGED
@@ -79,7 +79,6 @@ export default [
79
79
  | [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. | βœ… | | πŸ’‘ |
80
80
  | [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `for…of` over the `forEach` method. | βœ… | πŸ”§ | πŸ’‘ |
81
81
  | [no-array-method-this-argument](docs/rules/no-array-method-this-argument.md) | Disallow using the `this` argument in array methods. | βœ… | πŸ”§ | πŸ’‘ |
82
- | [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. | βœ… | πŸ”§ | πŸ’‘ |
83
82
  | [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. | βœ… | | |
84
83
  | [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. | βœ… | πŸ”§ | |
85
84
  | [no-await-in-promise-methods](docs/rules/no-await-in-promise-methods.md) | Disallow using `await` in `Promise` method parameters. | βœ… | | πŸ’‘ |
@@ -92,7 +91,6 @@ export default [
92
91
  | [no-invalid-fetch-options](docs/rules/no-invalid-fetch-options.md) | Disallow invalid options in `fetch()` and `new Request()`. | βœ… | | |
93
92
  | [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. | βœ… | | |
94
93
  | [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | | |
95
- | [no-length-as-slice-end](docs/rules/no-length-as-slice-end.md) | Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()`. | βœ… | πŸ”§ | |
96
94
  | [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. | βœ… | πŸ”§ | |
97
95
  | [no-magic-array-flat-depth](docs/rules/no-magic-array-flat-depth.md) | Disallow a magic number as the `depth` argument in `Array#flat(…).` | βœ… | | |
98
96
  | [no-named-default](docs/rules/no-named-default.md) | Disallow named usage of default import and export. | βœ… | πŸ”§ | |
@@ -109,8 +107,11 @@ export default [
109
107
  | [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | βœ… | | |
110
108
  | [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | βœ… | | |
111
109
  | [no-typeof-undefined](docs/rules/no-typeof-undefined.md) | Disallow comparing `undefined` using `typeof`. | βœ… | πŸ”§ | πŸ’‘ |
110
+ | [no-unnecessary-array-flat-depth](docs/rules/no-unnecessary-array-flat-depth.md) | Disallow using `1` as the `depth` argument of `Array#flat()`. | βœ… | πŸ”§ | |
111
+ | [no-unnecessary-array-splice-count](docs/rules/no-unnecessary-array-splice-count.md) | Disallow using `.length` or `Infinity` as the `deleteCount` or `skipCount` argument of `Array#{splice,toSpliced}()`. | βœ… | πŸ”§ | |
112
112
  | [no-unnecessary-await](docs/rules/no-unnecessary-await.md) | Disallow awaiting non-promise values. | βœ… | πŸ”§ | |
113
113
  | [no-unnecessary-polyfills](docs/rules/no-unnecessary-polyfills.md) | Enforce the use of built-in methods instead of unnecessary polyfills. | βœ… | | |
114
+ | [no-unnecessary-slice-end](docs/rules/no-unnecessary-slice-end.md) | Disallow using `.length` or `Infinity` as the `end` argument of `{Array,String,TypedArray}#slice()`. | βœ… | πŸ”§ | |
114
115
  | [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | βœ… | πŸ”§ | |
115
116
  | [no-unreadable-iife](docs/rules/no-unreadable-iife.md) | Disallow unreadable IIFEs. | βœ… | | |
116
117
  | [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | |
@@ -141,6 +142,7 @@ export default [
141
142
  | [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | βœ… | | |
142
143
  | [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | βœ… | πŸ”§ | πŸ’‘ |
143
144
  | [prefer-global-this](docs/rules/prefer-global-this.md) | Prefer `globalThis` over `window`, `self`, and `global`. | βœ… | πŸ”§ | |
145
+ | [prefer-import-meta-properties](docs/rules/prefer-import-meta-properties.md) | Prefer `import.meta.{dirname,filename}` over legacy techniques for getting file paths. | | πŸ”§ | |
144
146
  | [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ |
145
147
  | [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | πŸ”§ | |
146
148
  | [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | βœ… | πŸ”§ | |
@@ -162,6 +164,7 @@ export default [
162
164
  | [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. | βœ… | πŸ”§ | πŸ’‘ |
163
165
  | [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ |
164
166
  | [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. | βœ… | πŸ”§ | |
167
+ | [prefer-single-call](docs/rules/prefer-single-call.md) | Enforce combining multiple `Array#push()`, `Element#classList.{add,remove}()`, and `importScripts()` into one call. | βœ… | πŸ”§ | πŸ’‘ |
165
168
  | [prefer-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split('')`. | βœ… | πŸ”§ | πŸ’‘ |
166
169
  | [prefer-string-raw](docs/rules/prefer-string-raw.md) | Prefer using the `String.raw` tag to avoid escaping `\`. | βœ… | πŸ”§ | |
167
170
  | [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) | Prefer `String#replaceAll()` over regex searches with the global flag. | βœ… | πŸ”§ | |
@@ -186,9 +189,9 @@ export default [
186
189
 
187
190
  <!-- end auto-generated rules list -->
188
191
 
189
- ### Deprecated Rules
192
+ ### Deleted and deprecated rules
190
193
 
191
- See [docs/deprecated-rules.md](docs/deprecated-rules.md)
194
+ See [the list](docs/deleted-and-deprecated-rules.md).
192
195
 
193
196
  ## Preset configs
194
197
 
@@ -7,10 +7,7 @@ const messages = {
7
7
 
8
8
  const getProblem = (node, context) => {
9
9
  const {sourceCode} = context;
10
- const filter = node.type === 'RecordExpression'
11
- ? token => token.type === 'Punctuator' && (token.value === '#{' || token.value === '{|')
12
- : isOpeningBraceToken;
13
- const openingBrace = sourceCode.getFirstToken(node, {filter});
10
+ const openingBrace = sourceCode.getFirstToken(node, {filter: isOpeningBraceToken});
14
11
  const closingBrace = sourceCode.getLastToken(node);
15
12
  const [, start] = sourceCode.getRange(openingBrace);
16
13
  const [end] = sourceCode.getRange(closingBrace);
@@ -36,20 +33,11 @@ const create = context => {
36
33
  'BlockStatement',
37
34
  'ClassBody',
38
35
  'StaticBlock',
39
- ], node => {
40
- if (node.body.length > 0) {
41
- return;
42
- }
43
-
44
- return getProblem(node, context);
45
- });
46
-
47
- context.on([
48
36
  'ObjectExpression',
49
- // Experimental https://github.com/tc39/proposal-record-tuple
50
- 'RecordExpression',
51
37
  ], node => {
52
- if (node.properties.length > 0) {
38
+ const children = node.type === 'ObjectExpression' ? node.properties : node.body;
39
+
40
+ if (children.length > 0) {
53
41
  return;
54
42
  }
55
43
 
@@ -1,9 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import {isRegExp} from 'node:util/types';
3
- import {readPackageUpSync} from 'read-package-up';
4
3
  import semver from 'semver';
5
4
  import * as ci from 'ci-info';
6
5
  import getBuiltinRule from './utils/get-builtin-rule.js';
6
+ import {readPackageJson} from './shared/package-json.js';
7
7
 
8
8
  const baseRule = getBuiltinRule('no-warning-comments');
9
9
 
@@ -50,20 +50,9 @@ const messages = {
50
50
 
51
51
  /** @param {string} dirname */
52
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 = readPackageUpSync({normalize: false, cwd: dirname});
60
- } catch {
61
- // This can happen if package.json files have comments in them etc.
62
- packageResult = undefined;
63
- }
64
-
65
- const hasPackage = Boolean(packageResult);
66
- const packageJson = packageResult ? packageResult.packageJson : {};
53
+ const packageJsonResult = readPackageJson(dirname);
54
+ const packageJson = packageJsonResult?.packageJson ?? {};
55
+ const hasPackage = Boolean(packageJsonResult);
67
56
 
68
57
  const packageDependencies = {
69
58
  ...packageJson.dependencies,
@@ -187,7 +176,7 @@ function getPackageHelpers(dirname) {
187
176
  }
188
177
 
189
178
  return {
190
- packageResult,
179
+ packageResult: packageJsonResult,
191
180
  hasPackage,
192
181
  packageJson,
193
182
  packageDependencies,
package/rules/index.js CHANGED
@@ -24,7 +24,6 @@ import noAnonymousDefaultExport from './no-anonymous-default-export.js';
24
24
  import noArrayCallbackReference from './no-array-callback-reference.js';
25
25
  import noArrayForEach from './no-array-for-each.js';
26
26
  import noArrayMethodThisArgument from './no-array-method-this-argument.js';
27
- import noArrayPushPush from './no-array-push-push.js';
28
27
  import noArrayReduce from './no-array-reduce.js';
29
28
  import noAwaitExpressionMember from './no-await-expression-member.js';
30
29
  import noAwaitInPromiseMethods from './no-await-in-promise-methods.js';
@@ -37,7 +36,6 @@ import noInstanceofBuiltins from './no-instanceof-builtins.js';
37
36
  import noInvalidFetchOptions from './no-invalid-fetch-options.js';
38
37
  import noInvalidRemoveEventListener from './no-invalid-remove-event-listener.js';
39
38
  import noKeywordPrefix from './no-keyword-prefix.js';
40
- import noLengthAsSliceEnd from './no-length-as-slice-end.js';
41
39
  import noLonelyIf from './no-lonely-if.js';
42
40
  import noMagicArrayFlatDepth from './no-magic-array-flat-depth.js';
43
41
  import noNamedDefault from './no-named-default.js';
@@ -54,8 +52,11 @@ import noStaticOnlyClass from './no-static-only-class.js';
54
52
  import noThenable from './no-thenable.js';
55
53
  import noThisAssignment from './no-this-assignment.js';
56
54
  import noTypeofUndefined from './no-typeof-undefined.js';
55
+ import noUnnecessaryArrayFlatDepth from './no-unnecessary-array-flat-depth.js';
56
+ import noUnnecessaryArraySpliceCount from './no-unnecessary-array-splice-count.js';
57
57
  import noUnnecessaryAwait from './no-unnecessary-await.js';
58
58
  import noUnnecessaryPolyfills from './no-unnecessary-polyfills.js';
59
+ import noUnnecessarySliceEnd from './no-unnecessary-slice-end.js';
59
60
  import noUnreadableArrayDestructuring from './no-unreadable-array-destructuring.js';
60
61
  import noUnreadableIife from './no-unreadable-iife.js';
61
62
  import noUnusedProperties from './no-unused-properties.js';
@@ -86,6 +87,7 @@ import preferDomNodeTextContent from './prefer-dom-node-text-content.js';
86
87
  import preferEventTarget from './prefer-event-target.js';
87
88
  import preferExportFrom from './prefer-export-from.js';
88
89
  import preferGlobalThis from './prefer-global-this.js';
90
+ import preferImportMetaProperties from './prefer-import-meta-properties.js';
89
91
  import preferIncludes from './prefer-includes.js';
90
92
  import preferJsonParseBuffer from './prefer-json-parse-buffer.js';
91
93
  import preferKeyboardEventKey from './prefer-keyboard-event-key.js';
@@ -107,6 +109,7 @@ import preferReflectApply from './prefer-reflect-apply.js';
107
109
  import preferRegexpTest from './prefer-regexp-test.js';
108
110
  import preferSetHas from './prefer-set-has.js';
109
111
  import preferSetSize from './prefer-set-size.js';
112
+ import preferSingleCall from './prefer-single-call.js';
110
113
  import preferSpread from './prefer-spread.js';
111
114
  import preferStringRaw from './prefer-string-raw.js';
112
115
  import preferStringReplaceAll from './prefer-string-replace-all.js';
@@ -153,7 +156,6 @@ const rules = {
153
156
  'no-array-callback-reference': createRule(noArrayCallbackReference, 'no-array-callback-reference'),
154
157
  'no-array-for-each': createRule(noArrayForEach, 'no-array-for-each'),
155
158
  'no-array-method-this-argument': createRule(noArrayMethodThisArgument, 'no-array-method-this-argument'),
156
- 'no-array-push-push': createRule(noArrayPushPush, 'no-array-push-push'),
157
159
  'no-array-reduce': createRule(noArrayReduce, 'no-array-reduce'),
158
160
  'no-await-expression-member': createRule(noAwaitExpressionMember, 'no-await-expression-member'),
159
161
  'no-await-in-promise-methods': createRule(noAwaitInPromiseMethods, 'no-await-in-promise-methods'),
@@ -166,7 +168,6 @@ const rules = {
166
168
  'no-invalid-fetch-options': createRule(noInvalidFetchOptions, 'no-invalid-fetch-options'),
167
169
  'no-invalid-remove-event-listener': createRule(noInvalidRemoveEventListener, 'no-invalid-remove-event-listener'),
168
170
  'no-keyword-prefix': createRule(noKeywordPrefix, 'no-keyword-prefix'),
169
- 'no-length-as-slice-end': createRule(noLengthAsSliceEnd, 'no-length-as-slice-end'),
170
171
  'no-lonely-if': createRule(noLonelyIf, 'no-lonely-if'),
171
172
  'no-magic-array-flat-depth': createRule(noMagicArrayFlatDepth, 'no-magic-array-flat-depth'),
172
173
  'no-named-default': createRule(noNamedDefault, 'no-named-default'),
@@ -183,8 +184,11 @@ const rules = {
183
184
  'no-thenable': createRule(noThenable, 'no-thenable'),
184
185
  'no-this-assignment': createRule(noThisAssignment, 'no-this-assignment'),
185
186
  'no-typeof-undefined': createRule(noTypeofUndefined, 'no-typeof-undefined'),
187
+ 'no-unnecessary-array-flat-depth': createRule(noUnnecessaryArrayFlatDepth, 'no-unnecessary-array-flat-depth'),
188
+ 'no-unnecessary-array-splice-count': createRule(noUnnecessaryArraySpliceCount, 'no-unnecessary-array-splice-count'),
186
189
  'no-unnecessary-await': createRule(noUnnecessaryAwait, 'no-unnecessary-await'),
187
190
  'no-unnecessary-polyfills': createRule(noUnnecessaryPolyfills, 'no-unnecessary-polyfills'),
191
+ 'no-unnecessary-slice-end': createRule(noUnnecessarySliceEnd, 'no-unnecessary-slice-end'),
188
192
  'no-unreadable-array-destructuring': createRule(noUnreadableArrayDestructuring, 'no-unreadable-array-destructuring'),
189
193
  'no-unreadable-iife': createRule(noUnreadableIife, 'no-unreadable-iife'),
190
194
  'no-unused-properties': createRule(noUnusedProperties, 'no-unused-properties'),
@@ -215,6 +219,7 @@ const rules = {
215
219
  'prefer-event-target': createRule(preferEventTarget, 'prefer-event-target'),
216
220
  'prefer-export-from': createRule(preferExportFrom, 'prefer-export-from'),
217
221
  'prefer-global-this': createRule(preferGlobalThis, 'prefer-global-this'),
222
+ 'prefer-import-meta-properties': createRule(preferImportMetaProperties, 'prefer-import-meta-properties'),
218
223
  'prefer-includes': createRule(preferIncludes, 'prefer-includes'),
219
224
  'prefer-json-parse-buffer': createRule(preferJsonParseBuffer, 'prefer-json-parse-buffer'),
220
225
  'prefer-keyboard-event-key': createRule(preferKeyboardEventKey, 'prefer-keyboard-event-key'),
@@ -236,6 +241,7 @@ const rules = {
236
241
  'prefer-regexp-test': createRule(preferRegexpTest, 'prefer-regexp-test'),
237
242
  'prefer-set-has': createRule(preferSetHas, 'prefer-set-has'),
238
243
  'prefer-set-size': createRule(preferSetSize, 'prefer-set-size'),
244
+ 'prefer-single-call': createRule(preferSingleCall, 'prefer-single-call'),
239
245
  'prefer-spread': createRule(preferSpread, 'prefer-spread'),
240
246
  'prefer-string-raw': createRule(preferStringRaw, 'prefer-string-raw'),
241
247
  'prefer-string-replace-all': createRule(preferStringReplaceAll, 'prefer-string-replace-all'),
@@ -0,0 +1,50 @@
1
+ import {isMethodCall, isLiteral} from './ast/index.js';
2
+ import {removeArgument} from './fix/index.js';
3
+ import {} from './utils/index.js';
4
+
5
+ const MESSAGE_ID = 'no-unnecessary-array-flat-depth';
6
+ const messages = {
7
+ [MESSAGE_ID]: 'Passing `1` as the `depth` argument is unnecessary.',
8
+ };
9
+
10
+ /** @param {import('eslint').Rule.RuleContext} context */
11
+ const create = context => {
12
+ context.on('CallExpression', callExpression => {
13
+ if (!(
14
+ isMethodCall(callExpression, {
15
+ method: 'flat',
16
+ argumentsLength: 1,
17
+ optionalCall: false,
18
+ })
19
+ && isLiteral(callExpression.arguments[0], 1)
20
+ )) {
21
+ return;
22
+ }
23
+
24
+ const [numberOne] = callExpression.arguments;
25
+
26
+ return {
27
+ node: numberOne,
28
+ messageId: MESSAGE_ID,
29
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
30
+ fix: fixer => removeArgument(fixer, numberOne, context.sourceCode),
31
+ };
32
+ });
33
+ };
34
+
35
+ /** @type {import('eslint').Rule.RuleModule} */
36
+ const config = {
37
+ create,
38
+ meta: {
39
+ type: 'suggestion',
40
+ docs: {
41
+ description: 'Disallow using `1` as the `depth` argument of `Array#flat()`.',
42
+ recommended: true,
43
+ },
44
+ fixable: 'code',
45
+
46
+ messages,
47
+ },
48
+ };
49
+
50
+ export default config;
@@ -0,0 +1,23 @@
1
+ import {listen} from './shared/no-unnecessary-length-or-infinity-rule.js';
2
+
3
+ const MESSAGE_ID = 'no-unnecessary-array-splice-count';
4
+ const messages = {
5
+ [MESSAGE_ID]: 'Passing `{{description}}` as the `{{argumentName}}` argument is unnecessary.',
6
+ };
7
+
8
+ /** @type {import('eslint').Rule.RuleModule} */
9
+ const config = {
10
+ create: context => listen(context, {methods: ['splice', 'toSpliced'], messageId: MESSAGE_ID}),
11
+ meta: {
12
+ type: 'suggestion',
13
+ docs: {
14
+ description: 'Disallow using `.length` or `Infinity` as the `deleteCount` or `skipCount` argument of `Array#{splice,toSpliced}()`.',
15
+ recommended: true,
16
+ },
17
+ fixable: 'code',
18
+
19
+ messages,
20
+ },
21
+ };
22
+
23
+ export default config;
@@ -1,8 +1,8 @@
1
1
  import path from 'node:path';
2
- import {readPackageUpSync} from 'read-package-up';
3
2
  import coreJsCompat from 'core-js-compat';
4
3
  import {camelCase} from './utils/lodash.js';
5
4
  import isStaticRequire from './ast/is-static-require.js';
5
+ import {readPackageJson} from './shared/package-json.js';
6
6
 
7
7
  const {data: compatData, entries: coreJsEntries} = coreJsCompat;
8
8
 
@@ -57,18 +57,13 @@ function getTargets(options, dirname) {
57
57
  return options.targets;
58
58
  }
59
59
 
60
- /** @type {readPkgUp.ReadResult | undefined} */
61
- let packageResult;
62
- try {
63
- // It can fail if, for example, the package.json file has comments.
64
- packageResult = readPackageUpSync({normalize: false, cwd: dirname});
65
- } catch {}
60
+ const packageJsonResult = readPackageJson(dirname);
66
61
 
67
- if (!packageResult) {
62
+ if (!packageJsonResult) {
68
63
  return;
69
64
  }
70
65
 
71
- const {browserslist, engines} = packageResult.packageJson;
66
+ const {browserslist, engines} = packageJsonResult.packageJson;
72
67
  return browserslist ?? engines;
73
68
  }
74
69
 
@@ -0,0 +1,22 @@
1
+ import {listen} from './shared/no-unnecessary-length-or-infinity-rule.js';
2
+
3
+ const MESSAGE_ID = 'no-unnecessary-slice-end';
4
+ const messages = {
5
+ [MESSAGE_ID]: 'Passing `{{description}}` as the `end` argument is unnecessary.',
6
+ };
7
+
8
+ /** @type {import('eslint').Rule.RuleModule} */
9
+ const config = {
10
+ create: context => listen(context, {methods: ['slice'], messageId: MESSAGE_ID}),
11
+ meta: {
12
+ type: 'suggestion',
13
+ docs: {
14
+ description: 'Disallow using `.length` or `Infinity` as the `end` argument of `{Array,String,TypedArray}#slice()`.',
15
+ recommended: true,
16
+ },
17
+ fixable: 'code',
18
+ messages,
19
+ },
20
+ };
21
+
22
+ export default config;
@@ -0,0 +1,320 @@
1
+ import {findVariable} from '@eslint-community/eslint-utils';
2
+ import {
3
+ isMemberExpression,
4
+ isCallExpression,
5
+ isNewExpression,
6
+ isMethodCall,
7
+ } from './ast/index.js';
8
+
9
+ const ERROR_DIRNAME = 'error/calculate-dirname';
10
+ const ERROR_FILENAME = 'error/calculate-filename';
11
+ const messages = {
12
+ [ERROR_DIRNAME]: 'Do not construct dirname.',
13
+ [ERROR_FILENAME]: 'Do not construct filename using `fileURLToPath()`.',
14
+ };
15
+
16
+ const isParentLiteral = node => {
17
+ if (node?.type !== 'Literal') {
18
+ return false;
19
+ }
20
+
21
+ return node.value === '.' || node.value === './';
22
+ };
23
+
24
+ const isImportMeta = node =>
25
+ node.type === 'MetaProperty'
26
+ && node.meta.name === 'import'
27
+ && node.property.name === 'meta';
28
+
29
+ function isNodeBuiltinModuleFunctionCall(node, {modules, functionName, sourceCode}) {
30
+ if (!isCallExpression(node, {optional: false, argumentsLength: 1})) {
31
+ return false;
32
+ }
33
+
34
+ const visited = new Set();
35
+
36
+ return checkExpression(node.callee, 'property');
37
+
38
+ /** @param {import('estree').Expression} node */
39
+ function checkExpression(node, checkKind) {
40
+ if (node.type === 'MemberExpression') {
41
+ if (!(
42
+ checkKind === 'property'
43
+ && isMemberExpression(node, {property: functionName, computed: false, optional: false})
44
+ )) {
45
+ return false;
46
+ }
47
+
48
+ return checkExpression(node.object, 'module');
49
+ }
50
+
51
+ if (node.type === 'CallExpression') {
52
+ if (checkKind !== 'module') {
53
+ return false;
54
+ }
55
+
56
+ // `process.getBuiltinModule('x')`
57
+ return (
58
+ isMethodCall(node, {
59
+ object: 'process',
60
+ method: 'getBuiltinModule',
61
+ argumentsLength: 1,
62
+ optionalMember: false,
63
+ optionalCall: false,
64
+ })
65
+ && isModuleLiteral(node.arguments[0])
66
+ );
67
+ }
68
+
69
+ if (node.type !== 'Identifier') {
70
+ return false;
71
+ }
72
+
73
+ if (visited.has(node)) {
74
+ return false;
75
+ }
76
+
77
+ visited.add(node);
78
+
79
+ const variable = findVariable(sourceCode.getScope(node), node);
80
+ if (!variable || variable.defs.length !== 1) {
81
+ return;
82
+ }
83
+
84
+ return checkDefinition(variable.defs[0], checkKind);
85
+ }
86
+
87
+ /** @param {import('eslint').Scope.Definition} define */
88
+ function checkDefinition(define, checkKind) {
89
+ if (define.type === 'ImportBinding') {
90
+ if (!isModuleLiteral(define.parent.source)) {
91
+ return false;
92
+ }
93
+
94
+ const specifier = define.node;
95
+ return checkKind === 'module'
96
+ ? (specifier?.type === 'ImportDefaultSpecifier' || specifier?.type === 'ImportNamespaceSpecifier')
97
+ : (specifier?.type === 'ImportSpecifier' && specifier.imported.name === functionName);
98
+ }
99
+
100
+ return define.type === 'Variable' && checkPattern(define.name, checkKind);
101
+ }
102
+
103
+ /** @param {import('estree').Identifier | import('estree').ObjectPattern} node */
104
+ function checkPattern(node, checkKind) {
105
+ /** @type {{parent?: import('estree').Node}} */
106
+ const {parent} = node;
107
+ if (parent.type === 'VariableDeclarator') {
108
+ if (
109
+ !parent.init
110
+ || parent.id !== node
111
+ || parent.parent.type !== 'VariableDeclaration'
112
+ || parent.parent.kind !== 'const'
113
+ ) {
114
+ return false;
115
+ }
116
+
117
+ return checkExpression(parent.init, checkKind);
118
+ }
119
+
120
+ if (parent.type === 'Property') {
121
+ if (!(
122
+ checkKind === 'property'
123
+ && parent.value === node
124
+ && !parent.computed
125
+ && parent.key.type === 'Identifier'
126
+ && parent.key.name === functionName
127
+ )) {
128
+ return false;
129
+ }
130
+
131
+ // Check for ObjectPattern
132
+ return checkPattern(parent.parent, 'module');
133
+ }
134
+
135
+ return false;
136
+ }
137
+
138
+ function isModuleLiteral(node) {
139
+ return node?.type === 'Literal' && modules.has(node.value);
140
+ }
141
+ }
142
+
143
+ /**
144
+ @returns {node is import('estree').SimpleCallExpression}
145
+ */
146
+ function isUrlFileURLToPathCall(node, sourceCode) {
147
+ return isNodeBuiltinModuleFunctionCall(node, {
148
+ modules: new Set(['url', 'node:url']),
149
+ functionName: 'fileURLToPath',
150
+ sourceCode,
151
+ });
152
+ }
153
+
154
+ /**
155
+ @returns {node is import('estree').SimpleCallExpression}
156
+ */
157
+ function isPathDirnameCall(node, sourceCode) {
158
+ return isNodeBuiltinModuleFunctionCall(node, {
159
+ modules: new Set(['path', 'node:path']),
160
+ functionName: 'dirname',
161
+ sourceCode,
162
+ });
163
+ }
164
+
165
+ function create(context) {
166
+ const {sourceCode} = context;
167
+
168
+ context.on('MetaProperty', function * (node) {
169
+ if (!isImportMeta(node)) {
170
+ return;
171
+ }
172
+
173
+ /** @type {import('estree').Node} */
174
+ const memberExpression = node.parent;
175
+ if (!isMemberExpression(memberExpression, {
176
+ properties: ['url', 'filename'],
177
+ computed: false,
178
+ optional: false,
179
+ })) {
180
+ return;
181
+ }
182
+
183
+ const propertyName = memberExpression.property.name;
184
+ if (propertyName === 'url') {
185
+ // `url.fileURLToPath(import.meta.url)`
186
+ if (
187
+ isUrlFileURLToPathCall(memberExpression.parent, sourceCode)
188
+ && memberExpression.parent.arguments[0] === memberExpression
189
+ ) {
190
+ yield * iterateProblemsFromFilename(memberExpression.parent, {
191
+ reportFilenameNode: true,
192
+ });
193
+ return;
194
+ }
195
+
196
+ // `new URL(import.meta.url)`
197
+ // `new URL('.', import.meta.url)`
198
+ // `new URL('./', import.meta.url)`
199
+ if (isNewExpression(memberExpression.parent, {name: 'URL', minimumArguments: 1, maximumArguments: 2})) {
200
+ const newUrl = memberExpression.parent;
201
+ const urlParent = newUrl.parent;
202
+
203
+ // `new URL(import.meta.url)`
204
+ if (
205
+ newUrl.arguments.length === 1
206
+ && newUrl.arguments[0] === memberExpression
207
+ // `url.fileURLToPath(new URL(import.meta.url))`
208
+ && isUrlFileURLToPathCall(urlParent, sourceCode)
209
+ && urlParent.arguments[0] === newUrl
210
+ ) {
211
+ yield * iterateProblemsFromFilename(urlParent, {
212
+ reportFilenameNode: true,
213
+ });
214
+ return;
215
+ }
216
+
217
+ // `url.fileURLToPath(new URL(".", import.meta.url))`
218
+ // `url.fileURLToPath(new URL("./", import.meta.url))`
219
+ if (
220
+ newUrl.arguments.length === 2
221
+ && isParentLiteral(newUrl.arguments[0])
222
+ && newUrl.arguments[1] === memberExpression
223
+ && isUrlFileURLToPathCall(urlParent, sourceCode)
224
+ && urlParent.arguments[0] === newUrl
225
+ ) {
226
+ yield getProblem(urlParent, 'dirname');
227
+ }
228
+ }
229
+
230
+ return;
231
+ }
232
+
233
+ if (propertyName === 'filename') {
234
+ yield * iterateProblemsFromFilename(memberExpression);
235
+ }
236
+
237
+ /**
238
+ Iterates over reports where a given filename expression node
239
+ would be used to convert it to a dirname.
240
+ @param { import('estree').Expression} node
241
+ */
242
+ function * iterateProblemsFromFilename(node, {reportFilenameNode = false} = {}) {
243
+ /** @type {{parent: import('estree').Node}} */
244
+ const {parent} = node;
245
+
246
+ // `path.dirname(filename)`
247
+ if (
248
+ isPathDirnameCall(parent, sourceCode)
249
+ && parent.arguments[0] === node
250
+ ) {
251
+ yield getProblem(parent, 'dirname');
252
+ return;
253
+ }
254
+
255
+ if (reportFilenameNode) {
256
+ yield getProblem(node, 'filename');
257
+ }
258
+
259
+ if (
260
+ parent.type !== 'VariableDeclarator'
261
+ || parent.init !== node
262
+ || parent.id.type !== 'Identifier'
263
+ || parent.parent.type !== 'VariableDeclaration'
264
+ || parent.parent.kind !== 'const'
265
+ ) {
266
+ return;
267
+ }
268
+
269
+ /** @type {import('eslint').Scope.Variable|null} */
270
+ const variable = findVariable(sourceCode.getScope(parent.id), parent.id);
271
+ if (!variable) {
272
+ return;
273
+ }
274
+
275
+ for (const reference of variable.references) {
276
+ if (!reference.isReadOnly()) {
277
+ continue;
278
+ }
279
+
280
+ /** @type {{parent: import('estree').Node}} */
281
+ const {parent} = reference.identifier;
282
+ if (
283
+ isPathDirnameCall(parent, sourceCode)
284
+ && parent.arguments[0] === reference.identifier
285
+ ) {
286
+ // Report `path.dirname(identifier)`
287
+ yield getProblem(parent, 'dirname');
288
+ }
289
+ }
290
+ }
291
+
292
+ /**
293
+ @param { import('estree').Node} node
294
+ @param {'dirname' | 'filename'} name
295
+ */
296
+ function getProblem(node, name) {
297
+ return {
298
+ node,
299
+ messageId: name === 'dirname' ? ERROR_DIRNAME : ERROR_FILENAME,
300
+ fix: fixer => fixer.replaceText(node, `import.meta.${name}`),
301
+ };
302
+ }
303
+ });
304
+ }
305
+
306
+ /** @type {import('eslint').Rule.RuleModule} */
307
+ const config = {
308
+ create,
309
+ meta: {
310
+ type: 'suggestion',
311
+ docs: {
312
+ description: 'Prefer `import.meta.{dirname,filename}` over legacy techniques for getting file paths.',
313
+ recommended: false,
314
+ },
315
+ fixable: 'code',
316
+ messages,
317
+ },
318
+ };
319
+
320
+ export default config;
@@ -1,5 +1,8 @@
1
1
  import isBuiltinModule from 'is-builtin-module';
2
- import isStaticRequire from './ast/is-static-require.js';
2
+ import {
3
+ isStaticRequire,
4
+ isMethodCall,
5
+ } from './ast/index.js';
3
6
 
4
7
  const MESSAGE_ID = 'prefer-node-protocol';
5
8
  const messages = {
@@ -19,7 +22,16 @@ const create = context => ({
19
22
  && node.parent.source === node
20
23
  )
21
24
  || (
22
- isStaticRequire(node.parent)
25
+ (
26
+ isMethodCall(node.parent, {
27
+ object: 'process',
28
+ method: 'getBuiltinModule',
29
+ argumentsLength: 1,
30
+ optionalCall: false,
31
+ optionalMember: false,
32
+ })
33
+ || isStaticRequire(node.parent)
34
+ )
23
35
  && node.parent.arguments[0] === node
24
36
  )
25
37
  )) {
@@ -0,0 +1,179 @@
1
+ import {hasSideEffect, isSemicolonToken} from '@eslint-community/eslint-utils';
2
+ import {getCallExpressionTokens, getCallExpressionArgumentsText} from './utils/index.js';
3
+ import isSameReference from './utils/is-same-reference.js';
4
+ import {isNodeMatches} from './utils/is-node-matches.js';
5
+ import getPreviousNode from './utils/get-previous-node.js';
6
+ import {isMethodCall, isMemberExpression, isCallExpression} from './ast/index.js';
7
+
8
+ const ERROR = 'error/array-push';
9
+ const SUGGESTION = 'suggestion';
10
+ const messages = {
11
+ [ERROR]: 'Do not call `{{description}}` multiple times.',
12
+ [SUGGESTION]: 'Merge with previous one.',
13
+ };
14
+
15
+ const isExpressionStatement = node =>
16
+ node?.parent.type === 'ExpressionStatement'
17
+ && node.parent.expression === node;
18
+ const isClassList = node => isMemberExpression(node, {
19
+ property: 'classList',
20
+ optional: false,
21
+ computed: false,
22
+ });
23
+
24
+ const cases = [
25
+ {
26
+ description: 'Array#push()',
27
+ test: callExpression => isMethodCall(callExpression, {
28
+ method: 'push',
29
+ optionalCall: false,
30
+ optionalMember: false,
31
+ }),
32
+ ignore: [
33
+ 'stream.push',
34
+ 'this.push',
35
+ 'this.stream.push',
36
+ 'process.stdin.push',
37
+ 'process.stdout.push',
38
+ 'process.stderr.push',
39
+ ],
40
+ },
41
+ {
42
+ description: 'Element#classList.add()',
43
+ test: callExpression =>
44
+ isMethodCall(callExpression, {
45
+ method: 'add',
46
+ optionalCall: false,
47
+ optionalMember: false,
48
+ })
49
+ && isClassList(callExpression.callee.object),
50
+ },
51
+ {
52
+ description: 'Element#classList.remove()',
53
+ test: callExpression =>
54
+ isMethodCall(callExpression, {
55
+ method: 'remove',
56
+ optionalCall: false,
57
+ optionalMember: false,
58
+ })
59
+ && isClassList(callExpression.callee.object),
60
+ },
61
+ {
62
+ description: 'importScripts()',
63
+ test: callExpression => isCallExpression(callExpression, {
64
+ name: 'importScripts',
65
+ optional: false,
66
+ }),
67
+ },
68
+ ].map(problematicalCase => ({
69
+ ...problematicalCase,
70
+ test: callExpression => isExpressionStatement(callExpression) && problematicalCase.test(callExpression),
71
+ }));
72
+
73
+ function create(context) {
74
+ const {
75
+ ignore: ignoredCalleeInOptions,
76
+ } = {
77
+ ignore: [],
78
+ ...context.options[0],
79
+ };
80
+ const {sourceCode} = context;
81
+
82
+ return {
83
+ * CallExpression(secondCall) {
84
+ for (const {description, test, ignore = []} of cases) {
85
+ if (!test(secondCall)) {
86
+ continue;
87
+ }
88
+
89
+ const ignoredCallee = [...ignore, ...ignoredCalleeInOptions];
90
+ if (isNodeMatches(secondCall.callee, ignoredCallee)) {
91
+ continue;
92
+ }
93
+
94
+ const firstCall = getPreviousNode(secondCall.parent, sourceCode)?.expression;
95
+ if (!test(firstCall) || !isSameReference(firstCall.callee, secondCall.callee)) {
96
+ continue;
97
+ }
98
+
99
+ const secondCallArguments = secondCall.arguments;
100
+ const problem = {
101
+ node: secondCall.callee.type === 'Identifier' ? secondCall.callee : secondCall.callee.property,
102
+ messageId: ERROR,
103
+ data: {description},
104
+ };
105
+
106
+ const fix = function * (fixer) {
107
+ if (secondCallArguments.length > 0) {
108
+ const text = getCallExpressionArgumentsText(sourceCode, secondCall);
109
+
110
+ const {
111
+ trailingCommaToken,
112
+ closingParenthesisToken,
113
+ } = getCallExpressionTokens(sourceCode, firstCall);
114
+
115
+ yield (
116
+ trailingCommaToken
117
+ ? fixer.insertTextAfter(trailingCommaToken, ` ${text}`)
118
+ : fixer.insertTextBefore(closingParenthesisToken, firstCall.arguments.length > 0 ? `, ${text}` : text)
119
+ );
120
+ }
121
+
122
+ const firstExpression = firstCall.parent;
123
+ const secondExpression = secondCall.parent;
124
+ const shouldKeepSemicolon = !isSemicolonToken(sourceCode.getLastToken(firstExpression))
125
+ && isSemicolonToken(sourceCode.getLastToken(secondExpression));
126
+ const [, start] = sourceCode.getRange(firstExpression);
127
+ const [, end] = sourceCode.getRange(secondExpression);
128
+
129
+ yield fixer.replaceTextRange([start, end], shouldKeepSemicolon ? ';' : '');
130
+ };
131
+
132
+ if (secondCallArguments.some(element => hasSideEffect(element, sourceCode))) {
133
+ problem.suggest = [
134
+ {
135
+ messageId: SUGGESTION,
136
+ fix,
137
+ },
138
+ ];
139
+ } else {
140
+ problem.fix = fix;
141
+ }
142
+
143
+ yield problem;
144
+ }
145
+ },
146
+ };
147
+ }
148
+
149
+ const schema = [
150
+ {
151
+ type: 'object',
152
+ additionalProperties: false,
153
+ properties: {
154
+ ignore: {
155
+ type: 'array',
156
+ uniqueItems: true,
157
+ },
158
+ },
159
+ },
160
+ ];
161
+
162
+ /** @type {import('eslint').Rule.RuleModule} */
163
+ const config = {
164
+ create,
165
+ meta: {
166
+ type: 'suggestion',
167
+ docs: {
168
+ description: 'Enforce combining multiple `Array#push()`, `Element#classList.{add,remove}()`, and `importScripts()` into one call.',
169
+ recommended: true,
170
+ },
171
+ fixable: 'code',
172
+ hasSuggestions: true,
173
+ schema,
174
+ defaultOptions: [{}],
175
+ messages,
176
+ },
177
+ };
178
+
179
+ export default config;
@@ -0,0 +1,80 @@
1
+ import {isMethodCall, isMemberExpression} from '../ast/index.js';
2
+ import {isSameReference} from '../utils/index.js';
3
+ import {removeArgument} from '../fix/index.js';
4
+
5
+ function getObjectLengthOrInfinityDescription(node, object) {
6
+ // `Infinity`
7
+ if (node.type === 'Identifier' && node.name === 'Infinity') {
8
+ return 'Infinity';
9
+ }
10
+
11
+ // `Number.POSITIVE_INFINITY`
12
+ if (isMemberExpression(node, {
13
+ object: 'Number',
14
+ property: 'POSITIVE_INFINITY',
15
+ computed: false,
16
+ optional: false,
17
+ })) {
18
+ return 'Number.POSITIVE_INFINITY';
19
+ }
20
+
21
+ // `object?.length`
22
+ const isOptional = node.type === 'ChainExpression';
23
+ if (isOptional) {
24
+ node = node.expression;
25
+ }
26
+
27
+ // `object.length`
28
+ if (!(
29
+ isMemberExpression(node, {property: 'length', computed: false})
30
+ && isSameReference(object, node.object)
31
+ )) {
32
+ return;
33
+ }
34
+
35
+ return `${object.type === 'Identifier' ? object.name : '…'}${isOptional ? '?.' : '.'}length`;
36
+ }
37
+
38
+ /** @param {import('eslint').Rule.RuleContext} context */
39
+ function listen(context, {methods, messageId}) {
40
+ context.on('CallExpression', callExpression => {
41
+ if (!isMethodCall(callExpression, {
42
+ methods,
43
+ argumentsLength: 2,
44
+ optionalCall: false,
45
+ })) {
46
+ return;
47
+ }
48
+
49
+ const secondArgument = callExpression.arguments[1];
50
+ const description = getObjectLengthOrInfinityDescription(
51
+ secondArgument,
52
+ callExpression.callee.object,
53
+ );
54
+
55
+ if (!description) {
56
+ return;
57
+ }
58
+
59
+ const methodName = callExpression.callee.property.name;
60
+ const messageData = {
61
+ description,
62
+ };
63
+
64
+ if (methodName === 'splice') {
65
+ messageData.argumentName = 'deleteCount';
66
+ } else if (methodName === 'toSpliced') {
67
+ messageData.argumentName = 'skipCount';
68
+ }
69
+
70
+ return {
71
+ node: secondArgument,
72
+ messageId,
73
+ data: messageData,
74
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
75
+ fix: fixer => removeArgument(fixer, secondArgument, context.sourceCode),
76
+ };
77
+ });
78
+ }
79
+
80
+ export {listen};
@@ -0,0 +1,42 @@
1
+ import fs from 'node:fs';
2
+ import {findUpSync} from 'find-up-simple';
3
+
4
+ const directoryCache = new Map();
5
+ const dataCache = new Map();
6
+
7
+ /**
8
+ Finds the closest package.json file to the given directory and returns its path and contents.
9
+
10
+ Caches the result for future lookups.
11
+
12
+ @param dirname {string}
13
+ @return {{ path: string, packageJson: Record<string, unknown> } | undefined}
14
+ */
15
+ export function readPackageJson(dirname) {
16
+ let packageJsonPath;
17
+ if (directoryCache.has(dirname)) {
18
+ packageJsonPath = directoryCache.get(dirname);
19
+ } else {
20
+ packageJsonPath = findUpSync('package.json', {cwd: dirname, type: 'file'});
21
+ directoryCache.set(dirname, packageJsonPath);
22
+ }
23
+
24
+ if (!packageJsonPath) {
25
+ return;
26
+ }
27
+
28
+ let packageJson;
29
+ if (dataCache.has(packageJsonPath)) {
30
+ packageJson = dataCache.get(packageJsonPath);
31
+ } else {
32
+ try {
33
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
34
+ dataCache.set(packageJsonPath, packageJson);
35
+ } catch {
36
+ // This can happen if package.json files have comments in them etc.
37
+ return;
38
+ }
39
+ }
40
+
41
+ return {path: packageJsonPath, packageJson};
42
+ }
@@ -7,6 +7,7 @@ const typedArrayTypes = [
7
7
  'Uint16Array',
8
8
  'Int32Array',
9
9
  'Uint32Array',
10
+ 'Float16Array',
10
11
  'Float32Array',
11
12
  'Float64Array',
12
13
  'BigInt64Array',
@@ -1,150 +0,0 @@
1
- import {hasSideEffect, isSemicolonToken} from '@eslint-community/eslint-utils';
2
- import {getCallExpressionTokens, getCallExpressionArgumentsText} from './utils/index.js';
3
- import isSameReference from './utils/is-same-reference.js';
4
- import {isNodeMatches} from './utils/is-node-matches.js';
5
- import getPreviousNode from './utils/get-previous-node.js';
6
- import {isMethodCall} from './ast/index.js';
7
-
8
- const ERROR = 'error';
9
- const SUGGESTION = 'suggestion';
10
- const messages = {
11
- [ERROR]: 'Do not call `Array#push()` multiple times.',
12
- [SUGGESTION]: 'Merge with previous one.',
13
- };
14
-
15
- const isArrayPushCall = node =>
16
- node
17
- && node.parent.type === 'ExpressionStatement'
18
- && node.parent.expression === node
19
- && isMethodCall(node, {
20
- method: 'push',
21
- optionalCall: false,
22
- optionalMember: false,
23
- });
24
-
25
- function getFirstArrayPushCall(secondCall, sourceCode) {
26
- const firstCall = getPreviousNode(secondCall.parent, sourceCode)?.expression;
27
- if (isArrayPushCall(firstCall)) {
28
- return firstCall;
29
- }
30
- }
31
-
32
- function create(context) {
33
- const {ignore} = {
34
- ignore: [],
35
- ...context.options[0],
36
- };
37
- const ignoredObjects = [
38
- 'stream',
39
- 'this',
40
- 'this.stream',
41
- 'process.stdin',
42
- 'process.stdout',
43
- 'process.stderr',
44
- ...ignore,
45
- ];
46
- const {sourceCode} = context;
47
-
48
- return {
49
- CallExpression(secondCall) {
50
- if (!isArrayPushCall(secondCall)) {
51
- return;
52
- }
53
-
54
- const secondCallArray = secondCall.callee.object;
55
-
56
- if (isNodeMatches(secondCallArray, ignoredObjects)) {
57
- return;
58
- }
59
-
60
- const firstCall = getFirstArrayPushCall(secondCall, sourceCode);
61
- if (!firstCall) {
62
- return;
63
- }
64
-
65
- const firstCallArray = firstCall.callee.object;
66
-
67
- // Not same array
68
- if (!isSameReference(firstCallArray, secondCallArray)) {
69
- return;
70
- }
71
-
72
- const secondCallArguments = secondCall.arguments;
73
- const problem = {
74
- node: secondCall.callee.property,
75
- messageId: ERROR,
76
- };
77
-
78
- const fix = function * (fixer) {
79
- if (secondCallArguments.length > 0) {
80
- const text = getCallExpressionArgumentsText(sourceCode, secondCall);
81
-
82
- const {
83
- trailingCommaToken,
84
- closingParenthesisToken,
85
- } = getCallExpressionTokens(sourceCode, firstCall);
86
-
87
- yield (
88
- trailingCommaToken
89
- ? fixer.insertTextAfter(trailingCommaToken, ` ${text}`)
90
- : fixer.insertTextBefore(closingParenthesisToken, firstCall.arguments.length > 0 ? `, ${text}` : text)
91
- );
92
- }
93
-
94
- const firstExpression = firstCall.parent;
95
- const secondExpression = secondCall.parent;
96
- const shouldKeepSemicolon = !isSemicolonToken(sourceCode.getLastToken(firstExpression))
97
- && isSemicolonToken(sourceCode.getLastToken(secondExpression));
98
- const [, start] = sourceCode.getRange(firstExpression);
99
- const [, end] = sourceCode.getRange(secondExpression);
100
-
101
- yield fixer.replaceTextRange([start, end], shouldKeepSemicolon ? ';' : '');
102
- };
103
-
104
- if (secondCallArguments.some(element => hasSideEffect(element, sourceCode))) {
105
- problem.suggest = [
106
- {
107
- messageId: SUGGESTION,
108
- fix,
109
- },
110
- ];
111
- } else {
112
- problem.fix = fix;
113
- }
114
-
115
- return problem;
116
- },
117
- };
118
- }
119
-
120
- const schema = [
121
- {
122
- type: 'object',
123
- additionalProperties: false,
124
- properties: {
125
- ignore: {
126
- type: 'array',
127
- uniqueItems: true,
128
- },
129
- },
130
- },
131
- ];
132
-
133
- /** @type {import('eslint').Rule.RuleModule} */
134
- const config = {
135
- create,
136
- meta: {
137
- type: 'suggestion',
138
- docs: {
139
- description: 'Enforce combining multiple `Array#push()` into one call.',
140
- recommended: true,
141
- },
142
- fixable: 'code',
143
- hasSuggestions: true,
144
- schema,
145
- defaultOptions: [{}],
146
- messages,
147
- },
148
- };
149
-
150
- export default config;
@@ -1,54 +0,0 @@
1
- import {isMethodCall, isMemberExpression} from './ast/index.js';
2
- import {removeArgument} from './fix/index.js';
3
- import {isSameReference} from './utils/index.js';
4
-
5
- const MESSAGE_ID = 'no-length-as-slice-end';
6
- const messages = {
7
- [MESSAGE_ID]: 'Passing `….length` as the `end` argument is unnecessary.',
8
- };
9
-
10
- /** @param {import('eslint').Rule.RuleContext} context */
11
- const create = context => {
12
- context.on('CallExpression', callExpression => {
13
- if (!isMethodCall(callExpression, {
14
- method: 'slice',
15
- argumentsLength: 2,
16
- optionalCall: false,
17
- })) {
18
- return;
19
- }
20
-
21
- const secondArgument = callExpression.arguments[1];
22
- const node = secondArgument.type === 'ChainExpression' ? secondArgument.expression : secondArgument;
23
-
24
- if (
25
- !isMemberExpression(node, {property: 'length', computed: false})
26
- || !isSameReference(callExpression.callee.object, node.object)
27
- ) {
28
- return;
29
- }
30
-
31
- return {
32
- node,
33
- messageId: MESSAGE_ID,
34
- /** @param {import('eslint').Rule.RuleFixer} fixer */
35
- fix: fixer => removeArgument(fixer, secondArgument, context.sourceCode),
36
- };
37
- });
38
- };
39
-
40
- /** @type {import('eslint').Rule.RuleModule} */
41
- const config = {
42
- create,
43
- meta: {
44
- type: 'suggestion',
45
- docs: {
46
- description: 'Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()`.',
47
- recommended: true,
48
- },
49
- fixable: 'code',
50
- messages,
51
- },
52
- };
53
-
54
- export default config;