You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 7-8.RSVP
Socket
Socket
Sign inDemoInstall

eslint-plugin-unicorn

Package Overview
Dependencies
Maintainers
2
Versions
105
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 45.0.2 to 48.0.1

rules/ast/call-or-new-expression.js

13

configs/recommended.js
'use strict';
module.exports = {
env: {
es2022: true,
es2024: true,
},

@@ -59,3 +59,2 @@ parserOptions: {

'unicorn/no-unreadable-iife': 'error',
'unicorn/no-unsafe-regex': 'off',
'unicorn/no-unused-properties': 'off',

@@ -77,4 +76,4 @@ 'unicorn/no-useless-fallback-in-spread': 'error',

'unicorn/prefer-array-some': 'error',
// TODO: Enable this by default when targeting a Node.js version that supports `Array#at`.
'unicorn/prefer-at': 'off',
'unicorn/prefer-at': 'error',
'unicorn/prefer-blob-reading-methods': 'error',
'unicorn/prefer-code-point': 'error',

@@ -87,4 +86,3 @@ 'unicorn/prefer-date-now': 'error',

'unicorn/prefer-dom-node-text-content': 'error',
// TODO: Enable this by default when targeting Node.js 16.
'unicorn/prefer-event-target': 'off',
'unicorn/prefer-event-target': 'error',
'unicorn/prefer-export-from': 'error',

@@ -112,4 +110,3 @@ 'unicorn/prefer-includes': 'error',

'unicorn/prefer-spread': 'error',
// TODO: Enable this by default when targeting Node.js 16.
'unicorn/prefer-string-replace-all': 'off',
'unicorn/prefer-string-replace-all': 'error',
'unicorn/prefer-string-slice': 'error',

@@ -116,0 +113,0 @@ 'unicorn/prefer-string-starts-ends-with': 'error',

@@ -6,2 +6,3 @@ 'use strict';

const allRulesEnabledConfig = require('./configs/all.js');
const {name, version} = require('./package.json');

@@ -14,2 +15,3 @@ const deprecatedRules = createDeprecatedRules({

'no-reduce': 'unicorn/no-array-reduce',
'no-unsafe-regex': [],
'prefer-dataset': 'unicorn/prefer-dom-node-dataset',

@@ -30,2 +32,6 @@ 'prefer-event-key': 'unicorn/prefer-keyboard-event-key',

module.exports = {
meta: {
name,
version,
},
rules: {

@@ -32,0 +38,0 @@ ...loadRules(),

{
"name": "eslint-plugin-unicorn",
"version": "45.0.2",
"version": "48.0.1",
"description": "More than 100 powerful ESLint rules",

@@ -14,3 +14,3 @@ "license": "MIT",

"engines": {
"node": ">=14.18"
"node": ">=16"
},

@@ -20,3 +20,3 @@ "scripts": {

"fix": "run-p --continue-on-error fix:*",
"fix:eslint-docs": "eslint-doc-generator --ignore-deprecated-rules --ignore-config all --rule-doc-title-format desc --url-configs \"https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs\"",
"fix:eslint-docs": "eslint-doc-generator",
"fix:js": "npm run lint:js -- --fix",

@@ -51,9 +51,9 @@ "fix:md": "npm run lint:md -- --fix",

"dependencies": {
"@babel/helper-validator-identifier": "^7.19.1",
"@eslint-community/eslint-utils": "^4.1.2",
"ci-info": "^3.6.1",
"@babel/helper-validator-identifier": "^7.22.5",
"@eslint-community/eslint-utils": "^4.4.0",
"ci-info": "^3.8.0",
"clean-regexp": "^1.0.0",
"esquery": "^1.4.0",
"esquery": "^1.5.0",
"indent-string": "^4.0.0",
"is-builtin-module": "^3.2.0",
"is-builtin-module": "^3.2.1",
"jsesc": "^3.0.2",

@@ -63,40 +63,39 @@ "lodash": "^4.17.21",

"read-pkg-up": "^7.0.1",
"regexp-tree": "^0.1.24",
"regjsparser": "^0.9.1",
"safe-regex": "^2.1.1",
"semver": "^7.3.8",
"regexp-tree": "^0.1.27",
"regjsparser": "^0.10.0",
"semver": "^7.5.4",
"strip-indent": "^3.0.0"
},
"devDependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/core": "^7.20.2",
"@babel/eslint-parser": "^7.19.1",
"@babel/code-frame": "^7.22.5",
"@babel/core": "^7.22.8",
"@babel/eslint-parser": "^7.22.7",
"@lubien/fixture-beta-package": "^1.0.0-beta.1",
"@typescript-eslint/parser": "^5.43.0",
"@typescript-eslint/parser": "^6.2.0",
"ava": "^3.15.0",
"c8": "^7.12.0",
"chalk": "^5.1.2",
"c8": "^8.0.0",
"chalk": "^5.3.0",
"enquirer": "^2.3.6",
"eslint": "^8.28.0",
"eslint": "^8.44.0",
"eslint-ava-rule-tester": "^4.0.0",
"eslint-doc-generator": "^1.0.0",
"eslint-plugin-eslint-plugin": "^5.0.6",
"eslint-doc-generator": "^1.4.3",
"eslint-plugin-eslint-plugin": "^5.1.0",
"eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
"eslint-remote-tester": "^3.0.0",
"eslint-remote-tester-repositories": "^0.0.7",
"execa": "^6.1.0",
"eslint-remote-tester-repositories": "^1.0.1",
"execa": "^7.1.1",
"listr": "^0.14.3",
"lodash-es": "^4.17.21",
"markdownlint-cli": "^0.32.2",
"markdownlint-cli": "^0.35.0",
"mem": "^9.0.2",
"npm-package-json-lint": "^6.3.0",
"npm-package-json-lint": "^7.0.0",
"npm-run-all": "^4.1.5",
"outdent": "^0.8.0",
"typescript": "^4.9.3",
"vue-eslint-parser": "^9.1.0",
"xo": "^0.53.1",
"yaml": "^2.1.3"
"typescript": "^5.1.6",
"vue-eslint-parser": "^9.3.1",
"xo": "^0.54.2",
"yaml": "^2.3.1"
},
"peerDependencies": {
"eslint": ">=8.28.0"
"eslint": ">=8.44.0"
},

@@ -116,2 +115,5 @@ "ava": {

"xo": {
"extends": [
"plugin:internal-rules/all"
],
"ignores": [

@@ -123,2 +125,3 @@ ".cache-eslint-remote-tester",

"rules": {
"unicorn/expiring-todo-comments": "off",
"unicorn/no-null": "error",

@@ -133,3 +136,4 @@ "unicorn/prefer-array-flat": [

}
]
],
"import/order": "off"
},

@@ -170,10 +174,2 @@ "overrides": [

}
},
{
"files": [
"rules/**/*.js"
],
"extends": [
"plugin:internal-rules/all"
]
}

@@ -180,0 +176,0 @@ ]

@@ -29,3 +29,3 @@ # eslint-plugin-unicorn [![Coverage Status](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main/graph/badge.svg)](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main) [![npm version](https://img.shields.io/npm/v/eslint-plugin-unicorn.svg?style=flat)](https://npmjs.com/package/eslint-plugin-unicorn)

"env": {
"es2022": true
"es2024": true
},

@@ -53,3 +53,2 @@ "parserOptions": {

💼 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs) enabled in.\
🚫 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs) disabled in.\
✅ Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs).\

@@ -59,113 +58,113 @@ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\

| Name                                    | Description | 💼 | 🚫 | 🔧 | 💡 |
| :----------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- |
| [better-regex](docs/rules/better-regex.md) | Improve regexes by making them shorter, consistent, and safer. | ✅ | | 🔧 | |
| [catch-error-name](docs/rules/catch-error-name.md) | Enforce a specific parameter name in catch clauses. | ✅ | | 🔧 | |
| [consistent-destructuring](docs/rules/consistent-destructuring.md) | Use destructured variables over properties. | ✅ | | 🔧 | 💡 |
| [consistent-function-scoping](docs/rules/consistent-function-scoping.md) | Move function definitions to the highest possible scope. | ✅ | | | |
| [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | ✅ | 🔧 | |
| [empty-brace-spaces](docs/rules/empty-brace-spaces.md) | Enforce no spaces between braces. | ✅ | | 🔧 | |
| [error-message](docs/rules/error-message.md) | Enforce passing a `message` value when creating a built-in error. | ✅ | | | |
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase values. | ✅ | | 🔧 | |
| [expiring-todo-comments](docs/rules/expiring-todo-comments.md) | Add expiration conditions to TODO comments. | ✅ | | | |
| [explicit-length-check](docs/rules/explicit-length-check.md) | Enforce explicitly comparing the `length` or `size` property of a value. | ✅ | | 🔧 | 💡 |
| [filename-case](docs/rules/filename-case.md) | Enforce a case style for filenames. | ✅ | | | |
| [import-style](docs/rules/import-style.md) | Enforce specific import styles per module. | ✅ | | | |
| [new-for-builtins](docs/rules/new-for-builtins.md) | Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. | ✅ | | 🔧 | |
| [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) | Enforce specifying rules to disable in `eslint-disable` comments. | ✅ | | | |
| [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. | ✅ | | | 💡 |
| [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `for…of` over the `forEach` method. | ✅ | | 🔧 | 💡 |
| [no-array-method-this-argument](docs/rules/no-array-method-this-argument.md) | Disallow using the `this` argument in array methods. | ✅ | | 🔧 | 💡 |
| [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. | ✅ | | 🔧 | 💡 |
| [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. | ✅ | | | |
| [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. | ✅ | | 🔧 | |
| [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. | ✅ | | 🔧 | |
| [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. | ✅ | | | |
| [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. | ✅ | | | |
| [no-for-loop](docs/rules/no-for-loop.md) | Do not use a `for` loop that can be replaced with a `for-of` loop. | ✅ | | 🔧 | |
| [no-hex-escape](docs/rules/no-hex-escape.md) | Enforce the use of Unicode escapes instead of hexadecimal escapes. | ✅ | | 🔧 | |
| [no-instanceof-array](docs/rules/no-instanceof-array.md) | Require `Array.isArray()` instead of `instanceof Array`. | ✅ | | 🔧 | |
| [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. | ✅ | | | |
| [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | ✅ | | |
| [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. | ✅ | | 🔧 | |
| [no-negated-condition](docs/rules/no-negated-condition.md) | Disallow negated conditions. | ✅ | | 🔧 | |
| [no-nested-ternary](docs/rules/no-nested-ternary.md) | Disallow nested ternary expressions. | ✅ | | 🔧 | |
| [no-new-array](docs/rules/no-new-array.md) | Disallow `new Array()`. | ✅ | | 🔧 | 💡 |
| [no-new-buffer](docs/rules/no-new-buffer.md) | Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. | ✅ | | 🔧 | 💡 |
| [no-null](docs/rules/no-null.md) | Disallow the use of the `null` literal. | ✅ | | 🔧 | 💡 |
| [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) | Disallow the use of objects as default parameters. | ✅ | | | |
| [no-process-exit](docs/rules/no-process-exit.md) | Disallow `process.exit()`. | ✅ | | | |
| [no-static-only-class](docs/rules/no-static-only-class.md) | Disallow classes that only have static members. | ✅ | | 🔧 | |
| [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | ✅ | | | |
| [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | | |
| [no-typeof-undefined](docs/rules/no-typeof-undefined.md) | Disallow comparing `undefined` using `typeof`. | ✅ | | 🔧 | 💡 |
| [no-unnecessary-await](docs/rules/no-unnecessary-await.md) | Disallow awaiting non-promise values. | ✅ | | 🔧 | |
| [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | ✅ | | 🔧 | |
| [no-unreadable-iife](docs/rules/no-unreadable-iife.md) | Disallow unreadable IIFEs. | ✅ | | | |
| [no-unsafe-regex](docs/rules/no-unsafe-regex.md) | Disallow unsafe regular expressions. | | ✅ | | |
| [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | ✅ | | |
| [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) | Disallow useless fallback when spreading in object literals. | ✅ | | 🔧 | |
| [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. | ✅ | | 🔧 | |
| [no-useless-promise-resolve-reject](docs/rules/no-useless-promise-resolve-reject.md) | Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks | ✅ | | 🔧 | |
| [no-useless-spread](docs/rules/no-useless-spread.md) | Disallow unnecessary spread. | ✅ | | 🔧 | |
| [no-useless-switch-case](docs/rules/no-useless-switch-case.md) | Disallow useless case in switch statements. | ✅ | | | 💡 |
| [no-useless-undefined](docs/rules/no-useless-undefined.md) | Disallow useless `undefined`. | ✅ | | 🔧 | |
| [no-zero-fractions](docs/rules/no-zero-fractions.md) | Disallow number literals with zero fractions or dangling dots. | ✅ | | 🔧 | |
| [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | ✅ | | 🔧 | |
| [numeric-separators-style](docs/rules/numeric-separators-style.md) | Enforce the style of numeric separators by correctly grouping digits. | ✅ | | 🔧 | |
| [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) | Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. | ✅ | | 🔧 | |
| [prefer-array-find](docs/rules/prefer-array-find.md) | Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)`. | ✅ | | 🔧 | 💡 |
| [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. | ✅ | | 🔧 | |
| [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()`. | ✅ | | 🔧 | |
| [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item. | ✅ | | 🔧 | 💡 |
| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`. | ✅ | | 🔧 | 💡 |
| [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | | ✅ | 🔧 | 💡 |
| [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. | ✅ | | | 💡 |
| [prefer-date-now](docs/rules/prefer-date-now.md) | Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. | ✅ | | 🔧 | |
| [prefer-default-parameters](docs/rules/prefer-default-parameters.md) | Prefer default parameters over reassignment. | ✅ | | 🔧 | 💡 |
| [prefer-dom-node-append](docs/rules/prefer-dom-node-append.md) | Prefer `Node#append()` over `Node#appendChild()`. | ✅ | | 🔧 | |
| [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over calling attribute methods. | ✅ | | 🔧 | |
| [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) | Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. | ✅ | | 🔧 | 💡 |
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | | 💡 |
| [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | | ✅ | | |
| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | | 🔧 | 💡 |
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | ✅ | | 🔧 | 💡 |
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | ✅ | 🔧 | |
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | | 🔧 | |
| [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. | ✅ | | | 💡 |
| [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | ✅ | | 🔧 | 💡 |
| [prefer-modern-dom-apis](docs/rules/prefer-modern-dom-apis.md) | Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`. | ✅ | | 🔧 | |
| [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | ✅ | | 🔧 | |
| [prefer-module](docs/rules/prefer-module.md) | Prefer JavaScript modules (ESM) over CommonJS. | ✅ | | 🔧 | 💡 |
| [prefer-native-coercion-functions](docs/rules/prefer-native-coercion-functions.md) | Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly. | ✅ | | 🔧 | |
| [prefer-negative-index](docs/rules/prefer-negative-index.md) | Prefer negative index over `.length - index` for `{String,Array,TypedArray}#{slice,at}()` and `Array#splice()`. | ✅ | | 🔧 | |
| [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | Prefer using the `node:` protocol when importing Node.js builtin modules. | ✅ | | 🔧 | |
| [prefer-number-properties](docs/rules/prefer-number-properties.md) | Prefer `Number` static properties over global ones. | ✅ | | 🔧 | 💡 |
| [prefer-object-from-entries](docs/rules/prefer-object-from-entries.md) | Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object. | ✅ | | 🔧 | |
| [prefer-optional-catch-binding](docs/rules/prefer-optional-catch-binding.md) | Prefer omitting the `catch` binding parameter. | ✅ | | 🔧 | |
| [prefer-prototype-methods](docs/rules/prefer-prototype-methods.md) | Prefer borrowing methods from the prototype instead of the instance. | ✅ | | 🔧 | |
| [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`. | ✅ | | 🔧 | |
| [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) | Prefer `Reflect.apply()` over `Function#apply()`. | ✅ | | 🔧 | |
| [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. | ✅ | | 🔧 | 💡 |
| [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | ✅ | | 🔧 | 💡 |
| [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. | ✅ | | 🔧 | |
| [prefer-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#slice()` and `String#split('')`. | ✅ | | 🔧 | 💡 |
| [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) | Prefer `String#replaceAll()` over regex searches with the global flag. | | ✅ | 🔧 | |
| [prefer-string-slice](docs/rules/prefer-string-slice.md) | Prefer `String#slice()` over `String#substr()` and `String#substring()`. | ✅ | | 🔧 | |
| [prefer-string-starts-ends-with](docs/rules/prefer-string-starts-ends-with.md) | Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`. | ✅ | | 🔧 | 💡 |
| [prefer-string-trim-start-end](docs/rules/prefer-string-trim-start-end.md) | Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`. | ✅ | | 🔧 | |
| [prefer-switch](docs/rules/prefer-switch.md) | Prefer `switch` over multiple `else-if`. | ✅ | | 🔧 | |
| [prefer-ternary](docs/rules/prefer-ternary.md) | Prefer ternary expressions over simple `if-else` statements. | ✅ | | 🔧 | |
| [prefer-top-level-await](docs/rules/prefer-top-level-await.md) | Prefer top-level await over top-level promises and async function calls. | ✅ | | | 💡 |
| [prefer-type-error](docs/rules/prefer-type-error.md) | Enforce throwing `TypeError` in type checking conditions. | ✅ | | 🔧 | |
| [prevent-abbreviations](docs/rules/prevent-abbreviations.md) | Prevent abbreviations. | ✅ | | 🔧 | |
| [relative-url-style](docs/rules/relative-url-style.md) | Enforce consistent relative URL style. | ✅ | | 🔧 | 💡 |
| [require-array-join-separator](docs/rules/require-array-join-separator.md) | Enforce using the separator argument with `Array#join()`. | ✅ | | 🔧 | |
| [require-number-to-fixed-digits-argument](docs/rules/require-number-to-fixed-digits-argument.md) | Enforce using the digits argument with `Number#toFixed()`. | ✅ | | 🔧 | |
| [require-post-message-target-origin](docs/rules/require-post-message-target-origin.md) | Enforce using the `targetOrigin` argument with `window.postMessage()`. | | ✅ | | 💡 |
| [string-content](docs/rules/string-content.md) | Enforce better string content. | | ✅ | 🔧 | 💡 |
| [switch-case-braces](docs/rules/switch-case-braces.md) | Enforce consistent brace style for `case` clauses. | ✅ | | 🔧 | |
| [template-indent](docs/rules/template-indent.md) | Fix whitespace-insensitive template indentation. | ✅ | | 🔧 | |
| [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | ✅ | | 🔧 | 💡 |
| [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | ✅ | | 🔧 | |
| Name                                    | Description | 💼 | 🔧 | 💡 |
| :----------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
| [better-regex](docs/rules/better-regex.md) | Improve regexes by making them shorter, consistent, and safer. | ✅ | 🔧 | |
| [catch-error-name](docs/rules/catch-error-name.md) | Enforce a specific parameter name in catch clauses. | ✅ | 🔧 | |
| [consistent-destructuring](docs/rules/consistent-destructuring.md) | Use destructured variables over properties. | ✅ | 🔧 | 💡 |
| [consistent-function-scoping](docs/rules/consistent-function-scoping.md) | Move function definitions to the highest possible scope. | ✅ | | |
| [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | 🔧 | |
| [empty-brace-spaces](docs/rules/empty-brace-spaces.md) | Enforce no spaces between braces. | ✅ | 🔧 | |
| [error-message](docs/rules/error-message.md) | Enforce passing a `message` value when creating a built-in error. | ✅ | | |
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase values. | ✅ | 🔧 | |
| [expiring-todo-comments](docs/rules/expiring-todo-comments.md) | Add expiration conditions to TODO comments. | ✅ | | |
| [explicit-length-check](docs/rules/explicit-length-check.md) | Enforce explicitly comparing the `length` or `size` property of a value. | ✅ | 🔧 | 💡 |
| [filename-case](docs/rules/filename-case.md) | Enforce a case style for filenames. | ✅ | | |
| [import-style](docs/rules/import-style.md) | Enforce specific import styles per module. | ✅ | | |
| [new-for-builtins](docs/rules/new-for-builtins.md) | Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. | ✅ | 🔧 | |
| [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) | Enforce specifying rules to disable in `eslint-disable` comments. | ✅ | | |
| [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. | ✅ | | 💡 |
| [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `for…of` over the `forEach` method. | ✅ | 🔧 | 💡 |
| [no-array-method-this-argument](docs/rules/no-array-method-this-argument.md) | Disallow using the `this` argument in array methods. | ✅ | 🔧 | 💡 |
| [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. | ✅ | 🔧 | 💡 |
| [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. | ✅ | | |
| [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. | ✅ | 🔧 | |
| [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. | ✅ | 🔧 | |
| [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. | ✅ | | |
| [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. | ✅ | | |
| [no-for-loop](docs/rules/no-for-loop.md) | Do not use a `for` loop that can be replaced with a `for-of` loop. | ✅ | 🔧 | |
| [no-hex-escape](docs/rules/no-hex-escape.md) | Enforce the use of Unicode escapes instead of hexadecimal escapes. | ✅ | 🔧 | |
| [no-instanceof-array](docs/rules/no-instanceof-array.md) | Require `Array.isArray()` instead of `instanceof Array`. | ✅ | 🔧 | |
| [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. | ✅ | | |
| [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | | |
| [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. | ✅ | 🔧 | |
| [no-negated-condition](docs/rules/no-negated-condition.md) | Disallow negated conditions. | ✅ | 🔧 | |
| [no-nested-ternary](docs/rules/no-nested-ternary.md) | Disallow nested ternary expressions. | ✅ | 🔧 | |
| [no-new-array](docs/rules/no-new-array.md) | Disallow `new Array()`. | ✅ | 🔧 | 💡 |
| [no-new-buffer](docs/rules/no-new-buffer.md) | Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. | ✅ | 🔧 | 💡 |
| [no-null](docs/rules/no-null.md) | Disallow the use of the `null` literal. | ✅ | 🔧 | 💡 |
| [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) | Disallow the use of objects as default parameters. | ✅ | | |
| [no-process-exit](docs/rules/no-process-exit.md) | Disallow `process.exit()`. | ✅ | | |
| [no-static-only-class](docs/rules/no-static-only-class.md) | Disallow classes that only have static members. | ✅ | 🔧 | |
| [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | ✅ | | |
| [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | |
| [no-typeof-undefined](docs/rules/no-typeof-undefined.md) | Disallow comparing `undefined` using `typeof`. | ✅ | 🔧 | 💡 |
| [no-unnecessary-await](docs/rules/no-unnecessary-await.md) | Disallow awaiting non-promise values. | ✅ | 🔧 | |
| [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | ✅ | 🔧 | |
| [no-unreadable-iife](docs/rules/no-unreadable-iife.md) | Disallow unreadable IIFEs. | ✅ | | |
| [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | |
| [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) | Disallow useless fallback when spreading in object literals. | ✅ | 🔧 | |
| [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. | ✅ | 🔧 | |
| [no-useless-promise-resolve-reject](docs/rules/no-useless-promise-resolve-reject.md) | Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks | ✅ | 🔧 | |
| [no-useless-spread](docs/rules/no-useless-spread.md) | Disallow unnecessary spread. | ✅ | 🔧 | |
| [no-useless-switch-case](docs/rules/no-useless-switch-case.md) | Disallow useless case in switch statements. | ✅ | | 💡 |
| [no-useless-undefined](docs/rules/no-useless-undefined.md) | Disallow useless `undefined`. | ✅ | 🔧 | |
| [no-zero-fractions](docs/rules/no-zero-fractions.md) | Disallow number literals with zero fractions or dangling dots. | ✅ | 🔧 | |
| [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | ✅ | 🔧 | |
| [numeric-separators-style](docs/rules/numeric-separators-style.md) | Enforce the style of numeric separators by correctly grouping digits. | ✅ | 🔧 | |
| [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) | Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. | ✅ | 🔧 | |
| [prefer-array-find](docs/rules/prefer-array-find.md) | Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)`. | ✅ | 🔧 | 💡 |
| [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. | ✅ | 🔧 | |
| [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()`. | ✅ | 🔧 | |
| [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item. | ✅ | 🔧 | 💡 |
| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`. | ✅ | 🔧 | 💡 |
| [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | ✅ | 🔧 | 💡 |
| [prefer-blob-reading-methods](docs/rules/prefer-blob-reading-methods.md) | Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)`. | ✅ | | |
| [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. | ✅ | | 💡 |
| [prefer-date-now](docs/rules/prefer-date-now.md) | Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. | ✅ | 🔧 | |
| [prefer-default-parameters](docs/rules/prefer-default-parameters.md) | Prefer default parameters over reassignment. | ✅ | 🔧 | 💡 |
| [prefer-dom-node-append](docs/rules/prefer-dom-node-append.md) | Prefer `Node#append()` over `Node#appendChild()`. | ✅ | 🔧 | |
| [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over calling attribute methods. | ✅ | 🔧 | |
| [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) | Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. | ✅ | 🔧 | 💡 |
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | 💡 |
| [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | ✅ | | |
| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | 🔧 | 💡 |
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | 🔧 | |
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | |
| [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. | ✅ | | 💡 |
| [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | ✅ | 🔧 | 💡 |
| [prefer-modern-dom-apis](docs/rules/prefer-modern-dom-apis.md) | Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`. | ✅ | 🔧 | |
| [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | ✅ | 🔧 | |
| [prefer-module](docs/rules/prefer-module.md) | Prefer JavaScript modules (ESM) over CommonJS. | ✅ | 🔧 | 💡 |
| [prefer-native-coercion-functions](docs/rules/prefer-native-coercion-functions.md) | Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly. | ✅ | 🔧 | |
| [prefer-negative-index](docs/rules/prefer-negative-index.md) | Prefer negative index over `.length - index` when possible. | ✅ | 🔧 | |
| [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | Prefer using the `node:` protocol when importing Node.js builtin modules. | ✅ | 🔧 | |
| [prefer-number-properties](docs/rules/prefer-number-properties.md) | Prefer `Number` static properties over global ones. | ✅ | 🔧 | 💡 |
| [prefer-object-from-entries](docs/rules/prefer-object-from-entries.md) | Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object. | ✅ | 🔧 | |
| [prefer-optional-catch-binding](docs/rules/prefer-optional-catch-binding.md) | Prefer omitting the `catch` binding parameter. | ✅ | 🔧 | |
| [prefer-prototype-methods](docs/rules/prefer-prototype-methods.md) | Prefer borrowing methods from the prototype instead of the instance. | ✅ | 🔧 | |
| [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`. | ✅ | 🔧 | |
| [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) | Prefer `Reflect.apply()` over `Function#apply()`. | ✅ | 🔧 | |
| [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. | ✅ | 🔧 | 💡 |
| [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
| [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. | ✅ | 🔧 | |
| [prefer-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split('')`. | ✅ | 🔧 | 💡 |
| [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) | Prefer `String#replaceAll()` over regex searches with the global flag. | ✅ | 🔧 | |
| [prefer-string-slice](docs/rules/prefer-string-slice.md) | Prefer `String#slice()` over `String#substr()` and `String#substring()`. | ✅ | 🔧 | |
| [prefer-string-starts-ends-with](docs/rules/prefer-string-starts-ends-with.md) | Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`. | ✅ | 🔧 | 💡 |
| [prefer-string-trim-start-end](docs/rules/prefer-string-trim-start-end.md) | Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`. | ✅ | 🔧 | |
| [prefer-switch](docs/rules/prefer-switch.md) | Prefer `switch` over multiple `else-if`. | ✅ | 🔧 | |
| [prefer-ternary](docs/rules/prefer-ternary.md) | Prefer ternary expressions over simple `if-else` statements. | ✅ | 🔧 | |
| [prefer-top-level-await](docs/rules/prefer-top-level-await.md) | Prefer top-level await over top-level promises and async function calls. | ✅ | | 💡 |
| [prefer-type-error](docs/rules/prefer-type-error.md) | Enforce throwing `TypeError` in type checking conditions. | ✅ | 🔧 | |
| [prevent-abbreviations](docs/rules/prevent-abbreviations.md) | Prevent abbreviations. | ✅ | 🔧 | |
| [relative-url-style](docs/rules/relative-url-style.md) | Enforce consistent relative URL style. | ✅ | 🔧 | 💡 |
| [require-array-join-separator](docs/rules/require-array-join-separator.md) | Enforce using the separator argument with `Array#join()`. | ✅ | 🔧 | |
| [require-number-to-fixed-digits-argument](docs/rules/require-number-to-fixed-digits-argument.md) | Enforce using the digits argument with `Number#toFixed()`. | ✅ | 🔧 | |
| [require-post-message-target-origin](docs/rules/require-post-message-target-origin.md) | Enforce using the `targetOrigin` argument with `window.postMessage()`. | | | 💡 |
| [string-content](docs/rules/string-content.md) | Enforce better string content. | | 🔧 | 💡 |
| [switch-case-braces](docs/rules/switch-case-braces.md) | Enforce consistent brace style for `case` clauses. | ✅ | 🔧 | |
| [template-indent](docs/rules/template-indent.md) | Fix whitespace-insensitive template indentation. | ✅ | 🔧 | |
| [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | ✅ | 🔧 | 💡 |
| [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | ✅ | 🔧 | |

@@ -172,0 +171,0 @@ <!-- end auto-generated rules list -->

@@ -11,2 +11,7 @@ 'use strict';

} = require('./literal.js');
const {
isNewExpression,
isCallExpression,
isCallOrNewExpression,
} = require('./call-or-new-expression.js');

@@ -22,6 +27,15 @@ module.exports = {

isArrowFunctionBody: require('./is-arrow-function-body.js'),
isCallExpression,
isCallOrNewExpression,
isEmptyNode: require('./is-empty-node.js'),
isExpressionStatement: require('./is-expression-statement.js'),
isFunction: require('./is-function.js'),
isMemberExpression: require('./is-member-expression.js'),
isMethodCall: require('./is-method-call.js'),
isNewExpression,
isReferenceIdentifier: require('./is-reference-identifier.js'),
isStaticRequire: require('./is-static-require.js'),
isUndefined: require('./is-undefined.js'),
isNewExpression: require('./is-new-expression.js'),
functionTypes: require('./function-types.js'),
};
'use strict';
const {isStringLiteral} = require('./literal.js');
const {isCallExpression} = require('./call-or-new-expression.js');
const isStaticRequire = node => Boolean(
node?.type === 'CallExpression'
&& node.callee.type === 'Identifier'
&& node.callee.name === 'require'
&& !node.optional
&& node.arguments.length === 1
&& isStringLiteral(node.arguments[0]),
);
const isStaticRequire = node =>
isCallExpression(node, {
name: 'require',
argumentsLength: 1,
optional: false,
})
&& isStringLiteral(node.arguments[0]);
module.exports = isStaticRequire;

@@ -5,4 +5,3 @@ 'use strict';

const escapeString = require('./utils/escape-string.js');
const {newExpressionSelector} = require('./selectors/index.js');
const {isStringLiteral} = require('./ast/index.js');
const {isStringLiteral, isNewExpression, isRegexLiteral} = require('./ast/index.js');

@@ -16,4 +15,2 @@ const MESSAGE_ID = 'better-regex';

const newRegExp = newExpressionSelector({name: 'RegExp', minimumArguments: 1});
/** @param {import('eslint').Rule.RuleContext} context */

@@ -30,3 +27,7 @@ const create = context => {

return {
'Literal[regex]'(node) {
Literal(node) {
if (!isRegexLiteral(node)) {
return;
}
const {raw: original, regex} = node;

@@ -86,3 +87,7 @@

},
[newRegExp](node) {
NewExpression(node) {
if (!isNewExpression(node, {name: 'RegExp', minimumArguments: 1})) {
return;
}
const [patternNode, flagsNode] = node.arguments;

@@ -89,0 +94,0 @@

@@ -5,3 +5,3 @@ 'use strict';

const {renameVariable} = require('./fix/index.js');
const {matches, methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -13,24 +13,24 @@ const MESSAGE_ID = 'catch-error-name';

const selector = matches([
// `try {} catch (foo) {}`
[
'CatchClause',
' > ',
'Identifier.param',
].join(''),
// - `promise.then(…, foo => {})`
// - `promise.then(…, function(foo) {})`
// - `promise.catch(foo => {})`
// - `promise.catch(function(foo) {})`
[
matches([
methodCallSelector({method: 'then', argumentsLength: 2}),
methodCallSelector({method: 'catch', argumentsLength: 1}),
]),
' > ',
':matches(FunctionExpression, ArrowFunctionExpression).arguments:last-child',
' > ',
'Identifier.params:first-child',
].join(''),
]);
// - `promise.then(…, foo => {})`
// - `promise.then(…, function(foo) {})`
// - `promise.catch(foo => {})`
// - `promise.catch(function(foo) {})`
const isPromiseCatchParameter = node =>
(node.parent.type === 'FunctionExpression' || node.parent.type === 'ArrowFunctionExpression')
&& node.parent.params[0] === node
&& (
isMethodCall(node.parent.parent, {
method: 'then',
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
|| isMethodCall(node.parent.parent, {
method: 'catch',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
)
&& node.parent.parent.arguments.at(-1) === node.parent;

@@ -55,3 +55,10 @@ /** @param {import('eslint').Rule.RuleContext} context */

return {
[selector](node) {
Identifier(node) {
if (
!(node.parent.type === 'CatchClause' && node.parent.param === node)
&& !isPromiseCatchParameter(node)
) {
return;
}
const originalName = node.name;

@@ -66,3 +73,3 @@

const scope = context.getScope();
const scope = context.sourceCode.getScope(node);
const variable = findVariable(scope, node);

@@ -69,0 +76,0 @@

'use strict';
const avoidCapture = require('./utils/avoid-capture.js');
const {not, notLeftHandSideSelector} = require('./selectors/index.js');
const isLeftHandSide = require('./utils/is-left-hand-side.js');
const {isCallOrNewExpression} = require('./ast/index.js');

@@ -8,19 +9,2 @@ const MESSAGE_ID = 'consistentDestructuring';

const declaratorSelector = [
'VariableDeclarator',
'[id.type="ObjectPattern"]',
'[init]',
'[init.type!="Literal"]',
].join('');
const memberSelector = [
'MemberExpression',
'[computed!=true]',
notLeftHandSideSelector(),
not([
'CallExpression > .callee',
'NewExpression> .callee',
]),
].join('');
const isSimpleExpression = expression => {

@@ -57,21 +41,37 @@ while (expression) {

const create = context => {
const source = context.getSourceCode();
const {sourceCode} = context;
const declarations = new Map();
return {
[declaratorSelector](node) {
// Ignore any complex expressions (e.g. arrays, functions)
if (!isSimpleExpression(node.init)) {
VariableDeclarator(node) {
if (!(
node.id.type === 'ObjectPattern'
&& node.init
&& node.init.type !== 'Literal'
// Ignore any complex expressions (e.g. arrays, functions)
&& isSimpleExpression(node.init)
)) {
return;
}
declarations.set(source.getText(node.init), {
scope: context.getScope(),
variables: context.getDeclaredVariables(node),
declarations.set(sourceCode.getText(node.init), {
scope: sourceCode.getScope(node),
variables: sourceCode.getDeclaredVariables(node),
objectPattern: node.id,
});
},
[memberSelector](node) {
const declaration = declarations.get(source.getText(node.object));
MemberExpression(node) {
if (
node.computed
|| (
isCallOrNewExpression(node.parent)
&& node.parent.callee === node
)
|| isLeftHandSide(node)
) {
return;
}
const declaration = declarations.get(sourceCode.getText(node.object));
if (!declaration) {

@@ -82,3 +82,3 @@ return;

const {scope, objectPattern} = declaration;
const memberScope = context.getScope();
const memberScope = sourceCode.getScope(node);

@@ -99,4 +99,4 @@ // Property is destructured outside the current scope

const expression = source.getText(node);
const member = source.getText(node.property);
const expression = sourceCode.getText(node);
const member = sourceCode.getText(node.property);

@@ -103,0 +103,0 @@ // Member might already be destructured

'use strict';
const {getFunctionHeadLocation, getFunctionNameWithKind} = require('@eslint-community/eslint-utils');
const getReferences = require('./utils/get-references.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const {
getReferences,
isNodeMatches,
} = require('./utils/index.js');
const {
functionTypes,
} = require('./ast/index.js');

@@ -152,3 +157,3 @@ const MESSAGE_ID = 'consistent-function-scoping';

const {checkArrowFunctions} = {checkArrowFunctions: true, ...context.options[0]};
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {scopeManager} = sourceCode;

@@ -158,37 +163,37 @@

return {
':function'() {
functions.push(false);
},
JSXElement() {
// Turn off this rule if we see a JSX element because scope
// references does not include JSXElement nodes.
if (functions.length > 0) {
functions[functions.length - 1] = true;
}
},
':function:exit'(node) {
const currentFunctionHasJsx = functions.pop();
if (currentFunctionHasJsx) {
return;
}
context.on(functionTypes, () => {
functions.push(false);
});
if (node.type === 'ArrowFunctionExpression' && !checkArrowFunctions) {
return;
}
context.on('JSXElement', () => {
// Turn off this rule if we see a JSX element because scope
// references does not include JSXElement nodes.
if (functions.length > 0) {
functions[functions.length - 1] = true;
}
});
if (checkNode(node, scopeManager)) {
return;
}
context.onExit(functionTypes, node => {
const currentFunctionHasJsx = functions.pop();
if (currentFunctionHasJsx) {
return;
}
return {
node,
loc: getFunctionHeadLocation(node, sourceCode),
messageId: MESSAGE_ID,
data: {
functionNameWithKind: getFunctionNameWithKind(node, sourceCode),
},
};
},
};
if (node.type === 'ArrowFunctionExpression' && !checkArrowFunctions) {
return;
}
if (checkNode(node, scopeManager)) {
return;
}
return {
node,
loc: getFunctionHeadLocation(node, sourceCode),
messageId: MESSAGE_ID,
data: {
functionNameWithKind: getFunctionNameWithKind(node, sourceCode),
},
};
});
};

@@ -195,0 +200,0 @@

@@ -186,7 +186,19 @@ 'use strict';

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
ClassDeclaration: node => customErrorDefinition(context, node),
'AssignmentExpression[right.type="ClassExpression"]': node => customErrorDefinition(context, node.right),
'AssignmentExpression[left.type="MemberExpression"][left.object.type="Identifier"][left.object.name="exports"]': node => customErrorExport(context, node),
});
const create = context => {
context.on('ClassDeclaration', node => customErrorDefinition(context, node));
context.on('AssignmentExpression', node => {
if (node.right.type === 'ClassExpression') {
return customErrorDefinition(context, node.right);
}
});
context.on('AssignmentExpression', node => {
if (
node.left.type === 'MemberExpression'
&& node.left.object.type === 'Identifier'
&& node.left.object.name === 'exports'
) {
return customErrorExport(context, node);
}
});
};

@@ -193,0 +205,0 @@ /** @type {import('eslint').Rule.RuleModule} */

'use strict';
const {isOpeningBraceToken} = require('@eslint-community/eslint-utils');
const {matches} = require('./selectors/index.js');

@@ -10,38 +9,53 @@ const MESSAGE_ID = 'empty-brace-spaces';

const selector = matches([
'BlockStatement[body.length=0]',
'ClassBody[body.length=0]',
'ObjectExpression[properties.length=0]',
'StaticBlock[body.length=0]',
// Experimental https://github.com/tc39/proposal-record-tuple
'RecordExpression[properties.length=0]',
]);
const getProblem = (node, context) => {
const {sourceCode} = context;
const filter = node.type === 'RecordExpression'
? token => token.type === 'Punctuator' && (token.value === '#{' || token.value === '{|')
: isOpeningBraceToken;
const openingBrace = sourceCode.getFirstToken(node, {filter});
const closingBrace = sourceCode.getLastToken(node);
const [, start] = openingBrace.range;
const [end] = closingBrace.range;
const textBetween = sourceCode.text.slice(start, end);
if (!/^\s+$/.test(textBetween)) {
return;
}
return {
loc: {
start: openingBrace.loc.end,
end: closingBrace.loc.start,
},
messageId: MESSAGE_ID,
fix: fixer => fixer.removeRange([start, end]),
};
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const sourceCode = context.getSourceCode();
const filter = node.type === 'RecordExpression'
? token => token.type === 'Punctuator' && (token.value === '#{' || token.value === '{|')
: isOpeningBraceToken;
const openingBrace = sourceCode.getFirstToken(node, {filter});
const closingBrace = sourceCode.getLastToken(node);
const [, start] = openingBrace.range;
const [end] = closingBrace.range;
const textBetween = sourceCode.text.slice(start, end);
const create = context => {
context.on([
'BlockStatement',
'ClassBody',
'StaticBlock',
], node => {
if (node.body.length > 0) {
return;
}
if (!/^\s+$/.test(textBetween)) {
return getProblem(node, context);
});
context.on([
'ObjectExpression',
// Experimental https://github.com/tc39/proposal-record-tuple
'RecordExpression',
], node => {
if (node.properties.length > 0) {
return;
}
return {
loc: {
start: openingBrace.loc.end,
end: closingBrace.loc.start,
},
messageId: MESSAGE_ID,
fix: fixer => fixer.removeRange([start, end]),
};
},
});
return getProblem(node, context);
});
};

@@ -48,0 +62,0 @@ /** @type {import('eslint').Rule.RuleModule} */

'use strict';
const {getStaticValue} = require('@eslint-community/eslint-utils');
const isShadowed = require('./utils/is-shadowed.js');
const {callOrNewExpressionSelector} = require('./selectors/index.js');
const {isCallOrNewExpression} = require('./ast/index.js');

@@ -15,3 +15,3 @@ const MESSAGE_ID_MISSING_MESSAGE = 'missing-message';

const selector = callOrNewExpressionSelector([
const builtinErrors = [
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error

@@ -27,11 +27,19 @@ 'Error',

'AggregateError',
]);
];
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](expression) {
if (isShadowed(context.getScope(), expression.callee)) {
const create = context => {
context.on(['CallExpression', 'NewExpression'], expression => {
if (!isCallOrNewExpression(expression, {
names: builtinErrors,
optional: false,
})) {
return;
}
const scope = context.sourceCode.getScope(expression);
if (isShadowed(scope, expression.callee)) {
return;
}
const constructorName = expression.callee.name;

@@ -64,3 +72,3 @@ const messageArgumentIndex = constructorName === 'AggregateError' ? 1 : 0;

const staticResult = getStaticValue(node, context.getScope());
const staticResult = getStaticValue(node, scope);

@@ -86,4 +94,4 @@ // We don't know the value of `message`

}
},
});
});
};

@@ -90,0 +98,0 @@ /** @type {import('eslint').Rule.RuleModule} */

'use strict';
const {replaceTemplateElement} = require('./fix/index.js');
const {isRegexLiteral, isStringLiteral} = require('./ast/index.js');

@@ -24,29 +25,29 @@ const MESSAGE_ID = 'escape-case';

/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
Literal(node) {
if (typeof node.value !== 'string') {
return;
const create = context => {
context.on('Literal', node => {
if (isStringLiteral(node)) {
return getProblem({
node,
original: node.raw,
});
}
});
return getProblem({
node,
original: node.raw,
});
},
'Literal[regex]'(node) {
return getProblem({
node,
original: node.raw,
regex: escapePatternWithLowercase,
});
},
TemplateElement(node) {
return getProblem({
node,
original: node.value.raw,
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed),
});
},
});
context.on('Literal', node => {
if (isRegexLiteral(node)) {
return getProblem({
node,
original: node.raw,
regex: escapePatternWithLowercase,
});
}
});
context.on('TemplateElement', node => getProblem({
node,
original: node.value.raw,
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed),
}));
};
/** @type {import('eslint').Rule.RuleModule} */

@@ -53,0 +54,0 @@ module.exports = {

@@ -50,3 +50,6 @@ 'use strict';

const packageResult = readPkgUp.sync();
// We don't need to normalize the package.json data, because we are only using 2 properties and those 2 properties
// aren't validated by the normalization. But when this plugin is used in a monorepo, the name field in the
// package.json is invalid and would make this plugin throw an error. See also #1871
const packageResult = readPkgUp.sync({normalize: false});
const hasPackage = Boolean(packageResult);

@@ -264,3 +267,3 @@ const packageJson = hasPackage ? packageResult.packageJson : {};

const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const comments = sourceCode.getAllComments();

@@ -288,9 +291,5 @@ const unusedComments = comments

...context,
getSourceCode() {
return {
...sourceCode,
getAllComments() {
return options.allowWarningComments ? [] : unusedComments;
},
};
sourceCode: {
...sourceCode,
getAllComments: () => options.allowWarningComments ? [] : unusedComments,
},

@@ -297,0 +296,0 @@ };

@@ -6,5 +6,4 @@ 'use strict';

const {isBooleanNode, getBooleanAncestor} = require('./utils/boolean.js');
const {memberExpressionSelector} = require('./selectors/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isLiteral} = require('./ast/index.js');
const {isLiteral, isMemberExpression, isNumberLiteral} = require('./ast/index.js');

@@ -49,4 +48,2 @@ const TYPE_NON_ZERO = 'non-zero';

const lengthSelector = memberExpressionSelector(['length', 'size']);
function getLengthCheckNode(node) {

@@ -98,2 +95,11 @@ node = node.parent;

function isNodeValueNumber(node, context) {
if (isNumberLiteral(node)) {
return true;
}
const staticValue = getStaticValue(node, context.sourceCode.getScope(node));
return staticValue && typeof staticValue.value === 'number';
}
function create(context) {

@@ -105,3 +111,3 @@ const options = {

const nonZeroStyle = nonZeroStyles.get(options['non-zero']);
const sourceCode = context.getSourceCode();
const {sourceCode} = context;

@@ -149,8 +155,15 @@ function getProblem({node, isZeroLengthCheck, lengthNode, autoFix}) {

return {
[lengthSelector](lengthNode) {
if (lengthNode.object.type === 'ThisExpression') {
MemberExpression(memberExpression) {
if (
!isMemberExpression(memberExpression, {
properties: ['length', 'size'],
optional: false,
})
|| memberExpression.object.type === 'ThisExpression'
) {
return;
}
const staticValue = getStaticValue(lengthNode, context.getScope());
const lengthNode = memberExpression;
const staticValue = getStaticValue(lengthNode, sourceCode.getScope(lengthNode));
if (staticValue && (!Number.isInteger(staticValue.value) || staticValue.value < 0)) {

@@ -175,3 +188,9 @@ // Ignore known, non-positive-integer length properties.

node = ancestor;
} else if (isLogicalExpression(lengthNode.parent)) {
} else if (
isLogicalExpression(lengthNode.parent)
&& !(
lengthNode.parent.operator === '||'
&& isNodeValueNumber(lengthNode.parent.right, context)
)
) {
isZeroLengthCheck = isNegative;

@@ -178,0 +197,0 @@ node = lengthNode;

@@ -147,3 +147,3 @@ 'use strict';

const chosenCasesFunctions = chosenCases.map(case_ => ignoreNumbers(cases[case_].fn));
const filenameWithExtension = context.getPhysicalFilename();
const filenameWithExtension = context.physicalFilename;

@@ -150,0 +150,0 @@ if (filenameWithExtension === '<input>' || filenameWithExtension === '<text>') {

'use strict';
const {defaultsDeep} = require('lodash');
const {getStringIfConstant} = require('@eslint-community/eslint-utils');
const {callExpressionSelector} = require('./selectors/index.js');
const {isCallExpression} = require('./ast/index.js');

@@ -106,16 +106,8 @@ const MESSAGE_ID = 'importStyle';

const joinOr = words => words
.map((word, index) => {
if (index === words.length - 1) {
return word;
}
const isAssignedDynamicImport = node =>
node.parent.type === 'AwaitExpression'
&& node.parent.argument === node
&& node.parent.parent.type === 'VariableDeclarator'
&& node.parent.parent.init === node.parent;
if (index === words.length - 2) {
return word + ' or';
}
return word + ',';
})
.join(' ');
// Keep this alphabetically sorted for easier maintenance

@@ -134,15 +126,2 @@ const defaultStyles = {

const assignedDynamicImportSelector = [
'VariableDeclarator',
'[init.type="AwaitExpression"]',
'[init.argument.type="ImportExpression"]',
].join('');
const assignedRequireSelector = [
'VariableDeclarator',
'[init.type="CallExpression"]',
'[init.callee.type="Identifier"]',
'[init.callee.name="require"]',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */

@@ -172,2 +151,4 @@ const create = context => {

const {sourceCode} = context;
const report = (node, moduleName, actualImportStyles, allowedImportStyles, isRequire = false) => {

@@ -194,3 +175,3 @@ if (!allowedImportStyles || allowedImportStyles.size === 0) {

const data = {
allowedStyles: joinOr([...allowedImportStyles.keys()]),
allowedStyles: new Intl.ListFormat('en-US', {type: 'disjunction'}).format([...allowedImportStyles.keys()]),
moduleName,

@@ -206,102 +187,113 @@ };

let visitor = {};
if (checkImport) {
visitor = {
...visitor,
context.on('ImportDeclaration', node => {
const moduleName = getStringIfConstant(node.source, sourceCode.getScope(node.source));
ImportDeclaration(node) {
const moduleName = getStringIfConstant(node.source, context.getScope());
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualImportDeclarationStyles(node);
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualImportDeclarationStyles(node);
report(node, moduleName, actualImportStyles, allowedImportStyles);
},
};
report(node, moduleName, actualImportStyles, allowedImportStyles);
});
}
if (checkDynamicImport) {
visitor = {
...visitor,
context.on('ImportExpression', node => {
if (isAssignedDynamicImport(node)) {
return;
}
'ExpressionStatement > ImportExpression'(node) {
const moduleName = getStringIfConstant(node.source, context.getScope());
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = ['unassigned'];
const moduleName = getStringIfConstant(node.source, sourceCode.getScope(node.source));
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = ['unassigned'];
report(node, moduleName, actualImportStyles, allowedImportStyles);
},
report(node, moduleName, actualImportStyles, allowedImportStyles);
});
[assignedDynamicImportSelector](node) {
const assignmentTargetNode = node.id;
const moduleNameNode = node.init.argument.source;
const moduleName = getStringIfConstant(moduleNameNode, context.getScope());
context.on('VariableDeclarator', node => {
if (!(
node.init?.type === 'AwaitExpression'
&& node.init.argument.type === 'ImportExpression'
)) {
return;
}
if (!moduleName) {
return;
}
const assignmentTargetNode = node.id;
const moduleNameNode = node.init.argument.source;
const moduleName = getStringIfConstant(moduleNameNode, sourceCode.getScope(moduleNameNode));
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualAssignmentTargetImportStyles(assignmentTargetNode);
if (!moduleName) {
return;
}
report(node, moduleName, actualImportStyles, allowedImportStyles);
},
};
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualAssignmentTargetImportStyles(assignmentTargetNode);
report(node, moduleName, actualImportStyles, allowedImportStyles);
});
}
if (checkExportFrom) {
visitor = {
...visitor,
context.on('ExportAllDeclaration', node => {
const moduleName = getStringIfConstant(node.source, sourceCode.getScope(node.source));
ExportAllDeclaration(node) {
const moduleName = getStringIfConstant(node.source, context.getScope());
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = ['namespace'];
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = ['namespace'];
report(node, moduleName, actualImportStyles, allowedImportStyles);
});
report(node, moduleName, actualImportStyles, allowedImportStyles);
},
context.on('ExportNamedDeclaration', node => {
const moduleName = getStringIfConstant(node.source, sourceCode.getScope(node.source));
ExportNamedDeclaration(node) {
const moduleName = getStringIfConstant(node.source, context.getScope());
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualExportDeclarationStyles(node);
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualExportDeclarationStyles(node);
report(node, moduleName, actualImportStyles, allowedImportStyles);
},
};
report(node, moduleName, actualImportStyles, allowedImportStyles);
});
}
if (checkRequire) {
visitor = {
...visitor,
context.on('CallExpression', node => {
if (!(
isCallExpression(node, {
name: 'require',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& (node.parent.type === 'ExpressionStatement' && node.parent.expression === node)
)) {
return;
}
[`ExpressionStatement > ${callExpressionSelector({name: 'require', argumentsLength: 1})}.expression`](node) {
const moduleName = getStringIfConstant(node.arguments[0], context.getScope());
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = ['unassigned'];
const moduleName = getStringIfConstant(node.arguments[0], sourceCode.getScope(node.arguments[0]));
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = ['unassigned'];
report(node, moduleName, actualImportStyles, allowedImportStyles, true);
},
report(node, moduleName, actualImportStyles, allowedImportStyles, true);
});
[assignedRequireSelector](node) {
const assignmentTargetNode = node.id;
const moduleNameNode = node.init.arguments[0];
const moduleName = getStringIfConstant(moduleNameNode, context.getScope());
context.on('VariableDeclarator', node => {
if (!(
node.init?.type === 'CallExpression'
&& node.init.callee.type === 'Identifier'
&& node.init.callee.name === 'require'
)) {
return;
}
if (!moduleName) {
return;
}
const assignmentTargetNode = node.id;
const moduleNameNode = node.init.arguments[0];
const moduleName = getStringIfConstant(moduleNameNode, sourceCode.getScope(moduleNameNode));
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualAssignmentTargetImportStyles(assignmentTargetNode);
if (!moduleName) {
return;
}
report(node, moduleName, actualImportStyles, allowedImportStyles, true);
},
};
const allowedImportStyles = styles.get(moduleName);
const actualImportStyles = getActualAssignmentTargetImportStyles(assignmentTargetNode);
report(node, moduleName, actualImportStyles, allowedImportStyles, true);
});
}
return visitor;
};

@@ -308,0 +300,0 @@

@@ -52,3 +52,3 @@ 'use strict';

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const newExpressionTracker = new GlobalReferenceTracker({

@@ -66,4 +66,4 @@ objects: builtins.disallowNew,

return {
* 'Program:exit'() {
const scope = context.getScope();
* 'Program:exit'(program) {
const scope = sourceCode.getScope(program);

@@ -70,0 +70,0 @@ yield * newExpressionTracker.track(scope);

'use strict';
const {isParenthesized} = require('@eslint-community/eslint-utils');
const {methodCallSelector, notFunctionSelector} = require('./selectors/index.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const {isMethodCall} = require('./ast/index.js');
const {isNodeMatches, isNodeValueNotFunction} = require('./utils/index.js');

@@ -17,117 +17,124 @@ const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';

const iteratorMethods = [
[
'every',
{
ignore: [
'Boolean',
],
},
],
[
'filter', {
extraSelector: '[callee.object.name!="Vue"]',
ignore: [
'Boolean',
],
},
],
[
'find',
{
ignore: [
'Boolean',
],
},
],
[
'findLast',
{
ignore: [
'Boolean',
],
},
],
[
'findIndex',
{
ignore: [
'Boolean',
],
},
],
[
'findLastIndex',
{
ignore: [
'Boolean',
],
},
],
[
'flatMap',
],
[
'forEach',
{
returnsUndefined: true,
},
],
[
'map',
{
extraSelector: '[callee.object.name!="types"]',
ignore: [
'String',
'Number',
'BigInt',
'Boolean',
'Symbol',
],
},
],
[
'reduce',
{
parameters: [
'accumulator',
'element',
'index',
'array',
],
minParameters: 2,
},
],
[
'reduceRight',
{
parameters: [
'accumulator',
'element',
'index',
'array',
],
minParameters: 2,
},
],
[
'some',
{
ignore: [
'Boolean',
],
},
],
].map(([method, options]) => {
options = {
parameters: ['element', 'index', 'array'],
ignore: [],
minParameters: 1,
extraSelector: '',
returnsUndefined: false,
...options,
};
return [method, options];
});
const isAwaitExpressionArgument = node => node.parent.type === 'AwaitExpression' && node.parent.argument === node;
const iteratorMethods = new Map([
{
method: 'every',
ignore: [
'Boolean',
],
},
{
method: 'filter',
test: node => !(node.callee.object.type === 'Identifier' && node.callee.object.name === 'Vue'),
ignore: [
'Boolean',
],
},
{
method: 'find',
ignore: [
'Boolean',
],
},
{
method: 'findLast',
ignore: [
'Boolean',
],
},
{
method: 'findIndex',
ignore: [
'Boolean',
],
},
{
method: 'findLastIndex',
ignore: [
'Boolean',
],
},
{
method: 'flatMap',
},
{
method: 'forEach',
returnsUndefined: true,
},
{
method: 'map',
test: node => !(node.callee.object.type === 'Identifier' && node.callee.object.name === 'types'),
ignore: [
'String',
'Number',
'BigInt',
'Boolean',
'Symbol',
],
},
{
method: 'reduce',
parameters: [
'accumulator',
'element',
'index',
'array',
],
minParameters: 2,
},
{
method: 'reduceRight',
parameters: [
'accumulator',
'element',
'index',
'array',
],
minParameters: 2,
},
{
method: 'some',
ignore: [
'Boolean',
],
},
].map(({
method,
parameters = ['element', 'index', 'array'],
ignore = [],
minParameters = 1,
returnsUndefined = false,
test,
}) => [method, {
minParameters,
parameters,
returnsUndefined,
test(node) {
if (
method !== 'reduce'
&& method !== 'reduceRight'
&& isAwaitExpressionArgument(node)
) {
return false;
}
if (isNodeMatches(node.callee.object, ignoredCallee)) {
return false;
}
if (node.callee.object.type === 'CallExpression' && isNodeMatches(node.callee.object.callee, ignoredCallee)) {
return false;
}
const [callback] = node.arguments;
if (callback.type === 'Identifier' && ignore.includes(callback.name)) {
return false;
}
return !test || test(node);
},
}]));
const ignoredCallee = [

@@ -153,6 +160,2 @@ // http://bluebirdjs.com/docs/api/promise.map.html

if (type === 'Identifier' && options.ignore.includes(name)) {
return;
}
const problem = {

@@ -179,3 +182,3 @@ node,

fix(fixer) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
let nodeText = sourceCode.getText(node);

@@ -201,43 +204,46 @@ if (isParenthesized(node, sourceCode) || type === 'ConditionalExpression') {

const ignoredFirstArgumentSelector = [
notFunctionSelector('arguments.0'),
// Ignore all `CallExpression`s include `function.bind()`
'[arguments.0.type!="CallExpression"]',
'[arguments.0.type!="FunctionExpression"]',
'[arguments.0.type!="ArrowFunctionExpression"]',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const rules = {};
for (const [method, options] of iteratorMethods) {
const selector = [
method === 'reduce' || method === 'reduceRight' ? '' : ':not(AwaitExpression) > ',
methodCallSelector({
method,
const create = context => ({
CallExpression(node) {
if (
!isMethodCall(node, {
minimumArguments: 1,
maximumArguments: 2,
}),
options.extraSelector,
ignoredFirstArgumentSelector,
].join('');
optionalCall: false,
optionalMember: false,
computed: false,
})
|| node.callee.property.type !== 'Identifier'
) {
return;
}
rules[selector] = node => {
if (isNodeMatches(node.callee.object, ignoredCallee)) {
return;
}
const methodNode = node.callee.property;
const methodName = methodNode.name;
if (!iteratorMethods.has(methodName)) {
return;
}
if (node.callee.object.type === 'CallExpression' && isNodeMatches(node.callee.object.callee, ignoredCallee)) {
return;
}
const [callback] = node.arguments;
const [iterator] = node.arguments;
return getProblem(context, iterator, method, options);
};
}
if (
callback.type === 'FunctionExpression'
|| callback.type === 'ArrowFunctionExpression'
// Ignore all `CallExpression`s include `function.bind()`
|| callback.type === 'CallExpression'
|| isNodeValueNotFunction(callback)
) {
return;
}
return rules;
};
const options = iteratorMethods.get(methodName);
if (!options.test(node)) {
return;
}
return getProblem(context, callback, methodName, options);
},
});
/** @type {import('eslint').Rule.RuleModule} */

@@ -244,0 +250,0 @@ module.exports = {

@@ -10,3 +10,2 @@ 'use strict';

} = require('@eslint-community/eslint-utils');
const {methodCallSelector, referenceIdentifierSelector} = require('./selectors/index.js');
const {extendFixRange} = require('./fix/index.js');

@@ -21,3 +20,3 @@ const needsSemicolon = require('./utils/needs-semicolon.js');

const {fixSpaceAroundKeyword, removeParentheses} = require('./fix/index.js');
const {isArrowFunctionBody} = require('./ast/index.js');
const {isArrowFunctionBody, isMethodCall, isReferenceIdentifier, functionTypes} = require('./ast/index.js');

@@ -31,8 +30,2 @@ const MESSAGE_ID_ERROR = 'no-array-for-each/error';

const forEachMethodCallSelector = methodCallSelector({
method: 'forEach',
includeOptionalCall: true,
includeOptionalMember: true,
});
const continueAbleNodeTypes = new Set([

@@ -86,3 +79,3 @@ 'WhileStatement',

function getFixFunction(callExpression, functionInfo, context) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const [callback] = callExpression.arguments;

@@ -101,3 +94,3 @@ const parameters = callback.params;

let text = 'for (';
text += isFunctionParameterVariableReassigned(callback, context) ? 'let' : 'const';
text += isFunctionParameterVariableReassigned(callback, sourceCode) ? 'let' : 'const';
text += ' ';

@@ -284,4 +277,4 @@ text += shouldUseEntries ? `[${indexText}, ${elementText}]` : elementText;

function isFunctionParametersSafeToFix(callbackFunction, {context, scope, callExpression, allIdentifiers}) {
const variables = context.getDeclaredVariables(callbackFunction);
function isFunctionParametersSafeToFix(callbackFunction, {sourceCode, scope, callExpression, allIdentifiers}) {
const variables = sourceCode.getDeclaredVariables(callbackFunction);

@@ -320,4 +313,4 @@ for (const variable of variables) {

function isFunctionParameterVariableReassigned(callbackFunction, context) {
return context.getDeclaredVariables(callbackFunction)
function isFunctionParameterVariableReassigned(callbackFunction, sourceCode) {
return sourceCode.getDeclaredVariables(callbackFunction)
.filter(variable => variable.defs[0].type === 'Parameter')

@@ -329,3 +322,3 @@ .some(variable =>

function isFixable(callExpression, {scope, functionInfo, allIdentifiers, context}) {
function isFixable(callExpression, {scope, functionInfo, allIdentifiers, sourceCode}) {
// Check `CallExpression`

@@ -365,3 +358,3 @@ if (callExpression.optional || callExpression.arguments.length !== 1) {

|| parameters.some(({type, typeAnnotation}) => type === 'RestElement' || typeAnnotation)
|| !isFunctionParametersSafeToFix(callback, {scope, callExpression, allIdentifiers, context})
|| !isFunctionParametersSafeToFix(callback, {scope, callExpression, allIdentifiers, sourceCode})
) {

@@ -398,65 +391,79 @@ return false;

const functionInfo = new Map();
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
':function'(node) {
functionStack.push(node);
functionInfo.set(node, {
returnStatements: [],
scope: context.getScope(),
});
},
':function:exit'() {
functionStack.pop();
},
[referenceIdentifierSelector()](node) {
context.on(functionTypes, node => {
functionStack.push(node);
functionInfo.set(node, {
returnStatements: [],
scope: sourceCode.getScope(node),
});
});
context.onExit(functionTypes, () => {
functionStack.pop();
});
context.on('Identifier', node => {
if (isReferenceIdentifier(node)) {
allIdentifiers.push(node);
},
':function ReturnStatement'(node) {
const currentFunction = functionStack[functionStack.length - 1];
const {returnStatements} = functionInfo.get(currentFunction);
returnStatements.push(node);
},
[forEachMethodCallSelector](node) {
if (isNodeMatches(node.callee.object, ignoredObjects)) {
return;
}
}
});
callExpressions.push({
node,
scope: context.getScope(),
});
},
* 'Program:exit'() {
for (const {node, scope} of callExpressions) {
const iterable = node.callee;
context.on('ReturnStatement', node => {
const currentFunction = functionStack[functionStack.length - 1];
if (!currentFunction) {
return;
}
const problem = {
node: iterable.property,
messageId: MESSAGE_ID_ERROR,
};
const {returnStatements} = functionInfo.get(currentFunction);
returnStatements.push(node);
});
if (!isFixable(node, {scope, allIdentifiers, functionInfo, context})) {
yield problem;
continue;
}
context.on('CallExpression', node => {
if (
!isMethodCall(node, {
method: 'forEach',
})
|| isNodeMatches(node.callee.object, ignoredObjects)
) {
return;
}
const shouldUseSuggestion = iterable.optional && hasSideEffect(iterable, sourceCode);
const fix = getFixFunction(node, functionInfo, context);
callExpressions.push({
node,
scope: sourceCode.getScope(node),
});
});
if (shouldUseSuggestion) {
problem.suggest = [
{
messageId: MESSAGE_ID_SUGGESTION,
fix,
},
];
} else {
problem.fix = fix;
}
context.onExit('Program', function * () {
for (const {node, scope} of callExpressions) {
const iterable = node.callee;
const problem = {
node: iterable.property,
messageId: MESSAGE_ID_ERROR,
};
if (!isFixable(node, {scope, allIdentifiers, functionInfo, sourceCode})) {
yield problem;
continue;
}
},
};
const shouldUseSuggestion = iterable.optional && hasSideEffect(iterable, sourceCode);
const fix = getFixFunction(node, functionInfo, context);
if (shouldUseSuggestion) {
problem.suggest = [
{
messageId: MESSAGE_ID_SUGGESTION,
fix,
},
];
} else {
problem.fix = fix;
}
yield problem;
}
});
};

@@ -463,0 +470,0 @@

'use strict';
const {hasSideEffect} = require('@eslint-community/eslint-utils');
const {methodCallSelector, notFunctionSelector} = require('./selectors/index.js');
const {removeArgument} = require('./fix/index.js');

@@ -8,2 +7,4 @@ const {getParentheses, getParenthesizedText} = require('./utils/parentheses.js');

const {isNodeMatches} = require('./utils/is-node-matches.js');
const {isNodeValueNotFunction} = require('./utils/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -73,21 +74,2 @@ const ERROR = 'error';

const selector = [
methodCallSelector({
methods: [
'every',
'filter',
'find',
'findLast',
'findIndex',
'findLastIndex',
'flatMap',
'forEach',
'map',
'some',
],
argumentsLength: 2,
}),
notFunctionSelector('arguments.0'),
].join('');
function removeThisArgument(callExpression, sourceCode) {

@@ -124,11 +106,31 @@ return fixer => removeArgument(fixer, callExpression.arguments[1], sourceCode);

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[selector](callExpression) {
const {callee} = callExpression;
if (isNodeMatches(callee, ignored)) {
CallExpression(callExpression) {
if (
!isMethodCall(callExpression, {
methods: [
'every',
'filter',
'find',
'findLast',
'findIndex',
'findLastIndex',
'flatMap',
'forEach',
'map',
'some',
],
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
|| isNodeMatches(callExpression.callee, ignored)
|| isNodeValueNotFunction(callExpression.arguments[0])
) {
return;
}
const {callee} = callExpression;
const method = callee.property.name;

@@ -135,0 +137,0 @@ const [callback, thisArgument] = callExpression.arguments;

'use strict';
const {hasSideEffect, isCommaToken, isSemicolonToken} = require('@eslint-community/eslint-utils');
const {methodCallSelector} = require('./selectors/index.js');
const getCallExpressionArgumentsText = require('./utils/get-call-expression-arguments-text.js');
const isSameReference = require('./utils/is-same-reference.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const getPreviousNode = require('./utils/get-previous-node.js');
const {isMethodCall} = require('./ast/index.js');

@@ -15,26 +16,17 @@ const ERROR = 'error';

const arrayPushExpressionStatement = [
'ExpressionStatement',
methodCallSelector({path: 'expression', method: 'push'}),
].join('');
const isArrayPushCall = node =>
node
&& node.parent.type === 'ExpressionStatement'
&& node.parent.expression === node
&& isMethodCall(node, {
method: 'push',
optionalCall: false,
optionalMember: false,
});
const selector = `${arrayPushExpressionStatement} + ${arrayPushExpressionStatement}`;
function getFirstExpression(node, sourceCode) {
const {parent} = node;
const visitorKeys = sourceCode.visitorKeys[parent.type] || Object.keys(parent);
for (const property of visitorKeys) {
const value = parent[property];
if (Array.isArray(value)) {
const index = value.indexOf(node);
if (index !== -1) {
return value[index - 1];
}
}
function getFirstArrayPushCall(secondCall, sourceCode) {
const firstCall = getPreviousNode(secondCall.parent, sourceCode)?.expression;
if (isArrayPushCall(firstCall)) {
return firstCall;
}
/* c8 ignore next */
throw new Error('Cannot find the first `Array#push()` call.\nPlease open an issue at https://github.com/sindresorhus/eslint-plugin-unicorn/issues/new?title=%60no-array-push-push%60%3A%20Cannot%20find%20first%20%60push()%60');
}

@@ -56,7 +48,10 @@

];
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[selector](secondExpression) {
const secondCall = secondExpression.expression;
CallExpression(secondCall) {
if (!isArrayPushCall(secondCall)) {
return;
}
const secondCallArray = secondCall.callee.object;

@@ -68,4 +63,7 @@

const firstExpression = getFirstExpression(secondExpression, sourceCode);
const firstCall = firstExpression.expression;
const firstCall = getFirstArrayPushCall(secondCall, sourceCode);
if (!firstCall) {
return;
}
const firstCallArray = firstCall.callee.object;

@@ -96,2 +94,4 @@

const firstExpression = firstCall.parent;
const secondExpression = secondCall.parent;
const shouldKeepSemicolon = !isSemicolonToken(sourceCode.getLastToken(firstExpression))

@@ -98,0 +98,0 @@ && isSemicolonToken(sourceCode.getLastToken(secondExpression));

'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {arrayPrototypeMethodSelector, notFunctionSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const {isNodeValueNotFunction, isArrayPrototypeProperty} = require('./utils/index.js');

@@ -10,16 +10,14 @@ const MESSAGE_ID = 'no-reduce';

const prototypeSelector = method => [
methodCallSelector(method),
arrayPrototypeMethodSelector({
path: 'callee.object',
methods: ['reduce', 'reduceRight'],
}),
].join('');
const cases = [
// `array.{reduce,reduceRight}()`
{
selector: [
methodCallSelector({methods: ['reduce', 'reduceRight'], minimumArguments: 1, maximumArguments: 2}),
notFunctionSelector('arguments.0'),
].join(''),
test: callExpression =>
isMethodCall(callExpression, {
methods: ['reduce', 'reduceRight'],
minimumArguments: 1,
maximumArguments: 2,
optionalCall: false,
optionalMember: false,
})
&& !isNodeValueNotFunction(callExpression.arguments[0]),
getMethodNode: callExpression => callExpression.callee.property,

@@ -49,6 +47,15 @@ isSimpleOperation(callExpression) {

{
selector: [
prototypeSelector('call'),
notFunctionSelector('arguments.1'),
].join(''),
test: callExpression =>
isMethodCall(callExpression, {
method: 'call',
optionalCall: false,
optionalMember: false,
})
&& isArrayPrototypeProperty(callExpression.callee.object, {
properties: ['reduce', 'reduceRight'],
})
&& (
!callExpression.arguments[1]
|| !isNodeValueNotFunction(callExpression.arguments[1])
),
getMethodNode: callExpression => callExpression.callee.object.property,

@@ -58,3 +65,11 @@ },

{
selector: prototypeSelector('apply'),
test: callExpression =>
isMethodCall(callExpression, {
method: 'apply',
optionalCall: false,
optionalMember: false,
})
&& isArrayPrototypeProperty(callExpression.callee.object, {
properties: ['reduce', 'reduceRight'],
}),
getMethodNode: callExpression => callExpression.callee.object.property,

@@ -80,20 +95,23 @@ },

const {allowSimpleOperations} = {allowSimpleOperations: true, ...context.options[0]};
const listeners = {};
for (const {selector, getMethodNode, isSimpleOperation} of cases) {
listeners[selector] = callExpression => {
if (allowSimpleOperations && isSimpleOperation?.(callExpression)) {
return;
}
return {
* CallExpression(callExpression) {
for (const {test, getMethodNode, isSimpleOperation} of cases) {
if (!test(callExpression)) {
continue;
}
const methodNode = getMethodNode(callExpression);
return {
node: methodNode,
messageId: MESSAGE_ID,
data: {method: methodNode.name},
};
};
}
if (allowSimpleOperations && isSimpleOperation?.(callExpression)) {
continue;
}
return listeners;
const methodNode = getMethodNode(callExpression);
yield {
node: methodNode,
messageId: MESSAGE_ID,
data: {method: methodNode.name},
};
}
},
};
};

@@ -100,0 +118,0 @@

@@ -15,6 +15,10 @@ 'use strict';

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
'MemberExpression[object.type="AwaitExpression"]'(memberExpression) {
MemberExpression(memberExpression) {
if (memberExpression.object.type !== 'AwaitExpression') {
return;
}
const {property} = memberExpression;

@@ -21,0 +25,0 @@ const problem = {

'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const toLocation = require('./utils/to-location.js');
const {isStringLiteral} = require('./ast/index.js');
const {isStringLiteral, isMethodCall} = require('./ast/index.js');

@@ -11,16 +10,2 @@ const MESSAGE_ID = 'no-console-spaces';

const methods = [
'log',
'debug',
'info',
'warn',
'error',
];
const selector = methodCallSelector({
methods,
minimumArguments: 1,
object: 'console',
});
// Find exactly one leading space, allow exactly one space

@@ -34,3 +19,3 @@ const hasLeadingSpace = value => value.length > 1 && value.charAt(0) === ' ' && value.charAt(1) !== ' ';

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const getProblem = (node, method, position) => {

@@ -51,3 +36,21 @@ const index = position === 'leading'

return {
* [selector](node) {
* CallExpression(node) {
if (
!isMethodCall(node, {
object: 'console',
methods: [
'log',
'debug',
'info',
'warn',
'error',
],
minimumArguments: 1,
optionalCall: false,
optionalMember: false,
})
) {
return;
}
const method = node.callee.property.name;

@@ -54,0 +57,0 @@ const {arguments: messages} = node;

@@ -9,3 +9,3 @@ 'use strict';

const isDirective = node => node.type === 'ExpressionStatement' && 'directive' in node;
const isDirective = node => node.type === 'ExpressionStatement' && typeof node.directive === 'string';
const isEmpty = node => isEmptyNode(node, isDirective);

@@ -21,5 +21,5 @@

const create = context => {
const filename = context.getPhysicalFilename().toLowerCase();
const filename = context.physicalFilename;
if (!/\.(?:js|mjs|cjs|ts|mts|cts)$/.test(filename)) {
if (!/\.(?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$/i.test(filename)) {
return;

@@ -34,3 +34,3 @@ }

const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const comments = sourceCode.getAllComments();

@@ -37,0 +37,0 @@

@@ -265,3 +265,3 @@ 'use strict';

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {scopeManager, text: sourceCodeText} = sourceCode;

@@ -284,3 +284,3 @@

const scope = context.getScope();
const scope = sourceCode.getScope(node);
const staticResult = getStaticValue(arrayIdentifier, scope);

@@ -287,0 +287,0 @@ if (staticResult && !Array.isArray(staticResult.value)) {

'use strict';
const {replaceTemplateElement} = require('./fix/index.js');
const {isStringLiteral, isRegexLiteral} = require('./ast/index.js');

@@ -27,3 +28,3 @@ const MESSAGE_ID = 'no-hex-escape';

Literal(node) {
if (node.regex || typeof node.value === 'string') {
if (isStringLiteral(node) || isRegexLiteral(node)) {
return checkEscape(context, node, node.raw);

@@ -30,0 +31,0 @@ }

@@ -12,20 +12,22 @@ 'use strict';

};
const selector = [
'BinaryExpression',
'[operator="instanceof"]',
'[right.type="Identifier"]',
'[right.name="Array"]',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[selector](node) {
BinaryExpression(node) {
if (!(
node.operator === 'instanceof'
&& node.right.type === 'Identifier'
&& node.right.name === 'Array'
)) {
return;
}
const {left, right} = node;
let tokenStore = sourceCode;
let instanceofToken = tokenStore.getTokenAfter(left, isInstanceofToken);
if (!instanceofToken && context.parserServices.getTemplateBodyTokenStore) {
tokenStore = context.parserServices.getTemplateBodyTokenStore();
if (!instanceofToken && sourceCode.parserServices.getTemplateBodyTokenStore) {
tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore();
instanceofToken = tokenStore.getTokenAfter(left, isInstanceofToken);

@@ -32,0 +34,0 @@ }

'use strict';
const {getFunctionHeadLocation} = require('@eslint-community/eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url.js');
const {methodCallSelector, matches} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -11,23 +10,31 @@ const MESSAGE_ID = 'no-invalid-remove-event-listener';

const removeEventListenerSelector = [
methodCallSelector({
method: 'removeEventListener',
minimumArguments: 2,
}),
'[arguments.0.type!="SpreadElement"]',
matches([
'[arguments.1.type="FunctionExpression"]',
'[arguments.1.type="ArrowFunctionExpression"]',
methodCallSelector({method: 'bind', path: 'arguments.1'}),
]),
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[removeEventListenerSelector](node) {
const listener = node.arguments[1];
CallExpression(callExpression) {
if (!(
isMethodCall(callExpression, {
method: 'removeEventListener',
minimumArguments: 2,
optionalCall: false,
optionalMember: false,
})
&& callExpression.arguments[0].type !== 'SpreadElement'
&& (
callExpression.arguments[1].type === 'FunctionExpression'
|| callExpression.arguments[1].type === 'ArrowFunctionExpression'
|| isMethodCall(callExpression.arguments[1], {
method: 'bind',
optionalCall: false,
optionalMember: false,
})
)
)) {
return;
}
const [, listener] = callExpression.arguments;
if (['ArrowFunctionExpression', 'FunctionExpression'].includes(listener.type)) {
return {
node: listener,
loc: getFunctionHeadLocation(listener, context.getSourceCode()),
loc: getFunctionHeadLocation(listener, context.sourceCode),
messageId: MESSAGE_ID,

@@ -51,3 +58,2 @@ };

description: 'Prevent calling `EventTarget#removeEventListener()` with the result of an expression.',
url: getDocumentationUrl(__filename),
},

@@ -54,0 +60,0 @@ messages,

'use strict';
const {isParenthesized, isNotSemicolonToken} = require('@eslint-community/eslint-utils');
const needsSemicolon = require('./utils/needs-semicolon.js');
const {needsSemicolon} = require('./utils/index.js');
const {removeSpacesAfter} = require('./fix/index.js');
const {matches} = require('./selectors/index.js');

@@ -12,18 +11,4 @@ const MESSAGE_ID = 'no-lonely-if';

const ifStatementWithoutAlternate = 'IfStatement:not([alternate])';
const selector = matches([
// `if (a) { if (b) {} }`
[
ifStatementWithoutAlternate,
' > ',
'BlockStatement.consequent',
'[body.length=1]',
' > ',
`${ifStatementWithoutAlternate}.body`,
].join(''),
const isIfStatementWithoutAlternate = node => node.type === 'IfStatement' && !node.alternate;
// `if (a) if (b) {}`
`${ifStatementWithoutAlternate} > ${ifStatementWithoutAlternate}.consequent`,
]);
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table

@@ -116,3 +101,3 @@ // Lower precedence than `&&`

const nextToken = sourceCode.getTokenAfter(outer);
if (needsSemicolon(lastToken, sourceCode, nextToken.value)) {
if (nextToken && needsSemicolon(lastToken, sourceCode, nextToken.value)) {
yield fixer.insertTextBefore(nextToken, ';');

@@ -126,15 +111,32 @@ }

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const create = context => ({
IfStatement(ifStatement) {
if (!(
isIfStatementWithoutAlternate(ifStatement)
&& (
// `if (a) { if (b) {} }`
(
ifStatement.parent.type === 'BlockStatement'
&& ifStatement.parent.body.length === 1
&& ifStatement.parent.body[0] === ifStatement
&& isIfStatementWithoutAlternate(ifStatement.parent.parent)
&& ifStatement.parent.parent.consequent === ifStatement.parent
)
// `if (a) if (b) {}`
|| (
isIfStatementWithoutAlternate(ifStatement.parent)
&& ifStatement.parent.consequent === ifStatement
)
)
)) {
return;
}
return {
[selector](node) {
return {
node,
messageId: MESSAGE_ID,
fix: fix(node, sourceCode),
};
},
};
};
return {
node: ifStatement,
messageId: MESSAGE_ID,
fix: fix(ifStatement, context.sourceCode),
};
},
});

@@ -141,0 +143,0 @@ /** @type {import('eslint').Rule.RuleModule} */

@@ -6,3 +6,2 @@ /*

'use strict';
const {matches} = require('./selectors/index.js');
const {

@@ -25,14 +24,2 @@ removeParentheses,

const selector = [
matches([
'IfStatement[alternate][alternate.type!="IfStatement"]',
'ConditionalExpression',
]),
matches([
'[test.type="UnaryExpression"][test.operator="!"]',
'[test.type="BinaryExpression"][test.operator="!="]',
'[test.type="BinaryExpression"][test.operator="!=="]',
]),
].join('');
function * convertNegatedCondition(fixer, node, sourceCode) {

@@ -87,10 +74,29 @@ const {test} = node;

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const create = context => {
context.on(['IfStatement', 'ConditionalExpression'], node => {
if (
node.type === 'IfStatement'
&& (
!node.alternate
|| node.alternate.type === 'IfStatement'
)
) {
return;
}
const {test} = node;
if (!(
(test.type === 'UnaryExpression' && test.operator === '!')
|| (test.type === 'BinaryExpression' && (test.operator === '!=' || test.operator === '!=='))
)) {
return;
}
return {
node: node.test,
node: test,
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
yield * convertNegatedCondition(fixer, node, sourceCode);

@@ -101,3 +107,3 @@ yield * swapConsequentAndAlternate(fixer, node, sourceCode);

node.type !== 'ConditionalExpression'
|| node.test.type !== 'UnaryExpression'
|| test.type !== 'UnaryExpression'
) {

@@ -109,3 +115,3 @@ return;

const {test, parent} = node;
const {parent} = node;
const [firstToken, secondToken] = sourceCode.getFirstTokens(test, 2);

@@ -129,4 +135,4 @@ if (

};
},
});
});
};

@@ -133,0 +139,0 @@ /** @type {import('eslint').Rule.RuleModule} */

@@ -11,27 +11,38 @@ 'use strict';

const nestTernarySelector = level => `:not(ConditionalExpression)${' > ConditionalExpression'.repeat(level)}`;
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const create = context => ({
ConditionalExpression(node) {
if ([
node.test,
node.consequent,
node.alternate,
].some(node => node.type === 'ConditionalExpression')) {
return;
}
return {
[nestTernarySelector(3)]: node =>
// Nesting more than one level not allowed.
({node, messageId: MESSAGE_ID_TOO_DEEP}),
[nestTernarySelector(2)](node) {
if (!isParenthesized(node, sourceCode)) {
return {
node,
messageId: MESSAGE_ID_SHOULD_PARENTHESIZED,
fix: fixer => [
fixer.insertTextBefore(node, '('),
fixer.insertTextAfter(node, ')'),
],
};
}
},
};
};
const {sourceCode} = context;
const ancestors = sourceCode.getAncestors(node).reverse();
const nestLevel = ancestors.findIndex(node => node.type !== 'ConditionalExpression');
if (nestLevel === 1 && !isParenthesized(node, sourceCode)) {
return {
node,
messageId: MESSAGE_ID_SHOULD_PARENTHESIZED,
fix: fixer => [
fixer.insertTextBefore(node, '('),
fixer.insertTextAfter(node, ')'),
],
};
}
// Nesting more than one level not allowed
if (nestLevel > 1) {
return {
node: nestLevel > 2 ? ancestors[nestLevel - 3] : node,
messageId: MESSAGE_ID_TOO_DEEP,
};
}
},
});
/** @type {import('eslint').Rule.RuleModule} */

@@ -38,0 +49,0 @@ module.exports = {

'use strict';
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
const needsSemicolon = require('./utils/needs-semicolon.js');
const {newExpressionSelector} = require('./selectors/index.js');
const isNumber = require('./utils/is-number.js');
const {isNewExpression} = require('./ast/index.js');

@@ -17,9 +17,14 @@ const MESSAGE_ID_ERROR = 'error';

};
const newArraySelector = newExpressionSelector({
name: 'Array',
argumentsLength: 1,
allowSpreadElement: true,
});
function getProblem(context, node) {
if (
!isNewExpression(node, {
name: 'Array',
argumentsLength: 1,
allowSpreadElement: true,
})
) {
return;
}
const problem = {

@@ -32,3 +37,3 @@ node,

const sourceCode = context.getSourceCode();
const {sourceCode} = context;
let text = sourceCode.getText(argumentNode);

@@ -55,3 +60,4 @@ if (isParenthesized(argumentNode, sourceCode)) {

const fromLengthText = `Array.from(${text === 'length' ? '{length}' : `{length: ${text}}`})`;
if (isNumber(argumentNode, context.getScope())) {
const scope = sourceCode.getScope(node);
if (isNumber(argumentNode, scope)) {
problem.fix = fixer => fixer.replaceText(node, fromLengthText);

@@ -62,3 +68,3 @@ return problem;

const onlyElementText = `${maybeSemiColon}[${text}]`;
const result = getStaticValue(argumentNode, context.getScope());
const result = getStaticValue(argumentNode, scope);
if (result !== null && typeof result.value !== 'number') {

@@ -85,3 +91,3 @@ problem.fix = fixer => fixer.replaceText(node, onlyElementText);

const create = context => ({
[newArraySelector](node) {
NewExpression(node) {
return getProblem(context, node);

@@ -88,0 +94,0 @@ },

'use strict';
const {getStaticValue} = require('@eslint-community/eslint-utils');
const {newExpressionSelector} = require('./selectors/index.js');
const {switchNewExpressionToCallExpression} = require('./fix/index.js');
const isNumber = require('./utils/is-number.js');
const {isNewExpression} = require('./ast/index.js');

@@ -55,7 +55,11 @@ const ERROR = 'error';

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[newExpressionSelector('Buffer')](node) {
const method = inferMethod(node.arguments, context.getScope());
NewExpression(node) {
if (!isNewExpression(node, {name: 'Buffer'})) {
return;
}
const method = inferMethod(node.arguments, sourceCode.getScope(node));
if (method) {

@@ -62,0 +66,0 @@ return {

'use strict';
const {
not,
methodCallSelector,
callExpressionSelector,
} = require('./selectors/index.js');
isMethodCall,
isCallExpression,
isLiteral,
} = require('./ast/index.js');

@@ -17,17 +17,2 @@ const ERROR_MESSAGE_ID = 'error';

const selector = [
'Literal',
'[raw="null"]',
not([
// `Object.create(null)`, `Object.create(null, foo)`
`${methodCallSelector({object: 'Object', method: 'create', minimumArguments: 1, maximumArguments: 2})} > .arguments:first-child`,
// `useRef(null)`
`${callExpressionSelector({name: 'useRef', argumentsLength: 1})} > .arguments:first-child`,
// `React.useRef(null)`
`${methodCallSelector({object: 'React', method: 'useRef', argumentsLength: 1})} > .arguments:first-child`,
// `foo.insertBefore(bar, null)`
`${methodCallSelector({method: 'insertBefore', argumentsLength: 2})}[arguments.0.type!="SpreadElement"] > .arguments:nth-child(2)`,
]),
].join('');
const isLooseEqual = node => node.type === 'BinaryExpression' && ['==', '!='].includes(node.operator);

@@ -44,8 +29,56 @@ const isStrictEqual = node => node.type === 'BinaryExpression' && ['===', '!=='].includes(node.operator);

return {
[selector](node) {
const {parent} = node;
if (!checkStrictEquality && isStrictEqual(parent)) {
Literal(node) {
if (
// eslint-disable-next-line unicorn/no-null
!isLiteral(node, null)
|| (!checkStrictEquality && isStrictEqual(node.parent))
// `Object.create(null)`, `Object.create(null, foo)`
|| (
isMethodCall(node.parent, {
object: 'Object',
method: 'create',
minimumArguments: 1,
maximumArguments: 2,
optionalCall: false,
optionalMember: false,
})
&& node.parent.arguments[0] === node
)
// `useRef(null)`
|| (
isCallExpression(node.parent, {
name: 'useRef',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& node.parent.arguments[0] === node
)
// `React.useRef(null)`
|| (
isMethodCall(node.parent, {
object: 'React',
method: 'useRef',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& node.parent.arguments[0] === node
)
// `foo.insertBefore(bar, null)`
|| (
isMethodCall(node.parent, {
method: 'insertBefore',
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
&& node.parent.arguments[1] === node
)
) {
return;
}
const {parent} = node;
const problem = {

@@ -52,0 +85,0 @@ node,

'use strict';
const {isFunction} = require('./ast/index.js');

@@ -10,11 +11,14 @@ const MESSAGE_ID_IDENTIFIER = 'identifier';

const objectParameterSelector = [
':function > AssignmentPattern.params',
'[right.type="ObjectExpression"]',
'[right.properties.length>0]',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[objectParameterSelector](node) {
AssignmentPattern(node) {
if (!(
node.right.type === 'ObjectExpression'
&& node.right.properties.length > 0
&& isFunction(node.parent)
&& node.parent.params.includes(node)
)) {
return;
}
const {left, right} = node;

@@ -21,0 +25,0 @@

'use strict';
const {methodCallSelector, STATIC_REQUIRE_SELECTOR} = require('./selectors/index.js');
const {isStaticRequire, isMethodCall, isLiteral} = require('./ast/index.js');

@@ -9,28 +9,9 @@ const MESSAGE_ID = 'no-process-exit';

const importWorkerThreadsSelector = [
// `require('worker_threads')`
[
STATIC_REQUIRE_SELECTOR,
'[arguments.0.value="worker_threads"]',
].join(''),
// `import workerThreads from 'worker_threads'`
[
'ImportDeclaration',
'[source.type="Literal"]',
'[source.value="worker_threads"]',
].join(''),
].join(', ');
const processOnOrOnceCallSelector = methodCallSelector({
object: 'process',
methods: ['on', 'once'],
minimumArguments: 1,
});
const processExitCallSelector = methodCallSelector({
object: 'process',
method: 'exit',
});
const isWorkerThreads = node =>
isLiteral(node, 'node:worker_threads')
|| isLiteral(node, 'worker_threads');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const startsWithHashBang = context.getSourceCode().lines[0].indexOf('#!') === 0;
const startsWithHashBang = context.sourceCode.lines[0].indexOf('#!') === 0;

@@ -47,33 +28,67 @@ if (startsWithHashBang) {

return {
// Check `worker_threads` require / import
[importWorkerThreadsSelector]() {
// `require('worker_threads')`
context.on('CallExpression', callExpression => {
if (
isStaticRequire(callExpression)
&& isWorkerThreads(callExpression.arguments[0])
) {
requiredWorkerThreadsModule = true;
},
// Check `process.on` / `process.once` call
[processOnOrOnceCallSelector](node) {
}
});
// `import workerThreads from 'worker_threads'`
context.on('ImportDeclaration', importDeclaration => {
if (
importDeclaration.source.type === 'Literal'
&& isWorkerThreads(importDeclaration.source)
) {
requiredWorkerThreadsModule = true;
}
});
// Check `process.on` / `process.once` call
context.on('CallExpression', node => {
if (isMethodCall(node, {
object: 'process',
methods: ['on', 'once'],
minimumArguments: 1,
optionalCall: false,
optionalMember: false,
})) {
processEventHandler = node;
},
// Check `process.exit` call
[processExitCallSelector](node) {
if (!processEventHandler) {
problemNodes.push(node);
}
},
'CallExpression:exit'(node) {
if (node === processEventHandler) {
processEventHandler = undefined;
}
},
* 'Program:exit'() {
if (!requiredWorkerThreadsModule) {
for (const node of problemNodes) {
yield {
node,
messageId: MESSAGE_ID,
};
}
}
},
};
}
});
context.onExit('CallExpression', node => {
if (node === processEventHandler) {
processEventHandler = undefined;
}
});
// Check `process.exit` call
context.on('CallExpression', node => {
if (
!processEventHandler
&& isMethodCall(node, {
object: 'process',
method: 'exit',
optionalCall: false,
optionalMember: false,
})
) {
problemNodes.push(node);
}
});
context.onExit('Program', function * () {
if (requiredWorkerThreadsModule) {
return;
}
for (const node of problemNodes) {
yield {
node,
messageId: MESSAGE_ID,
};
}
});
};

@@ -80,0 +95,0 @@

@@ -12,9 +12,2 @@ 'use strict';

const selector = [
':matches(ClassDeclaration, ClassExpression)',
':not([superClass], [decorators.length>0])',
'[body.type="ClassBody"]',
'[body.body.length>0]',
].join('');
const isEqualToken = ({type, value}) => type === 'Punctuator' && value === '=';

@@ -55,4 +48,2 @@ const isDeclarationOfExportDefaultDeclaration = node =>

|| (Array.isArray(decorators) && decorators.length > 0)
// TODO: Remove this when we drop support for `@typescript-eslint/parser` v4
|| key.type === 'TSPrivateIdentifier'
) {

@@ -202,18 +193,22 @@ return false;

function create(context) {
const sourceCode = context.getSourceCode();
context.on(['ClassDeclaration', 'ClassExpression'], node => {
if (
node.superClass
|| (node.decorators && node.decorators.length > 0)
|| node.body.type !== 'ClassBody'
|| node.body.body.length === 0
|| node.body.body.some(node => !isStaticMember(node))
) {
return;
}
return {
[selector](node) {
if (node.body.body.some(node => !isStaticMember(node))) {
return;
}
const {sourceCode} = context;
return {
node,
loc: getClassHeadLocation(node, sourceCode),
messageId: MESSAGE_ID,
fix: switchClassToObject(node, sourceCode),
};
},
};
return {
node,
loc: getClassHeadLocation(node, sourceCode),
messageId: MESSAGE_ID,
fix: switchClassToObject(node, sourceCode),
};
});
}

@@ -220,0 +215,0 @@

'use strict';
const {getStaticValue, getPropertyName} = require('@eslint-community/eslint-utils');
const {methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -15,3 +15,3 @@ const MESSAGE_ID_OBJECT = 'no-thenable-object';

const isStringThen = (node, context) =>
getStaticValue(node, context.getScope())?.value === 'then';
getStaticValue(node, context.sourceCode.getScope(node))?.value === 'then';

@@ -24,4 +24,13 @@ const cases = [

{
selector: 'ObjectExpression > Property.properties > .key',
test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
selector: 'ObjectExpression',
* getNodes(node, context) {
for (const property of node.properties) {
if (
property.type === 'Property'
&& getPropertyName(property, context.sourceCode.getScope(property)) === 'then'
) {
yield property.key;
}
}
},
messageId: MESSAGE_ID_OBJECT,

@@ -34,4 +43,8 @@ },

{
selector: ':matches(PropertyDefinition, MethodDefinition) > .key',
test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
selectors: ['PropertyDefinition', 'MethodDefinition'],
* getNodes(node, context) {
if (getPropertyName(node, context.sourceCode.getScope(node)) === 'then') {
yield node.key;
}
},
messageId: MESSAGE_ID_CLASS,

@@ -42,4 +55,12 @@ },

{
selector: 'AssignmentExpression > MemberExpression.left > .property',
test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
selector: 'MemberExpression',
* getNodes(node, context) {
if (!(node.parent.type === 'AssignmentExpression' && node.parent.left === node)) {
return;
}
if (getPropertyName(node, context.sourceCode.getScope(node)) === 'then') {
yield node.property;
}
},
messageId: MESSAGE_ID_OBJECT,

@@ -50,26 +71,55 @@ },

{
selector: [
methodCallSelector({
objects: ['Object', 'Reflect'],
method: 'defineProperty',
minimumArguments: 3,
}),
'[arguments.0.type!="SpreadElement"]',
' > .arguments:nth-child(2)',
].join(''),
test: isStringThen,
selector: 'CallExpression',
* getNodes(node, context) {
if (!(
isMethodCall(node, {
objects: ['Object', 'Reflect'],
method: 'defineProperty',
minimumArguments: 3,
optionalCall: false,
optionalMember: false,
})
&& node.arguments[0].type !== 'SpreadElement'
)) {
return;
}
const [, secondArgument] = node.arguments;
if (isStringThen(secondArgument, context)) {
yield secondArgument;
}
},
messageId: MESSAGE_ID_OBJECT,
},
// `Object.fromEntries(['then', …])`
// `Object.fromEntries([['then', …]])`
{
selector: [
methodCallSelector({
object: 'Object',
method: 'fromEntries',
argumentsLength: 1,
}),
' > ArrayExpression.arguments:nth-child(1)',
' > .elements:nth-child(1)',
].join(''),
test: isStringThen,
selector: 'CallExpression',
* getNodes(node, context) {
if (!(
isMethodCall(node, {
object: 'Object',
method: 'fromEntries',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& node.arguments[0].type === 'ArrayExpression'
)) {
return;
}
for (const pairs of node.arguments[0].elements) {
if (
pairs?.type === 'ArrayExpression'
&& pairs.elements[0]
&& pairs.elements[0].type !== 'SpreadElement'
) {
const [key] = pairs.elements;
if (isStringThen(key, context)) {
yield key;
}
}
}
},
messageId: MESSAGE_ID_OBJECT,

@@ -79,3 +129,12 @@ },

{
selector: 'ExportSpecifier.specifiers > Identifier.exported[name="then"]',
selector: 'Identifier',
* getNodes(node) {
if (
node.name === 'then'
&& node.parent.type === 'ExportSpecifier'
&& node.parent.exported === node
) {
yield node;
}
},
messageId: MESSAGE_ID_EXPORT,

@@ -86,3 +145,14 @@ },

{
selector: 'ExportNamedDeclaration > :matches(FunctionDeclaration, ClassDeclaration).declaration > Identifier[name="then"].id',
selector: 'Identifier',
* getNodes(node) {
if (
node.name === 'then'
&& (node.parent.type === 'FunctionDeclaration' || node.parent.type === 'ClassDeclaration')
&& node.parent.id === node
&& node.parent.parent.type === 'ExportNamedDeclaration'
&& node.parent.parent.declaration === node.parent
) {
yield node;
}
},
messageId: MESSAGE_ID_EXPORT,

@@ -92,5 +162,15 @@ },

{
selector: 'ExportNamedDeclaration > VariableDeclaration.declaration',
selector: 'VariableDeclaration',
* getNodes(node, context) {
if (!(node.parent.type === 'ExportNamedDeclaration' && node.parent.declaration === node)) {
return;
}
for (const variable of context.sourceCode.getDeclaredVariables(node)) {
if (variable.name === 'then') {
yield * variable.identifiers;
}
}
},
messageId: MESSAGE_ID_EXPORT,
getNodes: (node, context) => context.getDeclaredVariables(node).flatMap(({name, identifiers}) => name === 'then' ? identifiers : []),
},

@@ -100,23 +180,12 @@ ];

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => Object.fromEntries(
cases.map(({selector, test, messageId, getNodes}) => [
selector,
function * (node) {
if (getNodes) {
for (const problematicNode of getNodes(node, context)) {
yield {node: problematicNode, messageId};
}
return;
const create = context => {
for (const {selector, selectors, messageId, getNodes} of cases) {
context.on(selector ?? selectors, function * (node) {
for (const problematicNode of getNodes(node, context)) {
yield {node: problematicNode, messageId};
}
});
}
};
if (test && !test(node, context)) {
return;
}
yield {node, messageId};
},
]),
);
/** @type {import('eslint').Rule.RuleModule} */

@@ -123,0 +192,0 @@ module.exports = {

'use strict';
const {matches} = require('./selectors/index.js');
const MESSAGE_ID = 'no-this-assignment';

@@ -9,27 +7,22 @@ const messages = {

const variableDeclaratorSelector = [
'VariableDeclarator',
'[init.type="ThisExpression"]',
'[id.type="Identifier"]',
].join('');
function getProblem(variableNode, valueNode) {
if (
variableNode.type !== 'Identifier'
|| valueNode?.type !== 'ThisExpression'
) {
return;
}
const assignmentExpressionSelector = [
'AssignmentExpression',
'[right.type="ThisExpression"]',
'[left.type="Identifier"]',
].join('');
return {
node: valueNode.parent,
data: {name: variableNode.name},
messageId: MESSAGE_ID,
};
}
const selector = matches([variableDeclaratorSelector, assignmentExpressionSelector]);
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[selector](node) {
const variable = node.type === 'AssignmentExpression' ? node.left : node.id;
return {
node,
data: {name: variable.name},
messageId: MESSAGE_ID,
};
},
});
const create = context => {
context.on('VariableDeclarator', node => getProblem(node.id, node.init));
context.on('AssignmentExpression', node => getProblem(node.left, node.right));
};

@@ -36,0 +29,0 @@ /** @type {import('eslint').Rule.RuleModule} */

'use strict';
const isShadowed = require('./utils/is-shadowed.js');
const {matches} = require('./selectors/index.js');
const {isLiteral} = require('./ast/index.js');
const {
addParenthesizesToReturnOrThrowExpression,
removeSpacesAfter,
} = require('./fix/index.js');
const {removeSpacesAfter} = require('./fix/index.js');
const isOnSameLine = require('./utils/is-on-same-line.js');
const needsSemicolon = require('./utils/needs-semicolon.js');
const {
needsSemicolon,
isParenthesized,
} = require('./utils/parentheses.js');
isOnSameLine,
isShadowed,
} = require('./utils/index.js');

@@ -21,11 +21,2 @@ const MESSAGE_ID_ERROR = 'no-typeof-undefined/error';

const selector = [
'BinaryExpression',
matches(['===', '!==', '==', '!='].map(operator => `[operator="${operator}"]`)),
'[left.type="UnaryExpression"]',
'[left.operator="typeof"]',
'[left.prefix]',
'[right.type="Literal"]',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */

@@ -39,13 +30,26 @@ const create = context => {

};
const {sourceCode} = context;
return {
[selector](binaryExpression) {
const {left: typeofNode, right: undefinedString, operator} = binaryExpression;
if (undefinedString.value !== 'undefined') {
BinaryExpression(binaryExpression) {
if (!(
(
binaryExpression.operator === '==='
|| binaryExpression.operator === '!=='
|| binaryExpression.operator === '=='
|| binaryExpression.operator === '!='
)
&& binaryExpression.left.type === 'UnaryExpression'
&& binaryExpression.left.operator === 'typeof'
&& binaryExpression.left.prefix
&& isLiteral(binaryExpression.right, 'undefined')
)) {
return;
}
const {left: typeofNode, right: undefinedString, operator} = binaryExpression;
const valueNode = typeofNode.argument;
const isGlobalVariable = valueNode.type === 'Identifier'
&& !isShadowed(context.getScope(), valueNode);
&& !isShadowed(sourceCode.getScope(valueNode), valueNode);

@@ -56,3 +60,2 @@ if (!checkGlobalVariables && isGlobalVariable) {

const sourceCode = context.getSourceCode();
const [typeofToken, secondToken] = sourceCode.getFirstTokens(typeofNode, 2);

@@ -59,0 +62,0 @@

@@ -49,3 +49,3 @@ 'use strict';

const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const awaitToken = sourceCode.getFirstToken(node);

@@ -52,0 +52,0 @@ const problem = {

@@ -16,9 +16,11 @@ 'use strict';

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
'ArrayPattern[elements.length>=3]'(node) {
ArrayPattern(node) {
const {elements, parent} = node;
if (!elements.some((element, index, elements) => isCommaFollowedWithComma(element, index, elements))) {
if (
elements.length < 3
|| !elements.some((element, index, elements) => isCommaFollowedWithComma(element, index, elements))) {
return;

@@ -25,0 +27,0 @@ }

@@ -5,4 +5,4 @@ 'use strict';

getParenthesizedRange,
} = require('./utils/parentheses.js');
const toLocation = require('./utils/to-location.js');
toLocation,
} = require('./utils/index.js');

@@ -14,15 +14,12 @@ const MESSAGE_ID_ERROR = 'no-unreadable-iife';

const selector = [
'CallExpression',
' > ',
'ArrowFunctionExpression.callee',
' > ',
':not(BlockStatement).body',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const sourceCode = context.getSourceCode();
if (!isParenthesized(node, sourceCode)) {
CallExpression(callExpression) {
const {sourceCode} = context;
if (
callExpression.callee.type !== 'ArrowFunctionExpression'
|| callExpression.callee.body.type === 'BlockStatement'
|| !isParenthesized(callExpression.callee.body, sourceCode)
) {
return;

@@ -32,4 +29,4 @@ }

return {
node,
loc: toLocation(getParenthesizedRange(node, sourceCode), sourceCode),
node: callExpression,
loc: toLocation(getParenthesizedRange(callExpression.callee.body, sourceCode), sourceCode),
messageId: MESSAGE_ID_ERROR,

@@ -36,0 +33,0 @@ };

@@ -83,2 +83,3 @@ 'use strict';

const create = context => {
const {sourceCode} = context;
const getPropertyDisplayName = property => {

@@ -93,3 +94,3 @@ if (property.key.type === 'Identifier') {

return context.getSourceCode().getText(property.key);
return sourceCode.getText(property.key);
};

@@ -216,4 +217,4 @@

return {
'Program:exit'() {
const scopes = getScopes(context.getScope());
'Program:exit'(program) {
const scopes = getScopes(sourceCode.getScope(program));
for (const scope of scopes) {

@@ -220,0 +221,0 @@ if (scope.type === 'global') {

'use strict';
const {matches} = require('./selectors/index.js');
const {

@@ -15,26 +14,25 @@ isParenthesized,

const selector = [
'ObjectExpression',
' > ',
'SpreadElement.properties',
' > ',
'LogicalExpression.argument',
matches([
'[operator="||"]',
'[operator="??"]',
]),
' > ',
'ObjectExpression[properties.length=0].right',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](emptyObject) {
ObjectExpression(node) {
if (!(
node.properties.length === 0
&& node.parent.type === 'LogicalExpression'
&& node.parent.right === node
&& (node.parent.operator === '||' || node.parent.operator === '??')
&& node.parent.parent.type === 'SpreadElement'
&& node.parent.parent.argument === node.parent
&& node.parent.parent.parent.type === 'ObjectExpression'
&& node.parent.parent.parent.properties.includes(node.parent.parent)
)) {
return;
}
return {
node: emptyObject,
node,
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
const sourceCode = context.getSourceCode();
const logicalExpression = emptyObject.parent;
const {sourceCode} = context;
const logicalExpression = node.parent;
const {left} = logicalExpression;

@@ -41,0 +39,0 @@ const isLeftObjectParenthesized = isParenthesized(left, sourceCode);

'use strict';
const {methodCallSelector, matches, memberExpressionSelector} = require('./selectors/index.js');
const isSameReference = require('./utils/is-same-reference.js');
const {getParenthesizedRange} = require('./utils/parentheses.js');
const {isMethodCall, isMemberExpression} = require('./ast/index.js');
const {
getParenthesizedRange,
isSameReference,
isLogicalExpression,
} = require('./utils/index.js');

@@ -11,25 +14,9 @@ const messages = {

const logicalExpressionSelector = [
'LogicalExpression',
matches(['[operator="||"]', '[operator="&&"]']),
].join('');
// We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
const lengthCompareZeroSelector = [
logicalExpressionSelector,
' > ',
'BinaryExpression',
memberExpressionSelector({path: 'left', property: 'length'}),
'[right.type="Literal"]',
'[right.raw="0"]',
].join('');
const zeroLengthCheckSelector = [
lengthCompareZeroSelector,
'[operator="==="]',
].join('');
const nonZeroLengthCheckSelector = [
lengthCompareZeroSelector,
matches(['[operator=">"]', '[operator="!=="]']),
].join('');
const arraySomeCallSelector = methodCallSelector('some');
const arrayEveryCallSelector = methodCallSelector('every');
const isLengthCompareZero = node =>
node.type === 'BinaryExpression'
&& node.right.type === 'Literal'
&& node.right.raw === '0'
&& isMemberExpression(node.left, {property: 'length', optional: false})
&& isLogicalExpression(node.parent);

@@ -87,17 +74,33 @@ function flatLogicalExpression(node) {

return {
[zeroLengthCheckSelector](node) {
zeroLengthChecks.add(node);
BinaryExpression(node) {
if (isLengthCompareZero(node)) {
const {operator} = node;
if (operator === '===') {
zeroLengthChecks.add(node);
} else if (operator === '>' || operator === '!==') {
nonZeroLengthChecks.add(node);
}
}
},
[nonZeroLengthCheckSelector](node) {
nonZeroLengthChecks.add(node);
CallExpression(node) {
if (
isMethodCall(node, {
optionalCall: false,
optionalMember: false,
computed: false,
})
&& node.callee.property.type === 'Identifier'
) {
if (node.callee.property.name === 'some') {
arraySomeCalls.add(node);
} else if (node.callee.property.name === 'every') {
arrayEveryCalls.add(node);
}
}
},
[arraySomeCallSelector](node) {
arraySomeCalls.add(node);
LogicalExpression(node) {
if (isLogicalExpression(node)) {
logicalExpressions.push(node);
}
},
[arrayEveryCallSelector](node) {
arrayEveryCalls.add(node);
},
[logicalExpressionSelector](node) {
logicalExpressions.push(node);
},
* 'Program:exit'() {

@@ -119,3 +122,3 @@ const nodes = new Set(

fix(fixer) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {left, right} = node.parent;

@@ -122,0 +125,0 @@ const leftRange = getParenthesizedRange(left, sourceCode);

'use strict';
const {matches, methodCallSelector} = require('./selectors/index.js');
const {getParenthesizedRange} = require('./utils/parentheses.js');
const {getParenthesizedRange} = require('./utils/index.js');
const {isFunction, isMethodCall} = require('./ast/index.js');

@@ -12,19 +12,2 @@ const MESSAGE_ID_RESOLVE = 'resolve';

const selector = [
methodCallSelector({
object: 'Promise',
methods: ['resolve', 'reject'],
}),
matches([
'ArrowFunctionExpression > .body',
'ReturnStatement > .argument',
'YieldExpression[delegate!=true] > .argument',
]),
].join('');
const functionTypes = new Set([
'ArrowFunctionExpression',
'FunctionDeclaration',
'FunctionExpression',
]);
function getFunctionNode(node) {

@@ -34,3 +17,3 @@ let isInTryStatement = false;

for (; node; node = node.parent) {
if (functionTypes.has(node.type)) {
if (isFunction(node)) {
functionNode = node;

@@ -178,6 +161,31 @@ break;

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[selector](callExpression) {
CallExpression(callExpression) {
if (!(
isMethodCall(callExpression, {
object: 'Promise',
methods: ['resolve', 'reject'],
optionalCall: false,
optionalMember: false,
})
&& (
(
callExpression.parent.type === 'ArrowFunctionExpression'
&& callExpression.parent.body === callExpression
)
|| (
callExpression.parent.type === 'ReturnStatement'
&& callExpression.parent.argument === callExpression
)
|| (
callExpression.parent.type === 'YieldExpression'
&& !callExpression.parent.delegate && callExpression.parent.argument === callExpression
)
)
)) {
return;
}
const {functionNode, isInTryStatement} = getFunctionNode(callExpression);

@@ -184,0 +192,0 @@ if (!functionNode || !(functionNode.async || isPromiseCallback(functionNode))) {

'use strict';
const {isCommaToken} = require('@eslint-community/eslint-utils');
const {
matches,
newExpressionSelector,
methodCallSelector,
} = require('./selectors/index.js');
const typedArray = require('./shared/typed-array.js');

@@ -14,7 +9,7 @@ const {

} = require('./fix/index.js');
const isOnSameLine = require('./utils/is-on-same-line.js');
const {
isParenthesized,
} = require('./utils/parentheses.js');
const {isNewExpression} = require('./ast/index.js');
isOnSameLine,
} = require('./utils/index.js');
const {isNewExpression, isMethodCall, isCallOrNewExpression} = require('./ast/index.js');

@@ -34,73 +29,7 @@ const SPREAD_IN_LIST = 'spread-in-list';

const uselessSpreadInListSelector = matches([
'ArrayExpression > SpreadElement.elements > ArrayExpression.argument',
'ObjectExpression > SpreadElement.properties > ObjectExpression.argument',
'CallExpression > SpreadElement.arguments > ArrayExpression.argument',
'NewExpression > SpreadElement.arguments > ArrayExpression.argument',
]);
const isSingleArraySpread = node =>
node.type === 'ArrayExpression'
&& node.elements.length === 1
&& node.elements[0]?.type === 'SpreadElement';
const singleArraySpreadSelector = [
'ArrayExpression',
'[elements.length=1]',
'[elements.0.type="SpreadElement"]',
].join('');
const uselessIterableToArraySelector = matches([
[
matches([
newExpressionSelector({names: ['Map', 'WeakMap', 'Set', 'WeakSet'], argumentsLength: 1}),
newExpressionSelector({names: typedArray, minimumArguments: 1}),
methodCallSelector({
object: 'Promise',
methods: ['all', 'allSettled', 'any', 'race'],
argumentsLength: 1,
}),
methodCallSelector({
objects: ['Array', ...typedArray],
method: 'from',
argumentsLength: 1,
}),
methodCallSelector({object: 'Object', method: 'fromEntries', argumentsLength: 1}),
]),
' > ',
`${singleArraySpreadSelector}.arguments:first-child`,
].join(''),
`ForOfStatement > ${singleArraySpreadSelector}.right`,
`YieldExpression[delegate=true] > ${singleArraySpreadSelector}.argument`,
]);
const uselessArrayCloneSelector = [
`${singleArraySpreadSelector} > .elements:first-child > .argument`,
matches([
// Array methods returns a new array
methodCallSelector([
'concat',
'copyWithin',
'filter',
'flat',
'flatMap',
'map',
'slice',
'splice',
]),
// `String#split()`
methodCallSelector('split'),
// `Object.keys()` and `Object.values()`
methodCallSelector({object: 'Object', methods: ['keys', 'values'], argumentsLength: 1}),
// `await Promise.all()` and `await Promise.allSettled`
[
'AwaitExpression',
methodCallSelector({
object: 'Promise',
methods: ['all', 'allSettled'],
argumentsLength: 1,
path: 'argument',
}),
].join(''),
// `Array.from()`, `Array.of()`
methodCallSelector({object: 'Array', methods: ['from', 'of']}),
// `new Array()`
newExpressionSelector('Array'),
]),
].join('');
const parentDescriptions = {

@@ -153,7 +82,7 @@ ArrayExpression: 'array literal',

// `[...value]`
// ^
// ^
yield fixer.remove(closingBracketToken);
// `[...value,]`
// ^
// ^
if (isCommaToken(commaToken)) {

@@ -188,124 +117,256 @@ yield fixer.remove(commaToken);

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[uselessSpreadInListSelector](spreadObject) {
const spreadElement = spreadObject.parent;
const spreadToken = sourceCode.getFirstToken(spreadElement);
const parentType = spreadElement.parent.type;
// Useless spread in list
context.on(['ArrayExpression', 'ObjectExpression'], node => {
if (!(
node.parent.type === 'SpreadElement'
&& node.parent.argument === node
&& (
(
node.type === 'ObjectExpression'
&& node.parent.parent.type === 'ObjectExpression'
&& node.parent.parent.properties.includes(node.parent)
)
|| (
node.type === 'ArrayExpression'
&& (
(
node.parent.parent.type === 'ArrayExpression'
&& node.parent.parent.elements.includes(node.parent)
)
|| (
isCallOrNewExpression(node.parent.parent)
&& node.parent.parent.arguments.includes(node.parent)
)
)
)
)
)) {
return;
}
return {
node: spreadToken,
messageId: SPREAD_IN_LIST,
data: {
argumentType: spreadObject.type === 'ArrayExpression' ? 'array' : 'object',
parentDescription: parentDescriptions[parentType],
},
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
// `[...[foo]]`
// ^^^
yield fixer.remove(spreadToken);
const spreadObject = node;
const spreadElement = spreadObject.parent;
const spreadToken = sourceCode.getFirstToken(spreadElement);
const parentType = spreadElement.parent.type;
// `[...(( [foo] ))]`
// ^^ ^^
yield * removeParentheses(spreadObject, fixer, sourceCode);
return {
node: spreadToken,
messageId: SPREAD_IN_LIST,
data: {
argumentType: spreadObject.type === 'ArrayExpression' ? 'array' : 'object',
parentDescription: parentDescriptions[parentType],
},
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
// `[...[foo]]`
// ^^^
yield fixer.remove(spreadToken);
// `[...[foo]]`
// ^
const firstToken = sourceCode.getFirstToken(spreadObject);
yield fixer.remove(firstToken);
// `[...(( [foo] ))]`
// ^^ ^^
yield * removeParentheses(spreadObject, fixer, sourceCode);
const [
penultimateToken,
lastToken,
] = sourceCode.getLastTokens(spreadObject, 2);
// `[...[foo]]`
// ^
const firstToken = sourceCode.getFirstToken(spreadObject);
yield fixer.remove(firstToken);
// `[...[foo]]`
// ^
yield fixer.remove(lastToken);
const [
penultimateToken,
lastToken,
] = sourceCode.getLastTokens(spreadObject, 2);
// `[...[foo,]]`
// ^
if (isCommaToken(penultimateToken)) {
yield fixer.remove(penultimateToken);
}
// `[...[foo]]`
// ^
yield fixer.remove(lastToken);
if (parentType !== 'CallExpression' && parentType !== 'NewExpression') {
return;
}
// `[...[foo,]]`
// ^
if (isCommaToken(penultimateToken)) {
yield fixer.remove(penultimateToken);
}
const commaTokens = getCommaTokens(spreadObject, sourceCode);
for (const [index, commaToken] of commaTokens.entries()) {
if (spreadObject.elements[index]) {
continue;
}
if (parentType !== 'CallExpression' && parentType !== 'NewExpression') {
return;
}
// `call(...[foo, , bar])`
// ^ Replace holes with `undefined`
yield fixer.insertTextBefore(commaToken, 'undefined');
const commaTokens = getCommaTokens(spreadObject, sourceCode);
for (const [index, commaToken] of commaTokens.entries()) {
if (spreadObject.elements[index]) {
continue;
}
},
};
},
[uselessIterableToArraySelector](arrayExpression) {
const {parent} = arrayExpression;
let parentDescription = '';
let messageId = ITERABLE_TO_ARRAY;
switch (parent.type) {
case 'ForOfStatement': {
messageId = ITERABLE_TO_ARRAY_IN_FOR_OF;
break;
}
case 'YieldExpression': {
messageId = ITERABLE_TO_ARRAY_IN_YIELD_STAR;
break;
// `call(...[foo, , bar])`
// ^ Replace holes with `undefined`
yield fixer.insertTextBefore(commaToken, 'undefined');
}
},
};
});
case 'NewExpression': {
parentDescription = `new ${parent.callee.name}(…)`;
break;
}
// Useless iterable to array
context.on('ArrayExpression', arrayExpression => {
if (!isSingleArraySpread(arrayExpression)) {
return;
}
case 'CallExpression': {
parentDescription = `${parent.callee.object.name}.${parent.callee.property.name}(…)`;
break;
}
// No default
const {parent} = arrayExpression;
if (!(
(parent.type === 'ForOfStatement' && parent.right === arrayExpression)
|| (parent.type === 'YieldExpression' && parent.delegate && parent.argument === arrayExpression)
|| (
(
isNewExpression(parent, {names: ['Map', 'WeakMap', 'Set', 'WeakSet'], argumentsLength: 1})
|| isNewExpression(parent, {names: typedArray, minimumArguments: 1})
|| isMethodCall(parent, {
object: 'Promise',
methods: ['all', 'allSettled', 'any', 'race'],
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
|| isMethodCall(parent, {
objects: ['Array', ...typedArray],
method: 'from',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
|| isMethodCall(parent, {
object: 'Object',
method: 'fromEntries',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
)
&& parent.arguments[0] === arrayExpression
)
)) {
return;
}
let parentDescription = '';
let messageId = ITERABLE_TO_ARRAY;
switch (parent.type) {
case 'ForOfStatement': {
messageId = ITERABLE_TO_ARRAY_IN_FOR_OF;
break;
}
return {
node: arrayExpression,
messageId,
data: {parentDescription},
fix: fixer => unwrapSingleArraySpread(fixer, arrayExpression, sourceCode),
};
},
[uselessArrayCloneSelector](node) {
const arrayExpression = node.parent.parent;
const problem = {
node: arrayExpression,
messageId: CLONE_ARRAY,
};
case 'YieldExpression': {
messageId = ITERABLE_TO_ARRAY_IN_YIELD_STAR;
break;
}
if (
// `[...new Array(1)]` -> `new Array(1)` is not safe to fix since there are holes
isNewExpression(node, {name: 'Array'})
// `[...foo.slice(1)]` -> `foo.slice(1)` is not safe to fix since `foo` can be a string
|| (
node.type === 'CallExpression'
&& node.callee.type === 'MemberExpression'
&& node.callee.property.type === 'Identifier'
&& node.callee.property.name === 'slice'
)
) {
return problem;
case 'NewExpression': {
parentDescription = `new ${parent.callee.name}(…)`;
break;
}
return Object.assign(problem, {
fix: fixer => unwrapSingleArraySpread(fixer, arrayExpression, sourceCode),
});
},
};
case 'CallExpression': {
parentDescription = `${parent.callee.object.name}.${parent.callee.property.name}(…)`;
break;
}
// No default
}
return {
node: arrayExpression,
messageId,
data: {parentDescription},
fix: fixer => unwrapSingleArraySpread(fixer, arrayExpression, sourceCode),
};
});
// Useless array clone
context.on('ArrayExpression', arrayExpression => {
if (!isSingleArraySpread(arrayExpression)) {
return;
}
const node = arrayExpression.elements[0].argument;
if (!(
// Array methods returns a new array
isMethodCall(node, {
methods: [
'concat',
'copyWithin',
'filter',
'flat',
'flatMap',
'map',
'slice',
'splice',
'toReversed',
'toSorted',
'toSpliced',
'with',
],
optionalCall: false,
optionalMember: false,
})
// `String#split()`
|| isMethodCall(node, {
method: 'split',
optionalCall: false,
optionalMember: false,
})
// `Object.keys()` and `Object.values()`
|| isMethodCall(node, {
object: 'Object',
methods: ['keys', 'values'],
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
// `await Promise.all()` and `await Promise.allSettled`
|| (
node.type === 'AwaitExpression'
&& isMethodCall(node.argument, {
object: 'Promise',
methods: ['all', 'allSettled'],
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
)
// `Array.from()`, `Array.of()`
|| isMethodCall(node, {
object: 'Array',
methods: ['from', 'of'],
optionalCall: false,
optionalMember: false,
})
// `new Array()`
|| isNewExpression(node, {name: 'Array'})
)) {
return;
}
const problem = {
node: arrayExpression,
messageId: CLONE_ARRAY,
};
if (
// `[...new Array(1)]` -> `new Array(1)` is not safe to fix since there are holes
isNewExpression(node, {name: 'Array'})
// `[...foo.slice(1)]` -> `foo.slice(1)` is not safe to fix since `foo` can be a string
|| (
node.type === 'CallExpression'
&& node.callee.type === 'MemberExpression'
&& node.callee.property.type === 'Identifier'
&& node.callee.property.name === 'slice'
)
) {
return problem;
}
return Object.assign(problem, {
fix: fixer => unwrapSingleArraySpread(fixer, arrayExpression, sourceCode),
});
});
};

@@ -312,0 +373,0 @@

@@ -16,3 +16,3 @@ 'use strict';

const create = context => ({
* 'SwitchStatement[cases.length>1]'(switchStatement) {
* SwitchStatement(switchStatement) {
const {cases} = switchStatement;

@@ -47,3 +47,3 @@

node,
loc: getSwitchCaseHeadLocation(node, context.getSourceCode()),
loc: getSwitchCaseHeadLocation(node, context.sourceCode),
messageId: MESSAGE_ID_ERROR,

@@ -50,0 +50,0 @@ suggest: [

'use strict';
const {isCommaToken} = require('@eslint-community/eslint-utils');
const {replaceNodeOrTokenAndSpacesBefore} = require('./fix/index.js');
const {isUndefined} = require('./ast/index.js');
const {isUndefined, isFunction} = require('./ast/index.js');

@@ -11,28 +11,2 @@ const messageId = 'no-useless-undefined';

const getSelector = (parent, property) =>
`${parent} > Identifier.${property}[name="undefined"]`;
// `return undefined`
const returnSelector = getSelector('ReturnStatement', 'argument');
// `yield undefined`
const yieldSelector = getSelector('YieldExpression[delegate!=true]', 'argument');
// `() => undefined`
const arrowFunctionSelector = getSelector('ArrowFunctionExpression', 'body');
// `let foo = undefined` / `var foo = undefined`
const variableInitSelector = getSelector(
[
'VariableDeclaration',
'[kind!="const"]',
'>',
'VariableDeclarator',
].join(''),
'init',
);
// `const {foo = undefined} = {}`
const assignmentPatternSelector = getSelector('AssignmentPattern', 'right');
const compareFunctionNames = new Set([

@@ -110,7 +84,12 @@ 'is',

const isTypeScriptFile = context =>
/\.(?:ts|mts|cts|tsx)$/i.test(context.physicalFilename);
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const listener = (fix, checkFunctionReturnType) => node => {
const {sourceCode} = context;
const getProblem = (node, fix, checkFunctionReturnType) => {
if (checkFunctionReturnType) {
const functionNode = getFunction(context.getScope());
const functionNode = getFunction(sourceCode.getScope(node));
if (functionNode?.returnType) {

@@ -124,7 +103,6 @@ return;

messageId,
fix: fixer => fix(node, fixer),
fix,
};
};
const sourceCode = context.getSourceCode();
const options = {

@@ -138,79 +116,156 @@ checkArguments: true,

const listeners = {
[returnSelector]: listener(
removeNodeAndLeadingSpace,
/* CheckFunctionReturnType */ true,
),
[yieldSelector]: listener(removeNodeAndLeadingSpace),
[arrowFunctionSelector]: listener(
(node, fixer) => replaceNodeOrTokenAndSpacesBefore(node, ' {}', fixer, sourceCode),
/* CheckFunctionReturnType */ true,
),
[variableInitSelector]: listener(
(node, fixer) => fixer.removeRange([node.parent.id.range[1], node.range[1]]),
),
[assignmentPatternSelector]: listener(
(node, fixer) => fixer.removeRange([node.parent.left.range[1], node.range[1]]),
),
};
// `return undefined`
context.on('Identifier', node => {
if (
isUndefined(node)
&& node.parent.type === 'ReturnStatement'
&& node.parent.argument === node
) {
return getProblem(
node,
fixer => removeNodeAndLeadingSpace(node, fixer),
/* CheckFunctionReturnType */ true,
);
}
});
if (options.checkArguments) {
listeners.CallExpression = node => {
if (shouldIgnore(node.callee)) {
return;
}
// `yield undefined`
context.on('Identifier', node => {
if (
isUndefined(node)
&& node.parent.type === 'YieldExpression'
&& !node.parent.delegate
&& node.parent.argument === node
) {
return getProblem(
node,
fixer => removeNodeAndLeadingSpace(node, fixer),
);
}
});
const argumentNodes = node.arguments;
// `() => undefined`
context.on('Identifier', node => {
if (
isUndefined(node)
&& node.parent.type === 'ArrowFunctionExpression'
&& node.parent.body === node
) {
return getProblem(
node,
fixer => replaceNodeOrTokenAndSpacesBefore(node, ' {}', fixer, sourceCode),
/* CheckFunctionReturnType */ true,
);
}
});
// Ignore arguments in `Function#bind()`, but not `this` argument
if (isFunctionBindCall(node) && argumentNodes.length !== 1) {
return;
}
// `let foo = undefined` / `var foo = undefined`
context.on('Identifier', node => {
if (
isUndefined(node)
&& node.parent.type === 'VariableDeclarator'
&& node.parent.init === node
&& node.parent.parent.type === 'VariableDeclaration'
&& node.parent.parent.kind !== 'const'
&& node.parent.parent.declarations.includes(node.parent)
) {
return getProblem(
node,
fixer => fixer.removeRange([node.parent.id.range[1], node.range[1]]),
/* CheckFunctionReturnType */ true,
);
}
});
const undefinedArguments = [];
for (let index = argumentNodes.length - 1; index >= 0; index--) {
const node = argumentNodes[index];
if (isUndefined(node)) {
undefinedArguments.unshift(node);
} else {
break;
}
}
// `const {foo = undefined} = {}`
context.on('Identifier', node => {
if (
isUndefined(node)
&& node.parent.type === 'AssignmentPattern'
&& node.parent.right === node
) {
return getProblem(
node,
function * (fixer) {
const assignmentPattern = node.parent;
const {left} = assignmentPattern;
if (undefinedArguments.length === 0) {
return;
yield fixer.removeRange([left.range[1], node.range[1]]);
if (
(left.typeAnnotation || isTypeScriptFile(context))
&& !left.optional
&& isFunction(assignmentPattern.parent)
&& assignmentPattern.parent.params.includes(assignmentPattern)
) {
yield (
left.typeAnnotation
? fixer.insertTextBefore(left.typeAnnotation, '?')
: fixer.insertTextAfter(left, '?')
);
}
},
/* CheckFunctionReturnType */ true,
);
}
});
if (!options.checkArguments) {
return;
}
context.on('CallExpression', node => {
if (shouldIgnore(node.callee)) {
return;
}
const argumentNodes = node.arguments;
// Ignore arguments in `Function#bind()`, but not `this` argument
if (isFunctionBindCall(node) && argumentNodes.length !== 1) {
return;
}
const undefinedArguments = [];
for (let index = argumentNodes.length - 1; index >= 0; index--) {
const node = argumentNodes[index];
if (isUndefined(node)) {
undefinedArguments.unshift(node);
} else {
break;
}
}
const firstUndefined = undefinedArguments[0];
const lastUndefined = undefinedArguments[undefinedArguments.length - 1];
if (undefinedArguments.length === 0) {
return;
}
return {
messageId,
loc: {
start: firstUndefined.loc.start,
end: lastUndefined.loc.end,
},
fix(fixer) {
let start = firstUndefined.range[0];
let end = lastUndefined.range[1];
const firstUndefined = undefinedArguments[0];
const lastUndefined = undefinedArguments[undefinedArguments.length - 1];
const previousArgument = argumentNodes[argumentNodes.length - undefinedArguments.length - 1];
return {
messageId,
loc: {
start: firstUndefined.loc.start,
end: lastUndefined.loc.end,
},
fix(fixer) {
let start = firstUndefined.range[0];
let end = lastUndefined.range[1];
if (previousArgument) {
start = previousArgument.range[1];
} else {
// If all arguments removed, and there is trailing comma, we need remove it.
const tokenAfter = sourceCode.getTokenAfter(lastUndefined);
if (isCommaToken(tokenAfter)) {
end = tokenAfter.range[1];
}
const previousArgument = argumentNodes[argumentNodes.length - undefinedArguments.length - 1];
if (previousArgument) {
start = previousArgument.range[1];
} else {
// If all arguments removed, and there is trailing comma, we need remove it.
const tokenAfter = sourceCode.getTokenAfter(lastUndefined);
if (isCommaToken(tokenAfter)) {
end = tokenAfter.range[1];
}
}
return fixer.removeRange([start, end]);
},
};
return fixer.removeRange([start, end]);
},
};
}
return listeners;
});
};

@@ -217,0 +272,0 @@

@@ -42,3 +42,3 @@ 'use strict';

const start = end - (raw.length - formatted.length);
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {

@@ -45,0 +45,0 @@ loc: toLocation([start, end], sourceCode),

'use strict';
const {isParenthesized} = require('@eslint-community/eslint-utils');
const eventTypes = require('./shared/dom-events.js');
const {STATIC_REQUIRE_SOURCE_SELECTOR} = require('./selectors/index.js');
const {isUndefined, isNullLiteral} = require('./ast/index.js');
const {isUndefined, isNullLiteral, isStaticRequire} = require('./ast/index.js');

@@ -76,4 +75,8 @@ const MESSAGE_ID = 'prefer-add-event-listener';

[STATIC_REQUIRE_SOURCE_SELECTOR](node) {
if (!isDisabled && excludedPackages.has(node.value)) {
CallExpression(node) {
if (!isStaticRequire(node)) {
return;
}
if (!isDisabled && excludedPackages.has(node.arguments[0].value)) {
isDisabled = true;

@@ -83,4 +86,4 @@ }

'ImportDeclaration > Literal'(node) {
if (!isDisabled && excludedPackages.has(node.value)) {
Literal(node) {
if (node.parent.type === 'ImportDeclaration' && !isDisabled && excludedPackages.has(node.value)) {
isDisabled = true;

@@ -142,3 +145,3 @@ }

) {
fix = fixer => fixCode(fixer, context.getSourceCode(), node, memberExpression);
fix = fixer => fixCode(fixer, context.sourceCode, node, memberExpression);
}

@@ -145,0 +148,0 @@

'use strict';
const {isParenthesized, findVariable} = require('@eslint-community/eslint-utils');
const {
not,
methodCallSelector,
notLeftHandSideSelector,
} = require('./selectors/index.js');
const getVariableIdentifiers = require('./utils/get-variable-identifiers.js');
const avoidCapture = require('./utils/avoid-capture.js');
const getScopes = require('./utils/get-scopes.js');
const singular = require('./utils/singular.js');
const {
extendFixRange,

@@ -18,2 +9,10 @@ removeMemberExpressionProperty,

} = require('./fix/index.js');
const {
isLeftHandSide,
singular,
getScopes,
avoidCapture,
getVariableIdentifiers,
} = require('./utils/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -42,93 +41,10 @@ const ERROR_ZERO_INDEX = 'error-zero-index';

const filterMethodSelectorOptions = {
const isArrayFilterCall = node => isMethodCall(node, {
method: 'filter',
minimumArguments: 1,
maximumArguments: 2,
};
optionalCall: false,
optionalMember: false,
});
const filterVariableSelector = [
'VariableDeclaration',
// Exclude `export const foo = [];`
not('ExportNamedDeclaration > .declaration'),
' > ',
'VariableDeclarator.declarations',
'[id.type="Identifier"]',
methodCallSelector({
...filterMethodSelectorOptions,
path: 'init',
}),
].join('');
const zeroIndexSelector = [
'MemberExpression',
'[computed!=false]',
'[property.type="Literal"]',
'[property.raw="0"]',
notLeftHandSideSelector(),
methodCallSelector({
...filterMethodSelectorOptions,
path: 'object',
}),
].join('');
const shiftSelector = [
methodCallSelector({
method: 'shift',
argumentsLength: 0,
}),
methodCallSelector({
...filterMethodSelectorOptions,
path: 'callee.object',
}),
].join('');
const popSelector = [
methodCallSelector({
method: 'pop',
argumentsLength: 0,
}),
methodCallSelector({
...filterMethodSelectorOptions,
path: 'callee.object',
}),
].join('');
const atMinusOneSelector = [
methodCallSelector({
method: 'at',
argumentsLength: 1,
}),
'[arguments.0.type="UnaryExpression"]',
'[arguments.0.operator="-"]',
'[arguments.0.prefix]',
'[arguments.0.argument.type="Literal"]',
'[arguments.0.argument.raw=1]',
methodCallSelector({
...filterMethodSelectorOptions,
path: 'callee.object',
}),
].join('');
const destructuringDeclaratorSelector = [
'VariableDeclarator',
'[id.type="ArrayPattern"]',
'[id.elements.length=1]',
'[id.elements.0.type!="RestElement"]',
methodCallSelector({
...filterMethodSelectorOptions,
path: 'init',
}),
].join('');
const destructuringAssignmentSelector = [
'AssignmentExpression',
'[left.type="ArrayPattern"]',
'[left.elements.length=1]',
'[left.elements.0.type!="RestElement"]',
methodCallSelector({
...filterMethodSelectorOptions,
path: 'right',
}),
].join('');
// Need add `()` to the `AssignmentExpression`

@@ -257,2 +173,3 @@ // - `ObjectExpression`: `[{foo}] = array.filter(bar)` fix to `{foo} = array.find(bar)`

&& left.elements.length === 1
&& left.elements[0]
&& left.elements[0].type !== 'RestElement';

@@ -263,3 +180,3 @@ };

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {

@@ -272,118 +189,211 @@ checkFromLast,

const listeners = {
[zeroIndexSelector](node) {
return {
node: node.object.callee.property,
messageId: ERROR_ZERO_INDEX,
fix: fixer => [
fixer.replaceText(node.object.callee.property, 'find'),
removeMemberExpressionProperty(fixer, node, sourceCode),
],
};
},
[shiftSelector](node) {
return {
node: node.callee.object.callee.property,
messageId: ERROR_SHIFT,
fix: fixer => [
fixer.replaceText(node.callee.object.callee.property, 'find'),
...removeMethodCall(fixer, node, sourceCode),
],
};
},
[destructuringDeclaratorSelector](node) {
return {
node: node.init.callee.property,
messageId: ERROR_DESTRUCTURING_DECLARATION,
...fixDestructuringAndReplaceFilter(sourceCode, node),
};
},
[destructuringAssignmentSelector](node) {
return {
node: node.right.callee.property,
messageId: ERROR_DESTRUCTURING_ASSIGNMENT,
...fixDestructuringAndReplaceFilter(sourceCode, node),
};
},
[filterVariableSelector](node) {
const scope = context.getScope();
const variable = findVariable(scope, node.id);
const identifiers = getVariableIdentifiers(variable).filter(identifier => identifier !== node.id);
// Zero index access
context.on('MemberExpression', node => {
if (!(
node.computed
&& node.property.type === 'Literal'
&& node.property.raw === '0'
&& isArrayFilterCall(node.object)
&& !isLeftHandSide(node)
)) {
return;
}
if (identifiers.length === 0) {
return {
node: node.object.callee.property,
messageId: ERROR_ZERO_INDEX,
fix: fixer => [
fixer.replaceText(node.object.callee.property, 'find'),
removeMemberExpressionProperty(fixer, node, sourceCode),
],
};
});
// `array.filter().shift()`
context.on('CallExpression', node => {
if (!(
isMethodCall(node, {
method: 'shift',
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
&& isArrayFilterCall(node.callee.object)
)) {
return;
}
return {
node: node.callee.object.callee.property,
messageId: ERROR_SHIFT,
fix: fixer => [
fixer.replaceText(node.callee.object.callee.property, 'find'),
...removeMethodCall(fixer, node, sourceCode),
],
};
});
// `const [foo] = array.filter()`
context.on('VariableDeclarator', node => {
if (!(
node.id.type === 'ArrayPattern'
&& node.id.elements.length === 1
&& node.id.elements[0]
&& node.id.elements[0].type !== 'RestElement'
&& isArrayFilterCall(node.init)
)) {
return;
}
return {
node: node.init.callee.property,
messageId: ERROR_DESTRUCTURING_DECLARATION,
...fixDestructuringAndReplaceFilter(sourceCode, node),
};
});
// `[foo] = array.filter()`
context.on('AssignmentExpression', node => {
if (!(
node.left.type === 'ArrayPattern'
&& node.left.elements.length === 1
&& node.left.elements[0]
&& node.left.elements[0].type !== 'RestElement'
&& isArrayFilterCall(node.right)
)) {
return;
}
return {
node: node.right.callee.property,
messageId: ERROR_DESTRUCTURING_ASSIGNMENT,
...fixDestructuringAndReplaceFilter(sourceCode, node),
};
});
// `const foo = array.filter(); foo[0]; [bar] = foo`
context.on('VariableDeclarator', node => {
if (!(
node.id.type === 'Identifier'
&& isArrayFilterCall(node.init)
&& node.parent.type === 'VariableDeclaration'
&& node.parent.declarations.includes(node)
// Exclude `export const foo = [];`
&& !(
node.parent.parent.type === 'ExportNamedDeclaration'
&& node.parent.parent.declaration === node.parent
)
)) {
return;
}
const scope = sourceCode.getScope(node);
const variable = findVariable(scope, node.id);
const identifiers = getVariableIdentifiers(variable).filter(identifier => identifier !== node.id);
if (identifiers.length === 0) {
return;
}
const zeroIndexNodes = [];
const destructuringNodes = [];
for (const identifier of identifiers) {
if (isAccessingZeroIndex(identifier)) {
zeroIndexNodes.push(identifier.parent);
} else if (isDestructuringFirstElement(identifier)) {
destructuringNodes.push(identifier.parent);
} else {
return;
}
}
const zeroIndexNodes = [];
const destructuringNodes = [];
for (const identifier of identifiers) {
if (isAccessingZeroIndex(identifier)) {
zeroIndexNodes.push(identifier.parent);
} else if (isDestructuringFirstElement(identifier)) {
destructuringNodes.push(identifier.parent);
} else {
return;
}
}
const problem = {
node: node.init.callee.property,
messageId: ERROR_DECLARATION,
};
const problem = {
node: node.init.callee.property,
messageId: ERROR_DECLARATION,
};
// `const [foo = bar] = baz` is not fixable
if (!destructuringNodes.some(node => hasDefaultValue(node))) {
problem.fix = function * (fixer) {
yield fixer.replaceText(node.init.callee.property, 'find');
// `const [foo = bar] = baz` is not fixable
if (!destructuringNodes.some(node => hasDefaultValue(node))) {
problem.fix = function * (fixer) {
yield fixer.replaceText(node.init.callee.property, 'find');
const singularName = singular(node.id.name);
if (singularName) {
// Rename variable to be singularized now that it refers to a single item in the array instead of the entire array.
const singularizedName = avoidCapture(singularName, getScopes(scope));
yield * renameVariable(variable, singularizedName, fixer);
const singularName = singular(node.id.name);
if (singularName) {
// Rename variable to be singularized now that it refers to a single item in the array instead of the entire array.
const singularizedName = avoidCapture(singularName, getScopes(scope));
yield * renameVariable(variable, singularizedName, fixer);
// Prevent possible variable conflicts
yield * extendFixRange(fixer, sourceCode.ast.range);
}
// Prevent possible variable conflicts
yield * extendFixRange(fixer, sourceCode.ast.range);
}
for (const node of zeroIndexNodes) {
yield removeMemberExpressionProperty(fixer, node, sourceCode);
}
for (const node of zeroIndexNodes) {
yield removeMemberExpressionProperty(fixer, node, sourceCode);
}
for (const node of destructuringNodes) {
yield * fixDestructuring(node, sourceCode, fixer);
}
};
}
for (const node of destructuringNodes) {
yield * fixDestructuring(node, sourceCode, fixer);
}
};
}
return problem;
});
return problem;
},
};
if (!checkFromLast) {
return listeners;
return;
}
return Object.assign(listeners, {
[popSelector](node) {
return {
node: node.callee.object.callee.property,
messageId: ERROR_POP,
fix: fixer => [
fixer.replaceText(node.callee.object.callee.property, 'findLast'),
...removeMethodCall(fixer, node, sourceCode),
],
};
},
[atMinusOneSelector](node) {
return {
node: node.callee.object.callee.property,
messageId: ERROR_AT_MINUS_ONE,
fix: fixer => [
fixer.replaceText(node.callee.object.callee.property, 'findLast'),
...removeMethodCall(fixer, node, sourceCode),
],
};
},
// `array.filter().pop()`
context.on('CallExpression', node => {
if (!(
isMethodCall(node, {
method: 'pop',
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
&& isArrayFilterCall(node.callee.object)
)) {
return;
}
return {
node: node.callee.object.callee.property,
messageId: ERROR_POP,
fix: fixer => [
fixer.replaceText(node.callee.object.callee.property, 'findLast'),
...removeMethodCall(fixer, node, sourceCode),
],
};
});
// `array.filter().at(-1)`
context.on('CallExpression', node => {
if (!(
isMethodCall(node, {
method: 'at',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& node.arguments[0].type === 'UnaryExpression'
&& node.arguments[0].operator === '-'
&& node.arguments[0].prefix
&& node.arguments[0].argument.type === 'Literal'
&& node.arguments[0].argument.raw === '1'
&& isArrayFilterCall(node.callee.object)
)) {
return;
}
return {
node: node.callee.object.callee.property,
messageId: ERROR_AT_MINUS_ONE,
fix: fixer => [
fixer.replaceText(node.callee.object.callee.property, 'findLast'),
...removeMethodCall(fixer, node, sourceCode),
],
};
});
};

@@ -390,0 +400,0 @@

'use strict';
const {isNodeMatches} = require('./utils/is-node-matches.js');
const {methodCallSelector, matches} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const {removeMethodCall} = require('./fix/index.js');

@@ -11,11 +11,2 @@

const selector = [
methodCallSelector('flat'),
matches([
'[arguments.length=0]',
'[arguments.length=1][arguments.0.type="Literal"][arguments.0.raw="1"]',
]),
methodCallSelector({path: 'callee.object', method: 'map'}),
].join('');
const ignored = ['React.Children', 'Children'];

@@ -25,3 +16,27 @@

const create = context => ({
[selector](flatCallExpression) {
CallExpression(callExpression) {
if (!(
isMethodCall(callExpression, {
method: 'flat',
optionalCall: false,
optionalMember: false,
})
&& (
callExpression.arguments.length === 0
|| (
callExpression.arguments.length === 1
&& callExpression.arguments[0].type === 'Literal'
&& callExpression.arguments[0].raw === '1'
)
)
&& isMethodCall(callExpression.callee.object, {
method: 'map',
optionalCall: false,
optionalMember: false,
})
)) {
return;
}
const flatCallExpression = callExpression;
const mapCallExpression = flatCallExpression.callee.object;

@@ -32,3 +47,3 @@ if (isNodeMatches(mapCallExpression.callee.object, ignored)) {

const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const mapProperty = mapCallExpression.callee.property;

@@ -35,0 +50,0 @@

'use strict';
const {
methodCallSelector,
arrayPrototypeMethodSelector,
emptyArraySelector,
callExpressionSelector,
} = require('./selectors/index.js');
const needsSemicolon = require('./utils/needs-semicolon.js');
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
const {isNodeMatches, isNodeMatchesNameOrPath} = require('./utils/is-node-matches.js');
const {getParenthesizedText, isParenthesized} = require('./utils/parentheses.js');
getParenthesizedText,
isArrayPrototypeProperty,
isNodeMatches,
isNodeMatchesNameOrPath,
isParenthesized,
isSameIdentifier,
needsSemicolon,
shouldAddParenthesesToMemberExpressionObject,
} = require('./utils/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {
isMethodCall,
isCallExpression,
} = require('./ast/index.js');

@@ -19,17 +23,26 @@ const MESSAGE_ID = 'prefer-array-flat';

const isEmptyArrayExpression = node =>
node.type === 'ArrayExpression'
&& node.elements.length === 0;
// `array.flatMap(x => x)`
const arrayFlatMap = {
selector: [
methodCallSelector({
testFunction(node) {
if (!isMethodCall(node, {
method: 'flatMap',
argumentsLength: 1,
}),
'[arguments.0.type="ArrowFunctionExpression"]',
'[arguments.0.async!=true]',
'[arguments.0.generator!=true]',
'[arguments.0.params.length=1]',
'[arguments.0.params.0.type="Identifier"]',
'[arguments.0.body.type="Identifier"]',
].join(''),
testFunction: node => node.arguments[0].params[0].name === node.arguments[0].body.name,
optionalCall: false,
optionalMember: false,
})) {
return false;
}
const [firstArgument] = node.arguments;
return (
firstArgument.type === 'ArrowFunctionExpression'
&& !firstArgument.async
&& firstArgument.params.length === 1
&& isSameIdentifier(firstArgument.params[0], firstArgument.body)
);
},
getArrayNode: node => node.callee.object,

@@ -40,54 +53,50 @@ description: 'Array#flatMap()',

// `array.reduce((a, b) => a.concat(b), [])`
// `array.reduce((a, b) => [...a, ...b], [])`
const arrayReduce = {
selector: [
methodCallSelector({
testFunction(node) {
if (!isMethodCall(node, {
method: 'reduce',
argumentsLength: 2,
}),
'[arguments.0.type="ArrowFunctionExpression"]',
'[arguments.0.async!=true]',
'[arguments.0.generator!=true]',
'[arguments.0.params.length=2]',
'[arguments.0.params.0.type="Identifier"]',
'[arguments.0.params.1.type="Identifier"]',
methodCallSelector({
method: 'concat',
argumentsLength: 1,
path: 'arguments.0.body',
}),
'[arguments.0.body.callee.object.type="Identifier"]',
'[arguments.0.body.arguments.0.type="Identifier"]',
emptyArraySelector('arguments.1'),
].join(''),
testFunction: node =>
node.arguments[0].params[0].name === node.arguments[0].body.callee.object.name
&& node.arguments[0].params[1].name === node.arguments[0].body.arguments[0].name,
getArrayNode: node => node.callee.object,
description: 'Array#reduce()',
};
optionalCall: false,
optionalMember: false,
})) {
return false;
}
// `array.reduce((a, b) => [...a, ...b], [])`
const arrayReduce2 = {
selector: [
methodCallSelector({
method: 'reduce',
argumentsLength: 2,
}),
'[arguments.0.type="ArrowFunctionExpression"]',
'[arguments.0.async!=true]',
'[arguments.0.generator!=true]',
'[arguments.0.params.length=2]',
'[arguments.0.params.0.type="Identifier"]',
'[arguments.0.params.1.type="Identifier"]',
'[arguments.0.body.type="ArrayExpression"]',
'[arguments.0.body.elements.length=2]',
'[arguments.0.body.elements.0.type="SpreadElement"]',
'[arguments.0.body.elements.0.argument.type="Identifier"]',
'[arguments.0.body.elements.1.type="SpreadElement"]',
'[arguments.0.body.elements.1.argument.type="Identifier"]',
emptyArraySelector('arguments.1'),
].join(''),
testFunction: node =>
node.arguments[0].params[0].name === node.arguments[0].body.elements[0].argument.name
&& node.arguments[0].params[1].name === node.arguments[0].body.elements[1].argument.name,
const [firstArgument, secondArgument] = node.arguments;
if (!(
firstArgument.type === 'ArrowFunctionExpression'
&& !firstArgument.async
&& firstArgument.params.length === 2
&& isEmptyArrayExpression(secondArgument)
)) {
return false;
}
const firstArgumentBody = firstArgument.body;
const [firstParameter, secondParameter] = firstArgument.params;
return (
// `(a, b) => a.concat(b)`
(
isMethodCall(firstArgumentBody, {
method: 'concat',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& isSameIdentifier(firstParameter, firstArgumentBody.callee.object)
&& isSameIdentifier(secondParameter, firstArgumentBody.arguments[0])
)
// `(a, b) => [...a, ...b]`
|| (
firstArgumentBody.type === 'ArrayExpression'
&& firstArgumentBody.elements.length === 2
&& firstArgumentBody.elements.every((node, index) =>
node?.type === 'SpreadElement'
&& node.argument.type === 'Identifier'
&& isSameIdentifier(firstArgument.params[index], node.argument),
)
)
);
},
getArrayNode: node => node.callee.object,

@@ -97,12 +106,15 @@ description: 'Array#reduce()',

// `[].concat(maybeArray)` and `[].concat(...array)`
// `[].concat(maybeArray)`
// `[].concat(...array)`
const emptyArrayConcat = {
selector: [
methodCallSelector({
testFunction(node) {
return isMethodCall(node, {
method: 'concat',
argumentsLength: 1,
allowSpreadElement: true,
}),
emptyArraySelector('callee.object'),
].join(''),
optionalCall: false,
optionalMember: false,
})
&& isEmptyArrayExpression(node.callee.object);
},
getArrayNode(node) {

@@ -120,15 +132,25 @@ const argumentNode = node.arguments[0];

const arrayPrototypeConcat = {
selector: [
methodCallSelector({
methods: ['apply', 'call'],
argumentsLength: 2,
allowSpreadElement: true,
}),
emptyArraySelector('arguments.0'),
arrayPrototypeMethodSelector({
path: 'callee.object',
method: 'concat',
}),
].join(''),
testFunction: node => node.arguments[1].type !== 'SpreadElement' || node.callee.property.name === 'call',
testFunction(node) {
if (!(
isMethodCall(node, {
methods: ['apply', 'call'],
argumentsLength: 2,
allowSpreadElement: true,
optionalCall: false,
optionalMember: false,
})
&& isArrayPrototypeProperty(node.callee.object, {
property: 'concat',
})
)) {
return false;
}
const [firstArgument, secondArgument] = node.arguments;
return isEmptyArrayExpression(firstArgument)
&& (
node.callee.property.name === 'call'
|| secondArgument.type !== 'SpreadElement'
);
},
getArrayNode(node) {

@@ -147,6 +169,2 @@ const argumentNode = node.arguments[1];

];
const anyCall = {
selector: callExpressionSelector({argumentsLength: 1}),
getArrayNode: node => node.arguments[0],
};

@@ -190,4 +208,2 @@ function fix(node, array, sourceCode, shouldSwitchToArray) {

const functions = [...configFunctions, ...lodashFlattenFunctions];
const sourceCode = context.getSourceCode();
const listeners = {};

@@ -197,8 +213,10 @@ const cases = [

arrayReduce,
arrayReduce2,
emptyArrayConcat,
arrayPrototypeConcat,
{
...anyCall,
testFunction: node => isNodeMatches(node.callee, functions),
testFunction: node => isCallExpression(node, {
argumentsLength: 1,
optional: false,
}) && isNodeMatches(node.callee, functions),
getArrayNode: node => node.arguments[0],
description: node => `${functions.find(nameOrPath => isNodeMatchesNameOrPath(node.callee, nameOrPath)).trim()}()`,

@@ -208,33 +226,35 @@ },

for (const {selector, testFunction, description, getArrayNode, shouldSwitchToArray} of cases) {
listeners[selector] = function (node) {
if (testFunction && !testFunction(node)) {
return;
}
return {
* CallExpression(node) {
for (const {testFunction, description, getArrayNode, shouldSwitchToArray} of cases) {
if (!testFunction(node)) {
continue;
}
const array = getArrayNode(node);
const array = getArrayNode(node);
const data = {
description: typeof description === 'string' ? description : description(node),
};
const data = {
description: typeof description === 'string' ? description : description(node),
};
const problem = {
node,
messageId: MESSAGE_ID,
data,
};
const problem = {
node,
messageId: MESSAGE_ID,
data,
};
// Don't fix if it has comments.
if (
sourceCode.getCommentsInside(node).length
=== sourceCode.getCommentsInside(array).length
) {
problem.fix = fix(node, array, sourceCode, shouldSwitchToArray);
}
const {sourceCode} = context;
return problem;
};
}
// Don't fix if it has comments.
if (
sourceCode.getCommentsInside(node).length
=== sourceCode.getCommentsInside(array).length
) {
problem.fix = fix(node, array, sourceCode, shouldSwitchToArray);
}
return listeners;
yield problem;
}
},
};
}

@@ -241,0 +261,0 @@

@@ -16,6 +16,6 @@ 'use strict';

module.exports = {
create: context => ({
...indexOfOverFindIndexRule.createListeners(context),
...lastIndexOfOverFindLastIndexRule.createListeners(context),
}),
create(context) {
indexOfOverFindIndexRule.listen(context);
lastIndexOfOverFindLastIndexRule.listen(context);
},
meta: {

@@ -22,0 +22,0 @@ type: 'suggestion',

'use strict';
const {methodCallSelector, matches, memberExpressionSelector} = require('./selectors/index.js');
const {checkVueTemplate} = require('./utils/rule.js');
const {isBooleanNode} = require('./utils/boolean.js');
const {getParenthesizedRange} = require('./utils/parentheses.js');
const {
isBooleanNode,
getParenthesizedRange,
isNodeValueNotFunction,
} = require('./utils/index.js');
const {removeMemberExpressionProperty} = require('./fix/index.js');
const {isLiteral, isUndefined} = require('./ast/index.js');
const {isLiteral, isUndefined, isMethodCall, isMemberExpression} = require('./ast/index.js');

@@ -18,8 +20,2 @@ const ERROR_ID_ARRAY_SOME = 'some';

const arrayFindOrFindLastCallSelector = methodCallSelector({
methods: ['find', 'findLast'],
minimumArguments: 1,
maximumArguments: 2,
});
const isCheckingUndefined = node =>

@@ -49,17 +45,15 @@ node.parent.type === 'BinaryExpression'

const arrayFilterCallSelector = [
'BinaryExpression',
'[right.type="Literal"]',
'[right.raw="0"]',
// We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
matches(['[operator=">"]', '[operator="!=="]']),
' > ',
`${memberExpressionSelector('length')}.left`,
' > ',
`${methodCallSelector('filter')}.object`,
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[arrayFindOrFindLastCallSelector](callExpression) {
CallExpression(callExpression) {
if (!isMethodCall(callExpression, {
methods: ['find', 'findLast'],
minimumArguments: 1,
maximumArguments: 2,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const isCompare = isCheckingUndefined(callExpression);

@@ -85,3 +79,3 @@ if (!isCompare && !isBooleanNode(callExpression)) {

const parenthesizedRange = getParenthesizedRange(callExpression, context.getSourceCode());
const parenthesizedRange = getParenthesizedRange(callExpression, context.sourceCode);
yield fixer.replaceTextRange([parenthesizedRange[1], callExpression.parent.range[1]], '');

@@ -99,3 +93,24 @@

},
[arrayFilterCallSelector](filterCall) {
BinaryExpression(binaryExpression) {
if (!(
// We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
(binaryExpression.operator === '>' || binaryExpression.operator === '!==')
&& binaryExpression.right.type === 'Literal'
&& binaryExpression.right.raw === '0'
&& isMemberExpression(binaryExpression.left, {property: 'length', optional: false})
&& isMethodCall(binaryExpression.left.object, {
method: 'filter',
optionalCall: false,
optionalMember: false,
})
)) {
return;
}
const filterCall = binaryExpression.left.object;
const [firstArgument] = filterCall.arguments;
if (!firstArgument || isNodeValueNotFunction(firstArgument)) {
return;
}
const filterProperty = filterCall.callee.property;

@@ -109,4 +124,4 @@ return {

const sourceCode = context.getSourceCode();
const lengthNode = filterCall.parent;
const {sourceCode} = context;
const lengthNode = binaryExpression.left;
/*

@@ -119,3 +134,2 @@ Remove `.length`

const compareNode = lengthNode.parent;
/*

@@ -128,3 +142,3 @@ Remove `> 0`

getParenthesizedRange(lengthNode, sourceCode)[1],
compareNode.range[1],
binaryExpression.range[1],
]);

@@ -131,0 +145,0 @@

@@ -7,7 +7,7 @@ 'use strict';

getParenthesizedText,
} = require('./utils/parentheses.js');
const {isNodeMatchesNameOrPath} = require('./utils/is-node-matches.js');
const needsSemicolon = require('./utils/needs-semicolon.js');
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
const isLeftHandSide = require('./utils/is-left-hand-side.js');
isNodeMatchesNameOrPath,
needsSemicolon,
shouldAddParenthesesToMemberExpressionObject,
isLeftHandSide,
} = require('./utils/index.js');
const {

@@ -17,5 +17,4 @@ getNegativeIndexLengthNode,

} = require('./shared/negative-index.js');
const {methodCallSelector, callExpressionSelector, notLeftHandSideSelector} = require('./selectors/index.js');
const {removeMemberExpressionProperty, removeMethodCall} = require('./fix/index.js');
const {isLiteral} = require('./ast/index.js');
const {isLiteral, isCallExpression, isMethodCall} = require('./ast/index.js');

@@ -39,10 +38,2 @@ const MESSAGE_ID_NEGATIVE_INDEX = 'negative-index';

const indexAccess = [
'MemberExpression',
'[optional!=true]',
'[computed!=false]',
notLeftHandSideSelector(),
].join('');
const sliceCall = methodCallSelector({method: 'slice', minimumArguments: 1, maximumArguments: 2});
const stringCharAt = methodCallSelector({method: 'charAt', argumentsLength: 1});
const isArguments = node => node.type === 'Identifier' && node.name === 'arguments';

@@ -152,169 +143,204 @@

const getLastFunctions = [...getLastElementFunctions, ...lodashLastFunctions];
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[indexAccess](node) {
const indexNode = node.property;
const lengthNode = getNegativeIndexLengthNode(indexNode, node.object);
// Index access
context.on('MemberExpression', node => {
if (
node.optional
|| !node.computed
|| isLeftHandSide(node)
) {
return;
}
if (!lengthNode) {
if (!checkAllIndexAccess) {
return;
}
const indexNode = node.property;
const lengthNode = getNegativeIndexLengthNode(indexNode, node.object);
// Only if we are sure it's an positive integer
const staticValue = getStaticValue(indexNode, context.getScope());
if (!staticValue || !Number.isInteger(staticValue.value) || staticValue.value < 0) {
return;
}
if (!lengthNode) {
if (!checkAllIndexAccess) {
return;
}
const problem = {
node: indexNode,
messageId: lengthNode ? MESSAGE_ID_NEGATIVE_INDEX : MESSAGE_ID_INDEX,
};
// Only if we are sure it's an positive integer
const staticValue = getStaticValue(indexNode, sourceCode.getScope(indexNode));
if (!staticValue || !Number.isInteger(staticValue.value) || staticValue.value < 0) {
return;
}
}
if (isArguments(node.object)) {
return problem;
const problem = {
node: indexNode,
messageId: lengthNode ? MESSAGE_ID_NEGATIVE_INDEX : MESSAGE_ID_INDEX,
};
if (isArguments(node.object)) {
return problem;
}
problem.fix = function * (fixer) {
if (lengthNode) {
yield removeLengthNode(lengthNode, fixer, sourceCode);
}
problem.fix = function * (fixer) {
if (lengthNode) {
yield removeLengthNode(lengthNode, fixer, sourceCode);
}
// Only remove space for `foo[foo.length - 1]`
// Only remove space for `foo[foo.length - 1]`
if (
indexNode.type === 'BinaryExpression'
&& indexNode.operator === '-'
&& indexNode.left === lengthNode
&& indexNode.right.type === 'Literal'
&& /^\d+$/.test(indexNode.right.raw)
) {
const numberNode = indexNode.right;
const tokenBefore = sourceCode.getTokenBefore(numberNode);
if (
indexNode.type === 'BinaryExpression'
&& indexNode.operator === '-'
&& indexNode.left === lengthNode
&& indexNode.right.type === 'Literal'
&& /^\d+$/.test(indexNode.right.raw)
tokenBefore.type === 'Punctuator'
&& tokenBefore.value === '-'
&& /^\s+$/.test(sourceCode.text.slice(tokenBefore.range[1], numberNode.range[0]))
) {
const numberNode = indexNode.right;
const tokenBefore = sourceCode.getTokenBefore(numberNode);
if (
tokenBefore.type === 'Punctuator'
&& tokenBefore.value === '-'
&& /^\s+$/.test(sourceCode.text.slice(tokenBefore.range[1], numberNode.range[0]))
) {
yield fixer.removeRange([tokenBefore.range[1], numberNode.range[0]]);
}
yield fixer.removeRange([tokenBefore.range[1], numberNode.range[0]]);
}
}
const openingBracketToken = sourceCode.getTokenBefore(indexNode, isOpeningBracketToken);
yield fixer.replaceText(openingBracketToken, '.at(');
const openingBracketToken = sourceCode.getTokenBefore(indexNode, isOpeningBracketToken);
yield fixer.replaceText(openingBracketToken, '.at(');
const closingBracketToken = sourceCode.getTokenAfter(indexNode, isClosingBracketToken);
yield fixer.replaceText(closingBracketToken, ')');
};
const closingBracketToken = sourceCode.getTokenAfter(indexNode, isClosingBracketToken);
yield fixer.replaceText(closingBracketToken, ')');
};
return problem;
},
[stringCharAt](node) {
const [indexNode] = node.arguments;
const lengthNode = getNegativeIndexLengthNode(indexNode, node.callee.object);
return problem;
});
// `String#charAt` don't care about index value, we assume it's always number
if (!lengthNode && !checkAllIndexAccess) {
return;
}
// `string.charAt`
context.on('CallExpression', node => {
if (!isMethodCall(node, {
method: 'charAt',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}
return {
node: indexNode,
messageId: lengthNode ? MESSAGE_ID_STRING_CHAR_AT_NEGATIVE : MESSAGE_ID_STRING_CHAR_AT,
suggest: [{
messageId: SUGGESTION_ID,
* fix(fixer) {
if (lengthNode) {
yield removeLengthNode(lengthNode, fixer, sourceCode);
}
const [indexNode] = node.arguments;
const lengthNode = getNegativeIndexLengthNode(indexNode, node.callee.object);
yield fixer.replaceText(node.callee.property, 'at');
},
}],
};
},
[sliceCall](sliceCall) {
const result = checkSliceCall(sliceCall);
if (!result) {
return;
}
// `String#charAt` don't care about index value, we assume it's always number
if (!lengthNode && !checkAllIndexAccess) {
return;
}
const {safeToFix, firstElementGetMethod} = result;
return {
node: indexNode,
messageId: lengthNode ? MESSAGE_ID_STRING_CHAR_AT_NEGATIVE : MESSAGE_ID_STRING_CHAR_AT,
suggest: [{
messageId: SUGGESTION_ID,
* fix(fixer) {
if (lengthNode) {
yield removeLengthNode(lengthNode, fixer, sourceCode);
}
/** @param {import('eslint').Rule.RuleFixer} fixer */
function * fix(fixer) {
// `.slice` to `.at`
yield fixer.replaceText(sliceCall.callee.property, 'at');
yield fixer.replaceText(node.callee.property, 'at');
},
}],
};
});
// Remove extra arguments
if (sliceCall.arguments.length !== 1) {
const [, start] = getParenthesizedRange(sliceCall.arguments[0], sourceCode);
const [end] = sourceCode.getLastToken(sliceCall).range;
yield fixer.removeRange([start, end]);
}
// `.slice()`
context.on('CallExpression', sliceCall => {
if (!isMethodCall(sliceCall, {
method: 'slice',
minimumArguments: 1,
maximumArguments: 2,
optionalCall: false,
optionalMember: false,
})) {
return;
}
// Remove `[0]`, `.shift()`, or `.pop()`
if (firstElementGetMethod === 'zero-index') {
yield removeMemberExpressionProperty(fixer, sliceCall.parent, sourceCode);
} else {
yield * removeMethodCall(fixer, sliceCall.parent.parent, sourceCode);
}
}
const result = checkSliceCall(sliceCall);
if (!result) {
return;
}
const problem = {
node: sliceCall.callee.property,
messageId: MESSAGE_ID_SLICE,
};
const {safeToFix, firstElementGetMethod} = result;
if (safeToFix) {
problem.fix = fix;
} else {
problem.suggest = [{messageId: SUGGESTION_ID, fix}];
/** @param {import('eslint').Rule.RuleFixer} fixer */
function * fix(fixer) {
// `.slice` to `.at`
yield fixer.replaceText(sliceCall.callee.property, 'at');
// Remove extra arguments
if (sliceCall.arguments.length !== 1) {
const [, start] = getParenthesizedRange(sliceCall.arguments[0], sourceCode);
const [end] = sourceCode.getLastToken(sliceCall).range;
yield fixer.removeRange([start, end]);
}
return problem;
},
[callExpressionSelector({argumentsLength: 1})](node) {
const matchedFunction = getLastFunctions.find(nameOrPath => isNodeMatchesNameOrPath(node.callee, nameOrPath));
if (!matchedFunction) {
return;
// Remove `[0]`, `.shift()`, or `.pop()`
if (firstElementGetMethod === 'zero-index') {
yield removeMemberExpressionProperty(fixer, sliceCall.parent, sourceCode);
} else {
yield * removeMethodCall(fixer, sliceCall.parent.parent, sourceCode);
}
}
const problem = {
node: node.callee,
messageId: MESSAGE_ID_GET_LAST_FUNCTION,
data: {description: matchedFunction.trim()},
};
const problem = {
node: sliceCall.callee.property,
messageId: MESSAGE_ID_SLICE,
};
const [array] = node.arguments;
if (safeToFix) {
problem.fix = fix;
} else {
problem.suggest = [{messageId: SUGGESTION_ID, fix}];
}
if (isArguments(array)) {
return problem;
}
return problem;
});
problem.fix = function (fixer) {
let fixed = getParenthesizedText(array, sourceCode);
context.on('CallExpression', node => {
if (!isCallExpression(node, {argumentsLength: 1, optional: false})) {
return;
}
if (
!isParenthesized(array, sourceCode)
&& shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
) {
fixed = `(${fixed})`;
}
const matchedFunction = getLastFunctions.find(nameOrPath => isNodeMatchesNameOrPath(node.callee, nameOrPath));
if (!matchedFunction) {
return;
}
fixed = `${fixed}.at(-1)`;
const problem = {
node: node.callee,
messageId: MESSAGE_ID_GET_LAST_FUNCTION,
data: {description: matchedFunction.trim()},
};
const tokenBefore = sourceCode.getTokenBefore(node);
if (needsSemicolon(tokenBefore, sourceCode, fixed)) {
fixed = `;${fixed}`;
}
const [array] = node.arguments;
return fixer.replaceText(node, fixed);
};
if (isArguments(array)) {
return problem;
}
return problem;
},
};
problem.fix = function (fixer) {
let fixed = getParenthesizedText(array, sourceCode);
if (
!isParenthesized(array, sourceCode)
&& shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
) {
fixed = `(${fixed})`;
}
fixed = `${fixed}.at(-1)`;
const tokenBefore = sourceCode.getTokenBefore(node);
if (needsSemicolon(tokenBefore, sourceCode, fixed)) {
fixed = `;${fixed}`;
}
return fixer.replaceText(node, fixed);
};
return problem;
});
}

@@ -321,0 +347,0 @@

'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -7,40 +7,51 @@ const messages = {

'error/fromCharCode': 'Prefer `String.fromCodePoint()` over `String.fromCharCode()`.',
'suggestion/charCodeAt': 'Use `String#codePointAt()`.',
'suggestion/fromCharCode': 'Use `String.fromCodePoint()`.',
'suggestion/codePointAt': 'Use `String#codePointAt()`.',
'suggestion/fromCodePoint': 'Use `String.fromCodePoint()`.',
};
const cases = [
{
selector: methodCallSelector('charCodeAt'),
replacement: 'codePointAt',
},
{
selector: methodCallSelector({object: 'String', method: 'fromCharCode'}),
replacement: 'fromCodePoint',
},
];
const getReplacement = node => {
if (isMethodCall(node, {
method: 'charCodeAt',
optionalCall: false,
optionalMember: false,
})) {
return 'codePointAt';
}
if (isMethodCall(node, {
object: 'String',
method: 'fromCharCode',
optionalCall: false,
optionalMember: false,
})) {
return 'fromCodePoint';
}
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => Object.fromEntries(
cases.map(({selector, replacement}) => [
selector,
node => {
const method = node.callee.property;
const methodName = method.name;
const fix = fixer => fixer.replaceText(method, replacement);
const create = () => ({
CallExpression(node) {
const replacement = getReplacement(node);
return {
node: method,
messageId: `error/${methodName}`,
suggest: [
{
messageId: `suggestion/${methodName}`,
fix,
},
],
};
},
]),
);
if (!replacement) {
return;
}
const method = node.callee.property;
const methodName = method.name;
const fix = fixer => fixer.replaceText(method, replacement);
return {
node: method,
messageId: `error/${methodName}`,
suggest: [
{
messageId: `suggestion/${replacement}`,
fix,
},
],
};
},
});
/** @type {import('eslint').Rule.RuleModule} */

@@ -47,0 +58,0 @@ module.exports = {

'use strict';
const {
matches,
methodCallSelector,
newExpressionSelector,
callExpressionSelector,
} = require('./selectors/index.js');
isMethodCall,
isCallExpression,
isNewExpression,
} = require('./ast/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');

@@ -19,38 +18,3 @@

const createNewDateSelector = path => newExpressionSelector({path, name: 'Date', argumentsLength: 0});
const operatorsSelector = (...operators) => matches(operators.map(operator => `[operator="${operator}"]`));
// `new Date()`
const newDateSelector = createNewDateSelector();
// `new Date().{getTime,valueOf}()`
const methodsSelector = [
methodCallSelector({
methods: ['getTime', 'valueOf'],
argumentsLength: 0,
}),
createNewDateSelector('callee.object'),
].join('');
// `{Number,BigInt}(new Date())`
const builtinObjectSelector = [
callExpressionSelector({names: ['Number', 'BigInt'], argumentsLength: 1}),
createNewDateSelector('arguments.0'),
].join('');
// https://github.com/estree/estree/blob/master/es5.md#unaryoperator
const unaryExpressionsSelector = [
'UnaryExpression',
operatorsSelector('+', '-'),
createNewDateSelector('argument'),
].join('');
const assignmentExpressionSelector = [
'AssignmentExpression',
operatorsSelector('-=', '*=', '/=', '%=', '**='),
'>',
`${newDateSelector}.right`,
].join('');
const binaryExpressionSelector = [
'BinaryExpression',
operatorsSelector('-', '*', '/', '%', '**'),
// Both `left` and `right` properties
'>',
newDateSelector,
].join('');
const isNewDate = node => isNewExpression(node, {name: 'Date', argumentsLength: 0});

@@ -72,32 +36,88 @@ const getProblem = (node, problem, sourceCode) => ({

const create = context => ({
[methodsSelector](node) {
const method = node.callee.property;
return getProblem(node, {
node: method,
messageId: MESSAGE_ID_METHOD,
data: {method: method.name},
});
},
[builtinObjectSelector](node) {
const {name} = node.callee;
if (name === 'Number') {
return getProblem(node, {
messageId: MESSAGE_ID_NUMBER,
CallExpression(callExpression) {
// `new Date().{getTime,valueOf}()`
if (
isMethodCall(callExpression, {
methods: ['getTime', 'valueOf'],
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
&& isNewDate(callExpression.callee.object)
) {
const method = callExpression.callee.property;
return getProblem(callExpression, {
node: method,
messageId: MESSAGE_ID_METHOD,
data: {method: method.name},
});
}
return getProblem(node.arguments[0]);
// `{Number,BigInt}(new Date())`
if (
isCallExpression(callExpression, {
names: ['Number', 'BigInt'],
argumentsLength: 1,
optional: false,
})
&& isNewDate(callExpression.arguments[0])
) {
const {name} = callExpression.callee;
if (name === 'Number') {
return getProblem(callExpression, {
messageId: MESSAGE_ID_NUMBER,
});
}
return getProblem(callExpression.arguments[0]);
}
},
[unaryExpressionsSelector](node) {
return getProblem(
node.operator === '-' ? node.argument : node,
{},
context.getSourceCode(),
);
UnaryExpression(unaryExpression) {
// https://github.com/estree/estree/blob/master/es5.md#unaryoperator
if (
unaryExpression.operator !== '+'
&& unaryExpression.operator !== '-'
) {
return;
}
if (isNewDate(unaryExpression.argument)) {
return getProblem(
unaryExpression.operator === '-' ? unaryExpression.argument : unaryExpression,
{},
context.sourceCode,
);
}
},
[assignmentExpressionSelector](node) {
return getProblem(node);
AssignmentExpression(assignmentExpression) {
if (
assignmentExpression.operator !== '-='
&& assignmentExpression.operator !== '*='
&& assignmentExpression.operator !== '/='
&& assignmentExpression.operator !== '%='
&& assignmentExpression.operator !== '**='
) {
return;
}
if (isNewDate(assignmentExpression.right)) {
return getProblem(assignmentExpression.right);
}
},
[binaryExpressionSelector](node) {
return getProblem(node);
* BinaryExpression(binaryExpression) {
if (
binaryExpression.operator !== '-'
&& binaryExpression.operator !== '*'
&& binaryExpression.operator !== '/'
&& binaryExpression.operator !== '%'
&& binaryExpression.operator !== '**'
) {
return;
}
for (const node of [binaryExpression.left, binaryExpression.right]) {
if (isNewDate(node)) {
yield getProblem(node);
}
}
},

@@ -104,0 +124,0 @@ });

'use strict';
const {findVariable} = require('@eslint-community/eslint-utils');
const {functionTypes} = require('./ast/index.js');

@@ -7,12 +8,2 @@ const MESSAGE_ID = 'preferDefaultParameters';

const assignmentSelector = [
'ExpressionStatement',
'[expression.type="AssignmentExpression"]',
].join('');
const declarationSelector = [
'VariableDeclaration',
'[declarations.0.type="VariableDeclarator"]',
].join('');
const isDefaultExpression = (left, right) =>

@@ -131,3 +122,3 @@ left

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const functionStack = [];

@@ -153,3 +144,3 @@

const variable = findVariable(context.getScope(), secondId);
const variable = findVariable(sourceCode.getScope(node), secondId);

@@ -195,20 +186,21 @@ // This was reported https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1122

return {
':function'(node) {
functionStack.push(node);
},
':function:exit'() {
functionStack.pop();
},
[assignmentSelector](node) {
const {left, right} = node.expression;
context.on(functionTypes, node => {
functionStack.push(node);
});
return checkExpression(node, left, right, true);
},
[declarationSelector](node) {
const {id, init} = node.declarations[0];
context.onExit(functionTypes, () => {
functionStack.pop();
});
return checkExpression(node, id, init, false);
},
};
context.on('AssignmentExpression', node => {
if (node.parent.type === 'ExpressionStatement' && node.parent.expression === node) {
return checkExpression(node.parent, node.left, node.right, true);
}
});
context.on('VariableDeclarator', node => {
if (node.parent.type === 'VariableDeclaration' && node.parent.declarations[0] === node) {
return checkExpression(node.parent, node.id, node.init, false);
}
});
};

@@ -215,0 +207,0 @@

'use strict';
const isValueNotUsable = require('./utils/is-value-not-usable.js');
const {methodCallSelector, notDomNodeSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const {isNodeValueNotDomNode, isValueNotUsable} = require('./utils/index.js');

@@ -9,14 +9,18 @@ const MESSAGE_ID = 'prefer-dom-node-append';

};
const selector = [
methodCallSelector({
method: 'appendChild',
argumentsLength: 1,
}),
notDomNodeSelector('callee.object'),
notDomNodeSelector('arguments.0'),
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[selector](node) {
CallExpression(node) {
if (
!isMethodCall(node, {
method: 'appendChild',
argumentsLength: 1,
optionalCall: false,
})
|| isNodeValueNotDomNode(node.callee.object)
|| isNodeValueNotDomNode(node.arguments[0])
) {
return;
}
const fix = isValueNotUsable(node)

@@ -23,0 +27,0 @@ ? fixer => fixer.replaceText(node.callee.property, 'append')

'use strict';
const {isIdentifierName} = require('@babel/helper-validator-identifier');
const escapeString = require('./utils/escape-string.js');
const {methodCallSelector, matches} = require('./selectors/index.js');
const {
escapeString,
hasOptionalChainElement,
isValueNotUsable,
} = require('./utils/index.js');
const {isMethodCall, isStringLiteral, isExpressionStatement} = require('./ast/index.js');

@@ -11,34 +15,28 @@ const MESSAGE_ID = 'prefer-dom-node-dataset';

const selector = [
matches([
methodCallSelector({method: 'setAttribute', argumentsLength: 2}),
methodCallSelector({methods: ['getAttribute', 'removeAttribute', 'hasAttribute'], argumentsLength: 1}),
]),
'[arguments.0.type="Literal"]',
].join('');
const dashToCamelCase = string => string.replace(/-[a-z]/g, s => s[1].toUpperCase());
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const [nameNode] = node.arguments;
let attributeName = nameNode.value;
function getFix(callExpression, context) {
const method = callExpression.callee.property.name;
if (typeof attributeName !== 'string') {
return;
}
// `foo?.bar = ''` is invalid
// TODO: Remove this restriction if https://github.com/nicolo-ribaudo/ecma262/pull/4 get merged
if (method === 'setAttribute' && hasOptionalChainElement(callExpression.callee)) {
return;
}
attributeName = attributeName.toLowerCase();
// `element.setAttribute(…)` returns `undefined`, but `AssignmentExpression` returns value of RHS
if (method === 'setAttribute' && !isValueNotUsable(callExpression)) {
return;
}
if (!attributeName.startsWith('data-')) {
return;
}
if (method === 'removeAttribute' && !isExpressionStatement(callExpression.parent)) {
return;
}
const method = node.callee.property.name;
const name = dashToCamelCase(attributeName.slice(5));
const sourceCode = context.getSourceCode();
return fixer => {
const [nameNode] = callExpression.arguments;
const name = dashToCamelCase(nameNode.value.toLowerCase().slice(5));
const {sourceCode} = context;
let text = '';
const datasetText = `${sourceCode.getText(node.callee.object)}.dataset`;
const datasetText = `${sourceCode.getText(callExpression.callee.object)}.dataset`;
switch (method) {

@@ -51,3 +49,3 @@ case 'setAttribute':

if (method === 'setAttribute') {
text += ` = ${sourceCode.getText(node.arguments[1])}`;
text += ` = ${sourceCode.getText(callExpression.arguments[1])}`;
} else if (method === 'removeAttribute') {

@@ -71,7 +69,40 @@ text = `delete ${text}`;

return fixer.replaceText(callExpression, text);
};
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
CallExpression(callExpression) {
if (!(
(
isMethodCall(callExpression, {
method: 'setAttribute',
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
|| isMethodCall(callExpression, {
methods: ['getAttribute', 'removeAttribute', 'hasAttribute'],
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
)
&& isStringLiteral(callExpression.arguments[0])
)) {
return;
}
const attributeName = callExpression.arguments[0].value.toLowerCase();
if (!attributeName.startsWith('data-')) {
return;
}
return {
node,
node: callExpression,
messageId: MESSAGE_ID,
data: {method},
fix: fixer => fixer.replaceText(node, text),
data: {method: callExpression.callee.property.name},
fix: getFix(callExpression, context),
};

@@ -78,0 +109,0 @@ },

'use strict';
const {isParenthesized, hasSideEffect} = require('@eslint-community/eslint-utils');
const {methodCallSelector, notDomNodeSelector} = require('./selectors/index.js');
const needsSemicolon = require('./utils/needs-semicolon.js');
const isValueNotUsable = require('./utils/is-value-not-usable.js');
const {getParenthesizedText} = require('./utils/parentheses.js');
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
const {isMethodCall} = require('./ast/index.js');
const {
getParenthesizedText,
isNodeValueNotDomNode,
isValueNotUsable,
needsSemicolon,
shouldAddParenthesesToMemberExpressionObject,
} = require('./utils/index.js');

@@ -13,20 +16,32 @@ const ERROR_MESSAGE_ID = 'error';

[ERROR_MESSAGE_ID]: 'Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`.',
[SUGGESTION_MESSAGE_ID]: 'Replace `parentNode.removeChild(childNode)` with `childNode.remove()`.',
[SUGGESTION_MESSAGE_ID]: 'Replace `parentNode.removeChild(childNode)` with `childNode{{dotOrQuestionDot}}remove()`.',
};
const selector = [
methodCallSelector({
method: 'removeChild',
argumentsLength: 1,
}),
notDomNodeSelector('callee.object'),
notDomNodeSelector('arguments.0'),
].join('');
// TODO: Don't check node.type twice
const isMemberExpressionOptionalObject = node =>
node.parent.type === 'MemberExpression'
&& node.parent.object === node
&& (
node.parent.optional
|| (node.type === 'MemberExpression' && isMemberExpressionOptionalObject(node.object))
);
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[selector](node) {
CallExpression(node) {
if (
!isMethodCall(node, {
method: 'removeChild',
argumentsLength: 1,
optionalCall: false,
})
|| isNodeValueNotDomNode(node.callee.object)
|| isNodeValueNotDomNode(node.arguments[0])
) {
return;
}
const parentNode = node.callee.object;

@@ -40,3 +55,5 @@ const childNode = node.arguments[0];

const fix = fixer => {
const isOptionalParentNode = isMemberExpressionOptionalObject(parentNode);
const createFix = (optional = false) => fixer => {
let childNodeText = getParenthesizedText(childNode, sourceCode);

@@ -54,16 +71,38 @@ if (

return fixer.replaceText(node, `${childNodeText}.remove()`);
return fixer.replaceText(node, `${childNodeText}${optional ? '?' : ''}.remove()`);
};
if (!hasSideEffect(parentNode, sourceCode) && isValueNotUsable(node)) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: SUGGESTION_MESSAGE_ID,
fix,
},
];
if (!isOptionalParentNode) {
problem.fix = createFix(false);
return problem;
}
// The most common case `foo?.parentNode.remove(foo)`
// TODO: Allow case like `foo.bar?.parentNode.remove(foo.bar)`
if (
node.callee.type === 'MemberExpression'
&& !node.callee.optional
&& parentNode.type === 'MemberExpression'
&& parentNode.optional
&& !parentNode.computed
&& parentNode.property.type === 'Identifier'
&& parentNode.property.name === 'parentNode'
&& parentNode.object.type === 'Identifier'
&& childNode.type === 'Identifier'
&& parentNode.object.name === childNode.name
) {
problem.fix = createFix(true);
return problem;
}
}
problem.suggest = (
isOptionalParentNode ? [true, false] : [false]
).map(optional => ({
messageId: SUGGESTION_MESSAGE_ID,
data: {dotOrQuestionDot: optional ? '?.' : '.'},
fix: createFix(optional),
}));
return problem;

@@ -70,0 +109,0 @@ },

'use strict';
const {memberExpressionSelector} = require('./selectors/index.js');
const {isMemberExpression} = require('./ast/index.js');

@@ -11,17 +11,15 @@ const ERROR = 'error';

const memberExpressionPropertySelector = `${memberExpressionSelector('innerText')} > .property`;
const destructuringSelector = [
'ObjectPattern',
' > ',
'Property.properties',
'[kind="init"]',
'[computed!=true]',
' > ',
'Identifier.key',
'[name="innerText"]',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[memberExpressionPropertySelector](node) {
MemberExpression(memberExpression) {
if (
!isMemberExpression(memberExpression, {
property: 'innerText',
})
) {
return;
}
const node = memberExpression.property;
return {

@@ -38,3 +36,15 @@ node,

},
[destructuringSelector](node) {
Identifier(node) {
if (!(
node.name === 'innerText'
&& node.parent.type === 'Property'
&& node.parent.key === node
&& !node.parent.computed
&& node.parent.kind === 'init'
&& node.parent.parent.type === 'ObjectPattern'
&& node.parent.parent.properties.includes(node.parent)
)) {
return;
}
return {

@@ -41,0 +51,0 @@ node,

'use strict';
const {matches} = require('./selectors/index.js');

@@ -9,15 +8,18 @@ const MESSAGE_ID = 'prefer-event-target';

const selector = [
'Identifier',
'[name="EventEmitter"]',
matches([
'ClassDeclaration > .superClass',
'ClassExpression > .superClass',
'NewExpression > .callee',
]),
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[selector](node) {
Identifier(node) {
if (!(
node.name === 'EventEmitter'
&& (
(
(node.parent.type === 'ClassDeclaration' || node.parent.type === 'ClassExpression')
&& node.parent.superClass === node
)
|| (node.parent.type === 'NewExpression' && node.parent.callee === node)
)
)) {
return;
}
return {

@@ -24,0 +26,0 @@ node,

@@ -7,2 +7,5 @@ 'use strict';

} = require('@eslint-community/eslint-utils');
const {
isStringLiteral,
} = require('./ast/index.js');

@@ -174,3 +177,3 @@ const MESSAGE_ID_ERROR = 'error';

function getExported(identifier, context, sourceCode) {
function getExported(identifier, sourceCode) {
const {parent} = identifier;

@@ -206,3 +209,3 @@ switch (parent.type) {

&& parent.parent.parent.type === 'ExportNamedDeclaration'
&& isVariableUnused(parent, context)
&& isVariableUnused(parent, sourceCode)
) {

@@ -223,4 +226,4 @@ return {

function isVariableUnused(node, context) {
const variables = context.getDeclaredVariables(node);
function isVariableUnused(node, sourceCode) {
const variables = sourceCode.getDeclaredVariables(node);

@@ -277,6 +280,6 @@ /* c8 ignore next 3 */

function getExports(imported, context, sourceCode) {
function getExports(imported, sourceCode) {
const exports = [];
for (const {identifier} of imported.variable.references) {
const exported = getExported(identifier, context, sourceCode);
const exported = getExported(identifier, sourceCode);

@@ -320,3 +323,3 @@ if (!exported) {

function create(context) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {ignoreUsedVariables} = {ignoreUsedVariables: false, ...context.options[0]};

@@ -327,12 +330,16 @@ const importDeclarations = new Set();

return {
'ImportDeclaration[specifiers.length>0]'(node) {
importDeclarations.add(node);
ImportDeclaration(node) {
if (node.specifiers.length > 0) {
importDeclarations.add(node);
}
},
// `ExportAllDeclaration` and `ExportDefaultDeclaration` can't be reused
'ExportNamedDeclaration[source.type="Literal"]'(node) {
exportDeclarations.push(node);
ExportNamedDeclaration(node) {
if (isStringLiteral(node.source)) {
exportDeclarations.push(node);
}
},
* 'Program:exit'(program) {
for (const importDeclaration of importDeclarations) {
let variables = context.getDeclaredVariables(importDeclaration);
let variables = sourceCode.getDeclaredVariables(importDeclaration);

@@ -345,3 +352,3 @@ if (variables.some(variable => variable.defs.length !== 1 || variable.defs[0].parent !== importDeclaration)) {

const imported = getImported(variable, sourceCode);
const exports = getExports(imported, context, sourceCode);
const exports = getExports(imported, sourceCode);

@@ -348,0 +355,0 @@ return {

@@ -18,3 +18,3 @@ 'use strict';

const getProblem = (context, node, target, argumentsNodes) => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const memberExpressionNode = target.parent;

@@ -47,4 +47,6 @@ const dotToken = sourceCode.getTokenBefore(memberExpressionNode.property);

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
BinaryExpression(node) {
const create = context => {
includesOverSomeRule.listen(context);
context.on('BinaryExpression', node => {
const {left, right, operator} = node;

@@ -80,5 +82,4 @@

}
},
...includesOverSomeRule.createListeners(context),
});
});
};

@@ -85,0 +86,0 @@ /** @type {import('eslint').Rule.RuleModule} */

'use strict';
const {findVariable, getStaticValue, getPropertyName} = require('@eslint-community/eslint-utils');
const {methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const {removeArgument} = require('./fix/index.js');

@@ -11,11 +11,2 @@

const jsonParseArgumentSelector = [
methodCallSelector({
object: 'JSON',
method: 'parse',
argumentsLength: 1,
}),
' > .arguments:first-child',
].join('');
const getAwaitExpressionArgument = node => {

@@ -111,4 +102,16 @@ while (node.type === 'AwaitExpression') {

const create = context => ({
[jsonParseArgumentSelector](node) {
const scope = context.getScope();
CallExpression(callExpression) {
if (!(isMethodCall(callExpression, {
object: 'JSON',
method: 'parse',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
}))) {
return;
}
let [node] = callExpression.arguments;
const {sourceCode} = context;
const scope = sourceCode.getScope(node);
node = getIdentifierDeclaration(node, scope);

@@ -142,3 +145,3 @@ if (

messageId: MESSAGE_ID,
fix: fixer => removeArgument(fixer, charsetNode, context.getSourceCode()),
fix: fixer => removeArgument(fixer, charsetNode, sourceCode),
};

@@ -145,0 +148,0 @@ },

@@ -28,3 +28,3 @@ 'use strict';

case 'FunctionExpression': {
const eventVariable = context.getDeclaredVariables(callback)[0];
const eventVariable = context.sourceCode.getDeclaredVariables(callback)[0];
const references = eventVariable?.references;

@@ -114,3 +114,11 @@ return {

const create = context => ({
'Identifier:matches([name="keyCode"], [name="charCode"], [name="which"])'(node) {
Identifier(node) {
if (
node.name !== 'keyCode'
&& node.name !== 'charCode'
&& node.name !== 'which'
) {
return;
}
// Normal case when usage is direct -> `event.keyCode`

@@ -117,0 +125,0 @@ const {event, references} = getEventNodeAndReferences(context, node);

@@ -113,3 +113,3 @@ 'use strict';

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;

@@ -116,0 +116,0 @@ return {

'use strict';
const {hasSideEffect} = require('@eslint-community/eslint-utils');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isLiteral} = require('./ast/index.js');

@@ -14,23 +15,11 @@ const ERROR_BITWISE = 'error-bitwise';

const createBitwiseNotSelector = (level, isNegative) => {
const prefix = 'argument.'.repeat(level);
const selector = [
`[${prefix}type="UnaryExpression"]`,
`[${prefix}operator="~"]`,
].join('');
return isNegative ? `:not(${selector})` : selector;
};
// Bitwise operators
const bitwiseOperators = new Set(['|', '>>', '<<', '^']);
// Unary Expression Selector: Inner-most 2 bitwise NOT
const bitwiseNotUnaryExpressionSelector = [
createBitwiseNotSelector(0),
createBitwiseNotSelector(1),
createBitwiseNotSelector(2, true),
].join('');
const isBitwiseNot = node =>
node.type === 'UnaryExpression'
&& node.operator === '~';
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;

@@ -43,58 +32,66 @@ const mathTruncFunctionCall = node => {

return {
':matches(BinaryExpression, AssignmentExpression)[right.type="Literal"]'(node) {
const {type, operator, right, left} = node;
const isAssignment = type === 'AssignmentExpression';
if (
right.value !== 0
|| !bitwiseOperators.has(isAssignment ? operator.slice(0, -1) : operator)
) {
return;
}
context.on(['BinaryExpression', 'AssignmentExpression'], node => {
const {type, operator, right, left} = node;
const isAssignment = type === 'AssignmentExpression';
if (
!isLiteral(right, 0)
|| !bitwiseOperators.has(isAssignment ? operator.slice(0, -1) : operator)
) {
return;
}
const problem = {
node,
messageId: ERROR_BITWISE,
data: {
operator,
value: right.raw,
},
};
const problem = {
node,
messageId: ERROR_BITWISE,
data: {
operator,
value: right.raw,
},
};
if (!isAssignment || !hasSideEffect(left, sourceCode)) {
const fix = function * (fixer) {
const fixed = mathTruncFunctionCall(left);
if (isAssignment) {
const operatorToken = sourceCode.getTokenAfter(left, token => token.type === 'Punctuator' && token.value === operator);
yield fixer.replaceText(operatorToken, '=');
yield fixer.replaceText(right, fixed);
} else {
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
yield fixer.replaceText(node, fixed);
}
};
if (operator === '|') {
problem.suggest = [
{
messageId: SUGGESTION_BITWISE,
fix,
},
];
if (!isAssignment || !hasSideEffect(left, sourceCode)) {
const fix = function * (fixer) {
const fixed = mathTruncFunctionCall(left);
if (isAssignment) {
const operatorToken = sourceCode.getTokenAfter(left, token => token.type === 'Punctuator' && token.value === operator);
yield fixer.replaceText(operatorToken, '=');
yield fixer.replaceText(right, fixed);
} else {
problem.fix = fix;
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
yield fixer.replaceText(node, fixed);
}
};
if (operator === '|') {
problem.suggest = [
{
messageId: SUGGESTION_BITWISE,
fix,
},
];
} else {
problem.fix = fix;
}
}
return problem;
},
[bitwiseNotUnaryExpressionSelector]: node => ({
node,
messageId: ERROR_BITWISE_NOT,
* fix(fixer) {
yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
},
}),
};
return problem;
});
// Unary Expression Selector: Inner-most 2 bitwise NOT
context.on('UnaryExpression', node => {
if (
isBitwiseNot(node)
&& isBitwiseNot(node.argument)
&& !isBitwiseNot(node.argument.argument)
) {
return {
node,
messageId: ERROR_BITWISE_NOT,
* fix(fixer) {
yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
},
};
}
});
};

@@ -101,0 +98,0 @@

'use strict';
const isValueNotUsable = require('./utils/is-value-not-usable.js');
const {methodCallSelector} = require('./selectors/index.js');
const {isValueNotUsable} = require('./utils/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -12,16 +12,2 @@ const messages = {

const replaceChildOrInsertBeforeSelector = [
methodCallSelector({
methods: ['replaceChild', 'insertBefore'],
argumentsLength: 2,
}),
// We only allow Identifier for now
'[arguments.0.type="Identifier"]',
'[arguments.0.name!="undefined"]',
'[arguments.1.type="Identifier"]',
'[arguments.1.name!="undefined"]',
// This check makes sure that only the first method of chained methods with same identifier name e.g: parentNode.insertBefore(alfa, beta).insertBefore(charlie, delta); gets reported
'[callee.object.type="Identifier"]',
].join('');
const disallowedMethods = new Map([

@@ -59,15 +45,2 @@ ['replaceChild', 'replaceWith'],

const insertAdjacentTextOrInsertAdjacentElementSelector = [
methodCallSelector({
methods: ['insertAdjacentText', 'insertAdjacentElement'],
argumentsLength: 2,
}),
// Position argument should be `string`
'[arguments.0.type="Literal"]',
// TODO: remove this limits on second argument
':matches([arguments.1.type="Literal"], [arguments.1.type="Identifier"])',
// TODO: remove this limits on callee
'[callee.object.type="Identifier"]',
].join('');
const positionReplacers = new Map([

@@ -91,4 +64,5 @@ ['beforebegin', 'before'],

const preferredMethod = positionReplacers.get(position);
const content = context.getSource(contentNode);
const reference = context.getSource(node.callee.object);
const {sourceCode} = context;
const content = sourceCode.getText(contentNode);
const reference = sourceCode.getText(node.callee.object);

@@ -110,3 +84,3 @@ const fix = method === 'insertAdjacentElement' && !isValueNotUsable(node)

preferredMethod,
position: context.getSource(positionNode),
position: sourceCode.getText(positionNode),
content,

@@ -119,11 +93,43 @@ },

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[replaceChildOrInsertBeforeSelector](node) {
return checkForReplaceChildOrInsertBefore(context, node);
},
[insertAdjacentTextOrInsertAdjacentElementSelector](node) {
return checkForInsertAdjacentTextOrInsertAdjacentElement(context, node);
},
});
const create = context => {
context.on('CallExpression', node => {
if (
isMethodCall(node, {
methods: ['replaceChild', 'insertBefore'],
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
// We only allow Identifier for now
&& node.arguments.every(node => node.type === 'Identifier' && node.name !== 'undefined')
// This check makes sure that only the first method of chained methods with same identifier name e.g: parentNode.insertBefore(alfa, beta).insertBefore(charlie, delta); gets reported
&& node.callee.object.type === 'Identifier'
) {
return checkForReplaceChildOrInsertBefore(context, node);
}
});
context.on('CallExpression', node => {
if (
isMethodCall(node, {
methods: ['insertAdjacentText', 'insertAdjacentElement'],
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
// Position argument should be `string`
&& node.arguments[0].type === 'Literal'
// TODO: remove this limits on second argument
&& (
node.arguments[1].type === 'Literal'
|| node.arguments[1].type === 'Identifier'
)
// TODO: remove this limits on callee
&& node.callee.object.type === 'Identifier'
) {
return checkForInsertAdjacentTextOrInsertAdjacentElement(context, node);
}
});
};
/** @type {import('eslint').Rule.RuleModule} */

@@ -130,0 +136,0 @@ module.exports = {

'use strict';
const {getParenthesizedText} = require('./utils/parentheses.js');
const {
getParenthesizedText,
getParenthesizedRange,
isSameReference,
} = require('./utils/index.js');
const {isLiteral, isMethodCall} = require('./ast/index.js');
const {replaceNodeOrTokenAndSpacesBefore, removeParentheses} = require('./fix/index.js');

@@ -60,3 +66,3 @@ const MESSAGE_ID = 'prefer-modern-math-apis';

},
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.sourceCode)})`),
};

@@ -94,3 +100,3 @@ };

node,
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.sourceCode)})`),
};

@@ -107,2 +113,18 @@ };

const isPlusExpression = node => node.type === 'BinaryExpression' && node.operator === '+';
const isPow2Expression = node =>
node.type === 'BinaryExpression'
&& (
// `x * x`
(node.operator === '*' && isSameReference(node.left, node.right))
// `x ** 2`
|| (node.operator === '**' && isLiteral(node.right, 2))
);
const flatPlusExpression = node =>
isPlusExpression(node)
? [node.left, node.right].flatMap(child => flatPlusExpression(child))
: [node];
/** @param {import('eslint').Rule.RuleContext} context */

@@ -113,2 +135,54 @@ const create = context => {

return {
CallExpression(callExpression) {
if (!isMethodCall(callExpression, {
object: 'Math',
method: 'sqrt',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const expressions = flatPlusExpression(callExpression.arguments[0]);
if (expressions.some(expression => !isPow2Expression(expression))) {
return;
}
const replacementMethod = expressions.length === 1 ? 'abs' : 'hypot';
const plusExpressions = new Set(expressions.length === 1 ? [] : expressions.map(expression => expression.parent));
return {
node: callExpression.callee.property,
messageId: MESSAGE_ID,
data: {
replacement: `Math.${replacementMethod}(…)`,
description: 'Math.sqrt(…)',
},
* fix(fixer) {
const {sourceCode} = context;
// `Math.sqrt` -> `Math.{hypot,abs}`
yield fixer.replaceText(callExpression.callee.property, replacementMethod);
// `a ** 2 + b ** 2` -> `a, b`
for (const expression of plusExpressions) {
const plusToken = sourceCode.getTokenAfter(expression.left, token => token.type === 'Punctuator' && token.value === '+');
yield * replaceNodeOrTokenAndSpacesBefore(plusToken, ',', fixer, sourceCode);
yield * removeParentheses(expression, fixer, sourceCode);
}
// `x ** 2` => `x`
// `x * a` => `x`
for (const expression of expressions) {
yield fixer.removeRange([
getParenthesizedRange(expression.left, sourceCode)[1],
expression.range[1],
]);
}
},
};
},
BinaryExpression(node) {

@@ -115,0 +189,0 @@ nodes.push(node);

@@ -5,4 +5,3 @@ 'use strict';

const assertToken = require('./utils/assert-token.js');
const {referenceIdentifierSelector} = require('./selectors/index.js');
const {isStaticRequire} = require('./ast/index.js');
const {isStaticRequire, isReferenceIdentifier, isFunction} = require('./ast/index.js');
const {

@@ -33,10 +32,2 @@ removeParentheses,

const identifierSelector = referenceIdentifierSelector([
'exports',
'require',
'module',
'__filename',
'__dirname',
]);
function fixRequireCall(node, sourceCode) {

@@ -174,3 +165,12 @@ if (!isStaticRequire(node.parent) || node.parent.callee !== node) {

&& node.parent.property.name === 'exports';
const isTopLevelReturnStatement = node => {
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
if (isFunction(ancestor)) {
return false;
}
}
return true;
};
function fixDefaultExport(node, sourceCode) {

@@ -221,3 +221,3 @@ return function * (fixer) {

function create(context) {
const filename = context.getFilename().toLowerCase();
const filename = context.filename.toLowerCase();

@@ -228,21 +228,26 @@ if (filename.endsWith('.cjs')) {

const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
'ExpressionStatement[directive="use strict"]'(node) {
const problem = {node, messageId: ERROR_USE_STRICT_DIRECTIVE};
const fix = function * (fixer) {
yield fixer.remove(node);
yield removeSpacesAfter(node, sourceCode, fixer);
};
context.on('ExpressionStatement', node => {
if (node.directive !== 'use strict') {
return;
}
if (filename.endsWith('.mjs')) {
problem.fix = fix;
} else {
problem.suggest = [{messageId: SUGGESTION_USE_STRICT_DIRECTIVE, fix}];
}
const problem = {node, messageId: ERROR_USE_STRICT_DIRECTIVE};
const fix = function * (fixer) {
yield fixer.remove(node);
yield removeSpacesAfter(node, sourceCode, fixer);
};
return problem;
},
'ReturnStatement:not(:function ReturnStatement)'(node) {
if (filename.endsWith('.mjs')) {
problem.fix = fix;
} else {
problem.suggest = [{messageId: SUGGESTION_USE_STRICT_DIRECTIVE, fix}];
}
return problem;
});
context.on('ReturnStatement', node => {
if (isTopLevelReturnStatement(node)) {
return {

@@ -252,26 +257,47 @@ node: sourceCode.getFirstToken(node),

};
},
[identifierSelector](node) {
if (isShadowed(context.getScope(), node)) {
return;
}
}
});
const {name} = node;
context.on('Identifier', node => {
if (
!isReferenceIdentifier(node, [
'exports',
'require',
'module',
'__filename',
'__dirname',
])
|| isShadowed(sourceCode.getScope(node), node)
) {
return;
}
const problem = {
node,
messageId: ERROR_IDENTIFIER,
data: {name},
};
const {name} = node;
switch (name) {
case '__filename':
case '__dirname': {
const messageId = node.name === '__dirname' ? SUGGESTION_DIRNAME : SUGGESTION_FILENAME;
const replacement = node.name === '__dirname'
? 'path.dirname(url.fileURLToPath(import.meta.url))'
: 'url.fileURLToPath(import.meta.url)';
const problem = {
node,
messageId: ERROR_IDENTIFIER,
data: {name},
};
switch (name) {
case '__filename':
case '__dirname': {
const messageId = node.name === '__dirname' ? SUGGESTION_DIRNAME : SUGGESTION_FILENAME;
const replacement = node.name === '__dirname'
? 'path.dirname(url.fileURLToPath(import.meta.url))'
: 'url.fileURLToPath(import.meta.url)';
problem.suggest = [{
messageId,
fix: fixer => replaceReferenceIdentifier(node, replacement, fixer),
}];
return problem;
}
case 'require': {
const fix = fixRequireCall(node, sourceCode);
if (fix) {
problem.suggest = [{
messageId,
fix: fixer => replaceReferenceIdentifier(node, replacement, fixer),
messageId: SUGGESTION_IMPORT,
fix,
}];

@@ -281,47 +307,36 @@ return problem;

case 'require': {
const fix = fixRequireCall(node, sourceCode);
if (fix) {
problem.suggest = [{
messageId: SUGGESTION_IMPORT,
fix,
}];
return problem;
}
break;
}
break;
case 'exports': {
const fix = fixExports(node, sourceCode);
if (fix) {
problem.suggest = [{
messageId: SUGGESTION_EXPORT,
fix,
}];
return problem;
}
case 'exports': {
const fix = fixExports(node, sourceCode);
if (fix) {
problem.suggest = [{
messageId: SUGGESTION_EXPORT,
fix,
}];
return problem;
}
break;
}
break;
case 'module': {
const fix = fixModuleExports(node, sourceCode);
if (fix) {
problem.suggest = [{
messageId: SUGGESTION_EXPORT,
fix,
}];
return problem;
}
case 'module': {
const fix = fixModuleExports(node, sourceCode);
if (fix) {
problem.suggest = [{
messageId: SUGGESTION_EXPORT,
fix,
}];
return problem;
}
break;
}
break;
}
default:
}
default:
}
return problem;
},
};
return problem;
});
}

@@ -328,0 +343,0 @@

'use strict';
const {getFunctionHeadLocation, getFunctionNameWithKind} = require('@eslint-community/eslint-utils');
const {not} = require('./selectors/index.js');
const {functionTypes} = require('./ast/index.js');

@@ -72,15 +72,2 @@ const MESSAGE_ID = 'prefer-native-coercion-functions';

const functionsSelector = [
':function',
'[async!=true]',
'[generator!=true]',
'[params.length>0]',
'[params.0.type="Identifier"]',
not([
'MethodDefinition[kind="constructor"] > .value',
'MethodDefinition[kind="set"] > .value',
'Property[kind="set"] > .value',
]),
].join('');
function getArrayCallbackProblem(node) {

@@ -133,4 +120,23 @@ if (!isArrayIdentityCallback(node)) {

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[functionsSelector](node) {
const create = context => {
context.on(functionTypes, node => {
if (
node.async
|| node.generator
|| node.params.length === 0
|| node.params[0].type !== 'Identifier'
|| (
(
(
node.parent.type === 'MethodDefinition'
&& (node.parent.kind === 'constructor' || node.parent.kind === 'set')
)
|| (node.parent.type === 'Property' && node.parent.kind === 'set')
)
&& node.parent.value === node
)
) {
return;
}
let problem = getArrayCallbackProblem(node) || getCoercionFunctionProblem(node);

@@ -142,3 +148,3 @@

const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {replacementFunction, fix} = problem;

@@ -168,4 +174,4 @@

return problem;
},
});
});
};

@@ -172,0 +178,0 @@ /** @type {import('eslint').Rule.RuleModule} */

@@ -40,2 +40,11 @@ 'use strict';

[
'toSpliced',
{
argumentsIndexes: [0],
supportObjects: new Set([
'Array',
]),
},
],
[
'at',

@@ -51,2 +60,12 @@ {

],
[
'with',
{
argumentsIndexes: [0],
supportObjects: new Set([
'Array',
...typedArray,
]),
},
],
]);

@@ -97,3 +116,3 @@

if (
// [].{slice,splice}
// `[].{slice,splice,toSpliced,at,with}`
(

@@ -103,3 +122,3 @@ parentCallee.type === 'ArrayExpression'

)
// ''.slice
// `''.slice`
|| (

@@ -140,3 +159,7 @@ method === 'slice'

const create = context => ({
'CallExpression[callee.type="MemberExpression"]'(node) {
CallExpression(node) {
if (node.callee.type !== 'MemberExpression') {
return;
}
const parsed = parse(node);

@@ -168,3 +191,3 @@

* fix(fixer) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
for (const node of removableNodes) {

@@ -184,3 +207,3 @@ yield removeLengthNode(node, fixer, sourceCode);

docs: {
description: 'Prefer negative index over `.length - index` for `{String,Array,TypedArray}#{slice,at}()` and `Array#splice()`.',
description: 'Prefer negative index over `.length - index` when possible.',
},

@@ -187,0 +210,0 @@ fixable: 'code',

'use strict';
const isBuiltinModule = require('is-builtin-module');
const {matches, STATIC_REQUIRE_SOURCE_SELECTOR} = require('./selectors/index.js');
const {replaceStringLiteral} = require('./fix/index.js');
const isStaticRequire = require('./ast/is-static-require.js');

@@ -11,16 +11,23 @@ const MESSAGE_ID = 'prefer-node-protocol';

const importExportSourceSelector = [
':matches(ImportDeclaration, ExportNamedDeclaration, ImportExpression)',
' > ',
'Literal.source',
].join('');
const create = () => ({
Literal(node) {
if (!(
(
(
node.parent.type === 'ImportDeclaration'
|| node.parent.type === 'ExportNamedDeclaration'
|| node.parent.type === 'ImportExpression'
)
&& node.parent.source === node
)
|| (
isStaticRequire(node.parent)
&& node.parent.arguments[0] === node
)
)) {
return;
}
const selector = matches([
importExportSourceSelector,
STATIC_REQUIRE_SOURCE_SELECTOR,
]);
const {value} = node;
const create = () => ({
[selector](node) {
const {value} = node;
if (

@@ -27,0 +34,0 @@ typeof value !== 'string'

@@ -5,2 +5,3 @@ 'use strict';

const {fixSpaceAroundKeyword} = require('./fix/index.js');
const isLeftHandSide = require('./utils/is-left-hand-side.js');

@@ -84,3 +85,3 @@ const MESSAGE_ID_ERROR = 'error';

};
const sourceCode = context.getSourceCode();
const {sourceCode} = context;

@@ -95,2 +96,3 @@ let objects = Object.keys(globalObjects);

handle: reference => checkProperty(reference, sourceCode),
filter: ({node}) => !isLeftHandSide(node),
});

@@ -97,0 +99,0 @@

'use strict';
const {isCommaToken, isArrowToken, isClosingParenToken} = require('@eslint-community/eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url.js');
const {matches, methodCallSelector} = require('./selectors/index.js');
const {isMethodCall, isLiteral} = require('./ast/index.js');
const {removeParentheses} = require('./fix/index.js');
const {getParentheses, getParenthesizedText} = require('./utils/parentheses.js');
const {isNodeMatches, isNodeMatchesNameOrPath} = require('./utils/is-node-matches.js');
const {
getParentheses,
getParenthesizedText,
isNodeMatchesNameOrPath,
isSameIdentifier,
} = require('./utils/index.js');
const {isCallExpression} = require('./ast/call-or-new-expression.js');

@@ -16,70 +20,69 @@ const MESSAGE_ID_REDUCE = 'reduce';

const createEmptyObjectSelector = path => {
const prefix = path ? `${path}.` : '';
return matches([
// `{}`
`[${prefix}type="ObjectExpression"][${prefix}properties.length=0]`,
// `Object.create(null)`
[
methodCallSelector({path, object: 'Object', method: 'create', argumentsLength: 1}),
`[${prefix}arguments.0.type="Literal"]`,
`[${prefix}arguments.0.raw="null"]`,
].join(''),
]);
};
const isEmptyObject = node =>
// `{}`
(node.type === 'ObjectExpression' && node.properties.length === 0)
// `Object.create(null)`
|| (
isMethodCall(node, {
object: 'Object',
method: 'create',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
// eslint-disable-next-line unicorn/no-null
&& isLiteral(node.arguments[0], null)
);
const createArrowCallbackSelector = path => {
const prefix = path ? `${path}.` : '';
return [
`[${prefix}type="ArrowFunctionExpression"]`,
`[${prefix}async!=true]`,
`[${prefix}generator!=true]`,
`[${prefix}params.length>=1]`,
`[${prefix}params.0.type="Identifier"]`,
].join('');
};
const isArrowFunctionCallback = node =>
node.type === 'ArrowFunctionExpression'
&& !node.async
&& node.params.length > 0
&& node.params[0].type === 'Identifier';
const createPropertySelector = path => {
const prefix = path ? `${path}.` : '';
return [
`[${prefix}type="Property"]`,
`[${prefix}kind="init"]`,
`[${prefix}method!=true]`,
].join('');
};
const isProperty = node =>
node.type === 'Property'
&& node.kind === 'init'
&& !node.method;
// - `pairs.reduce(…, {})`
// - `pairs.reduce(…, Object.create(null))`
const arrayReduceWithEmptyObject = [
methodCallSelector({method: 'reduce', argumentsLength: 2}),
createEmptyObjectSelector('arguments.1'),
].join('');
const isArrayReduceWithEmptyObject = node =>
isMethodCall(node, {
method: 'reduce',
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
&& isEmptyObject(node.arguments[1]);
const fixableArrayReduceCases = [
{
selector: [
arrayReduceWithEmptyObject,
// () => Object.assign(object, {key})
createArrowCallbackSelector('arguments.0'),
methodCallSelector({path: 'arguments.0.body', object: 'Object', method: 'assign', argumentsLength: 2}),
'[arguments.0.body.arguments.0.type="Identifier"]',
'[arguments.0.body.arguments.1.type="ObjectExpression"]',
'[arguments.0.body.arguments.1.properties.length=1]',
createPropertySelector('arguments.0.body.arguments.1.properties.0'),
].join(''),
test: callback => callback.params[0].name === callback.body.arguments[0].name,
test: callExpression =>
isArrayReduceWithEmptyObject(callExpression)
// `() => Object.assign(object, {key})`
&& isArrowFunctionCallback(callExpression.arguments[0])
&& isMethodCall(callExpression.arguments[0].body, {
object: 'Object',
method: 'assign',
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})
&& callExpression.arguments[0].body.arguments[1].type === 'ObjectExpression'
&& callExpression.arguments[0].body.arguments[1].properties.length === 1
&& isProperty(callExpression.arguments[0].body.arguments[1].properties[0])
&& isSameIdentifier(callExpression.arguments[0].params[0], callExpression.arguments[0].body.arguments[0]),
getProperty: callback => callback.body.arguments[1].properties[0],
},
{
selector: [
arrayReduceWithEmptyObject,
// () => ({...object, key})
createArrowCallbackSelector('arguments.0'),
'[arguments.0.body.type="ObjectExpression"]',
'[arguments.0.body.properties.length=2]',
'[arguments.0.body.properties.0.type="SpreadElement"]',
'[arguments.0.body.properties.0.argument.type="Identifier"]',
createPropertySelector('arguments.0.body.properties.1'),
].join(''),
test: callback => callback.params[0].name === callback.body.properties[0].argument.name,
test: callExpression =>
isArrayReduceWithEmptyObject(callExpression)
// `() => ({...object, key})`
&& isArrowFunctionCallback(callExpression.arguments[0])
&& callExpression.arguments[0].body.type === 'ObjectExpression'
&& callExpression.arguments[0].body.properties.length === 2
&& callExpression.arguments[0].body.properties[0].type === 'SpreadElement'
&& isProperty(callExpression.arguments[0].body.properties[1])
&& isSameIdentifier(callExpression.arguments[0].params[0], callExpression.arguments[0].body.properties[0].argument),
getProperty: callback => callback.body.properties[1],

@@ -94,13 +97,6 @@ },

];
const anyCall = [
'CallExpression',
'[optional!=true]',
'[arguments.length=1]',
'[arguments.0.type!="SpreadElement"]',
' > .callee',
].join('');
function fixReduceAssignOrSpread({sourceCode, node, property}) {
function fixReduceAssignOrSpread({sourceCode, callExpression, property}) {
const removeInitObject = fixer => {
const initObject = node.arguments[1];
const initObject = callExpression.arguments[1];
const parentheses = getParentheses(initObject, sourceCode);

@@ -116,3 +112,3 @@ const firstToken = parentheses[0] || initObject;

function * removeFirstParameter(fixer) {
const parameters = node.arguments[0].params;
const parameters = callExpression.arguments[0].params;
const [firstParameter] = parameters;

@@ -151,3 +147,3 @@ const tokenAfter = sourceCode.getTokenAfter(firstParameter);

function * replaceFunctionBody(fixer) {
const functionBody = node.arguments[0].body;
const functionBody = callExpression.arguments[0].body;
const {keyText, valueText} = getKeyValueText();

@@ -160,7 +156,7 @@ yield fixer.replaceText(functionBody, `[${keyText}, ${valueText}]`);

// Wrap `array.reduce()` with `Object.fromEntries()`
yield fixer.insertTextBefore(node, 'Object.fromEntries(');
yield fixer.insertTextAfter(node, ')');
yield fixer.insertTextBefore(callExpression, 'Object.fromEntries(');
yield fixer.insertTextAfter(callExpression, ')');
// Switch `.reduce` to `.map`
yield fixer.replaceText(node.callee.property, 'map');
yield fixer.replaceText(callExpression.callee.property, 'map');

@@ -180,2 +176,3 @@ // Remove empty object

function create(context) {
const {sourceCode} = context;
const {functions: configFunctions} = {

@@ -186,57 +183,50 @@ functions: [],

const functions = [...configFunctions, ...lodashFromPairsFunctions];
const sourceCode = context.getSourceCode();
const listeners = {};
const arrayReduce = new Map();
for (const {selector, test, getProperty} of fixableArrayReduceCases) {
listeners[selector] = node => {
const [callbackFunction] = node.arguments;
if (!test(callbackFunction)) {
return;
return {
* CallExpression(callExpression) {
for (const {test, getProperty} of fixableArrayReduceCases) {
if (!test(callExpression)) {
continue;
}
const [callbackFunction] = callExpression.arguments;
const [firstParameter] = callbackFunction.params;
const variables = sourceCode.getDeclaredVariables(callbackFunction);
const firstParameterVariable = variables.find(variable => variable.identifiers.length === 1 && variable.identifiers[0] === firstParameter);
if (!firstParameterVariable || firstParameterVariable.references.length !== 1) {
continue;
}
yield {
node: callExpression.callee.property,
messageId: MESSAGE_ID_REDUCE,
fix: fixReduceAssignOrSpread({
sourceCode,
callExpression,
property: getProperty(callbackFunction),
}),
};
}
const [firstParameter] = callbackFunction.params;
const variables = context.getDeclaredVariables(callbackFunction);
const firstParameterVariable = variables.find(variable => variable.identifiers.length === 1 && variable.identifiers[0] === firstParameter);
if (!firstParameterVariable || firstParameterVariable.references.length !== 1) {
if (!isCallExpression(callExpression, {
argumentsLength: 1,
optional: false,
})) {
return;
}
arrayReduce.set(
node,
// The fix function
fixReduceAssignOrSpread({
sourceCode,
node,
property: getProperty(callbackFunction),
}),
);
};
}
listeners['Program:exit'] = () => {
for (const [node, fix] of arrayReduce.entries()) {
context.report({
node: node.callee.property,
messageId: MESSAGE_ID_REDUCE,
fix,
});
}
const functionNode = callExpression.callee;
for (const nameOrPath of functions) {
const functionName = nameOrPath.trim();
if (isNodeMatchesNameOrPath(functionNode, functionName)) {
yield {
node: functionNode,
messageId: MESSAGE_ID_FUNCTION,
data: {functionName},
fix: fixer => fixer.replaceText(functionNode, 'Object.fromEntries'),
};
}
}
},
};
listeners[anyCall] = node => {
if (!isNodeMatches(node, functions)) {
return;
}
const functionName = functions.find(nameOrPath => isNodeMatchesNameOrPath(node, nameOrPath)).trim();
context.report({
node,
messageId: MESSAGE_ID_FUNCTION,
data: {functionName},
fix: fixer => fixer.replaceText(node, 'Object.fromEntries'),
});
};
return listeners;
}

@@ -264,3 +254,2 @@

description: 'Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object.',
url: getDocumentationUrl(__filename),
},

@@ -267,0 +256,0 @@ fixable: 'code',

@@ -12,13 +12,13 @@ 'use strict';

const selector = [
'CatchClause',
' > ',
'.param',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const variables = context.getDeclaredVariables(node.parent);
CatchClause(catchClause) {
const node = catchClause.param;
if (!node) {
return;
}
const {sourceCode} = context;
const variables = sourceCode.getDeclaredVariables(node.parent);
if (variables.some(variable => variable.references.length > 0)) {

@@ -35,3 +35,2 @@ return;

* fix(fixer) {
const sourceCode = context.getSourceCode();
const tokenBefore = sourceCode.getTokenBefore(node);

@@ -38,0 +37,0 @@ assertToken(tokenBefore, {

'use strict';
const {getPropertyName} = require('@eslint-community/eslint-utils');
const {
methodCallSelector,
emptyObjectSelector,
emptyArraySelector,
matches,
} = require('./selectors/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isMemberExpression, isMethodCall} = require('./ast/index.js');

@@ -16,44 +11,61 @@ const messages = {

const emptyObjectOrArrayMethodSelector = [
'MemberExpression',
matches([emptyObjectSelector('object'), emptyArraySelector('object')]),
].join('');
const selector = matches([
// `[].foo.{apply,bind,call}(…)`
// `({}).foo.{apply,bind,call}(…)`
[
methodCallSelector(['apply', 'bind', 'call']),
' > ',
'.callee',
' > ',
`${emptyObjectOrArrayMethodSelector}.object`,
].join(''),
// `Reflect.apply([].foo, …)`
// `Reflect.apply({}.foo, …)`
[
methodCallSelector({object: 'Reflect', method: 'apply', minimumArguments: 1}),
' > ',
`${emptyObjectOrArrayMethodSelector}.arguments:first-child`,
].join(''),
]);
/** @param {import('eslint').Rule.RuleContext} context */
function create(context) {
return {
[selector](node) {
const constructorName = node.object.type === 'ArrayExpression' ? 'Array' : 'Object';
const methodName = getPropertyName(node, context.getScope());
CallExpression(callExpression) {
let methodNode;
if (
// `Reflect.apply([].foo, …)`
// `Reflect.apply({}.foo, …)`
isMethodCall(callExpression, {
object: 'Reflect',
method: 'apply',
minimumArguments: 1,
optionalCall: false,
optionalMember: false,
})
) {
methodNode = callExpression.arguments[0];
} else if (
// `[].foo.{apply,bind,call}(…)`
// `({}).foo.{apply,bind,call}(…)`
isMethodCall(callExpression, {
names: ['apply', 'bind', 'call'],
optionalCall: false,
optionalMember: false,
})
) {
methodNode = callExpression.callee.object;
}
if (!methodNode || !isMemberExpression(methodNode, {optional: false})) {
return;
}
const objectNode = methodNode.object;
if (!(
(objectNode.type === 'ArrayExpression' && objectNode.elements.length === 0)
|| (objectNode.type === 'ObjectExpression' && objectNode.properties.length === 0)
)) {
return;
}
const constructorName = objectNode.type === 'ArrayExpression' ? 'Array' : 'Object';
const {sourceCode} = context;
const methodName = getPropertyName(methodNode, sourceCode.getScope(methodNode));
return {
node,
node: methodNode,
messageId: methodName ? 'known-method' : 'unknown-method',
data: {constructorName, methodName},
* fix(fixer) {
yield fixer.replaceText(node.object, `${constructorName}.prototype`);
yield fixer.replaceText(objectNode, `${constructorName}.prototype`);
if (
node.object.type === 'ArrayExpression'
|| node.object.type === 'ObjectExpression'
objectNode.type === 'ArrayExpression'
|| objectNode.type === 'ObjectExpression'
) {
yield * fixSpaceAroundKeyword(fixer, node.parent.parent, context.getSourceCode());
yield * fixSpaceAroundKeyword(fixer, callExpression, sourceCode);
}

@@ -60,0 +72,0 @@ },

'use strict';
const {methodCallSelector, notDomNodeSelector} = require('./selectors/index.js');
const {isStringLiteral, isNullLiteral} = require('./ast/index.js');
const {isNodeValueNotDomNode} = require('./utils/index.js');
const {isMethodCall, isStringLiteral, isNullLiteral} = require('./ast/index.js');

@@ -10,10 +10,2 @@ const MESSAGE_ID = 'prefer-query-selector';

const selector = [
methodCallSelector({
methods: ['getElementById', 'getElementsByClassName', 'getElementsByTagName'],
argumentsLength: 1,
}),
notDomNodeSelector('callee.object'),
].join('');
const disallowedIdentifierNames = new Map([

@@ -100,3 +92,15 @@ ['getElementById', 'querySelector'],

const create = () => ({
[selector](node) {
CallExpression(node) {
if (
!isMethodCall(node, {
methods: ['getElementById', 'getElementsByClassName', 'getElementsByTagName'],
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
|| isNodeValueNotDomNode(node.callee.object)
) {
return;
}
const method = node.callee.property.name;

@@ -103,0 +107,0 @@ const preferredSelector = disallowedIdentifierNames.get(method);

'use strict';
const {getPropertyName} = require('@eslint-community/eslint-utils');
const {not, methodCallSelector} = require('./selectors/index.js');
const {isNullLiteral} = require('./ast/index.js');
const {isNullLiteral, isMethodCall} = require('./ast/index.js');

@@ -11,7 +10,2 @@ const MESSAGE_ID = 'prefer-reflect-apply';

const selector = [
methodCallSelector({allowComputed: true}),
not(['Literal', 'ArrayExpression', 'ObjectExpression'].map(type => `[callee.object.type=${type}]`)),
].join('');
const isApplySignature = (argument1, argument2) => (

@@ -68,4 +62,16 @@ (

const create = context => ({
[selector](node) {
const sourceCode = context.getSourceCode();
CallExpression(node) {
if (
!isMethodCall(node, {
optionalCall: false,
optionalMember: false,
})
|| node.callee.object.type === 'Literal'
|| node.callee.object.type === 'ArrayExpression'
|| node.callee.object.type === 'ObjectExpression'
) {
return;
}
const {sourceCode} = context;
const fix = fixDirectApplyCall(node, sourceCode) || fixFunctionPrototypeCall(node, sourceCode);

@@ -72,0 +78,0 @@ if (fix) {

'use strict';
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
const {checkVueTemplate} = require('./utils/rule.js');
const {methodCallSelector} = require('./selectors/index.js');
const {isRegexLiteral, isNewExpression} = require('./ast/index.js');
const {isBooleanNode} = require('./utils/boolean.js');
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
const {isRegexLiteral, isNewExpression, isMethodCall} = require('./ast/index.js');
const {
isBooleanNode,
shouldAddParenthesesToMemberExpressionObject,
} = require('./utils/index.js');

@@ -21,5 +22,7 @@ const REGEXP_EXEC = 'regexp-exec';

type: REGEXP_EXEC,
selector: methodCallSelector({
test: node => isMethodCall(node, {
method: 'exec',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
}),

@@ -35,5 +38,7 @@ getNodes: node => ({

type: STRING_MATCH,
selector: methodCallSelector({
test: node => isMethodCall(node, {
method: 'match',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
}),

@@ -92,11 +97,13 @@ getNodes: node => ({

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => Object.fromEntries(
cases.map(checkCase => [
checkCase.selector,
node => {
if (!isBooleanNode(node)) {
return;
const create = context => ({
* CallExpression(node) {
if (!isBooleanNode(node)) {
return;
}
for (const {type, test, getNodes, fix} of cases) {
if (!test(node)) {
continue;
}
const {type, getNodes, fix} = checkCase;
const nodes = getNodes(node);

@@ -106,3 +113,3 @@ const {methodNode, regexpNode} = nodes;

if (regexpNode.type === 'Literal' && !regexpNode.regex) {
return;
continue;
}

@@ -115,7 +122,8 @@

const fixFunction = fixer => fix(fixer, nodes, context.getSourceCode());
const {sourceCode} = context;
const fixFunction = fixer => fix(fixer, nodes, sourceCode);
if (
isRegExpNode(regexpNode)
|| isRegExpWithoutGlobalFlag(regexpNode, context.getScope())
|| isRegExpWithoutGlobalFlag(regexpNode, sourceCode.getScope(regexpNode))
) {

@@ -132,6 +140,6 @@ problem.fix = fixFunction;

return problem;
},
]),
);
yield problem;
}
},
});

@@ -138,0 +146,0 @@ /** @type {import('eslint').Rule.RuleModule} */

'use strict';
const {findVariable} = require('@eslint-community/eslint-utils');
const getVariableIdentifiers = require('./utils/get-variable-identifiers.js');
const {
matches,
not,
methodCallSelector,
callOrNewExpressionSelector,
} = require('./selectors/index.js');
const {getVariableIdentifiers} = require('./utils/index.js');
const {isCallOrNewExpression, isMethodCall} = require('./ast/index.js');

@@ -18,61 +13,20 @@ const MESSAGE_ID_ERROR = 'error';

// `[]`
const arrayExpressionSelector = [
'[init.type="ArrayExpression"]',
].join('');
const arrayMethodsReturnsArray = [
'concat',
'copyWithin',
'fill',
'filter',
'flat',
'flatMap',
'map',
'reverse',
'slice',
'sort',
'splice',
'toReversed',
'toSorted',
'toSpliced',
'with',
];
// `Array()` and `new Array()`
const newArraySelector = callOrNewExpressionSelector({name: 'Array', path: 'init'});
// `Array.from()` and `Array.of()`
const arrayStaticMethodSelector = methodCallSelector({
object: 'Array',
methods: ['from', 'of'],
path: 'init',
});
// `array.concat()`
// `array.copyWithin()`
// `array.fill()`
// `array.filter()`
// `array.flat()`
// `array.flatMap()`
// `array.map()`
// `array.reverse()`
// `array.slice()`
// `array.sort()`
// `array.splice()`
const arrayMethodSelector = methodCallSelector({
methods: [
'concat',
'copyWithin',
'fill',
'filter',
'flat',
'flatMap',
'map',
'reverse',
'slice',
'sort',
'splice',
],
path: 'init',
});
const selector = [
'VariableDeclaration',
// Exclude `export const foo = [];`
not('ExportNamedDeclaration > .declaration'),
' > ',
'VariableDeclarator.declarations',
matches([
arrayExpressionSelector,
newArraySelector,
arrayStaticMethodSelector,
arrayMethodSelector,
]),
' > ',
'Identifier.id',
].join('');
const isIncludesCall = node => {

@@ -124,5 +78,44 @@ const {type, optional, callee, arguments: includesArguments} = node.parent.parent ?? {};

const create = context => ({
[selector](node) {
const variable = findVariable(context.getScope(), node);
Identifier(node) {
const {parent} = node;
if (!(
parent.type === 'VariableDeclarator'
&& parent.id === node
&& Boolean(parent.init)
&& parent.parent.type === 'VariableDeclaration'
&& parent.parent.declarations.includes(parent)
// Exclude `export const foo = [];`
&& !(
parent.parent.parent.type === 'ExportNamedDeclaration'
&& parent.parent.parent.declaration === parent.parent
)
&& (
// `[]`
parent.init.type === 'ArrayExpression'
// `Array()` and `new Array()`
|| isCallOrNewExpression(parent.init, {
name: 'Array',
optional: false,
})
// `Array.from()` and `Array.of()`
|| isMethodCall(parent.init, {
object: 'Array',
methods: ['from', 'of'],
optionalCall: false,
optionalMember: false,
})
// Array methods that return an array
|| isMethodCall(parent.init, {
methods: arrayMethodsReturnsArray,
optionalCall: false,
optionalMember: false,
})
)
)) {
return;
}
const variable = findVariable(context.sourceCode.getScope(node), node);
// This was reported https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1075#issuecomment-768073342

@@ -129,0 +122,0 @@ // But can't reproduce, just ignore this case

'use strict';
const {findVariable} = require('@eslint-community/eslint-utils');
const {memberExpressionSelector} = require('./selectors/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isNewExpression} = require('./ast/index.js');
const {isNewExpression, isMemberExpression} = require('./ast/index.js');

@@ -12,9 +11,2 @@ const MESSAGE_ID = 'prefer-set-size';

const lengthAccessSelector = [
memberExpressionSelector('length'),
'[object.type="ArrayExpression"]',
'[object.elements.length=1]',
'[object.elements.0.type="SpreadElement"]',
].join('');
const isNewSet = node => isNewExpression(node, {name: 'Set'});

@@ -71,8 +63,20 @@

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[lengthAccessSelector](node) {
MemberExpression(node) {
if (
!isMemberExpression(node, {
property: 'length',
optional: false,
})
|| node.object.type !== 'ArrayExpression'
|| node.object.elements.length !== 1
|| node.object.elements[0]?.type !== 'SpreadElement'
) {
return;
}
const maybeSet = node.object.elements[0].argument;
if (!isSet(maybeSet, context.getScope())) {
if (!isSet(maybeSet, sourceCode.getScope(maybeSet))) {
return;

@@ -79,0 +83,0 @@ }

'use strict';
const {isParenthesized, getStaticValue, isCommaToken, hasSideEffect} = require('@eslint-community/eslint-utils');
const {methodCallSelector} = require('./selectors/index.js');
const needsSemicolon = require('./utils/needs-semicolon.js');
const {getParenthesizedRange, getParenthesizedText} = require('./utils/parentheses.js');
const shouldAddParenthesesToSpreadElementArgument = require('./utils/should-add-parentheses-to-spread-element-argument.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const {
replaceNodeOrTokenAndSpacesBefore,
removeSpacesAfter,
removeMethodCall,
} = require('./fix/index.js');
const {isLiteral} = require('./ast/index.js');
const isMethodNamed = require('./utils/is-method-named.js');
getParenthesizedRange,
getParenthesizedText,
needsSemicolon,
shouldAddParenthesesToSpreadElementArgument,
isNodeMatches,
isMethodNamed,
} = require('./utils/index.js');
const {removeMethodCall} = require('./fix/index.js');
const {isLiteral, isMethodCall} = require('./ast/index.js');

@@ -19,2 +17,3 @@ const ERROR_ARRAY_FROM = 'array-from';

const ERROR_ARRAY_SLICE = 'array-slice';
const ERROR_ARRAY_TO_SPLICED = 'array-to-spliced';
const ERROR_STRING_SPLIT = 'string-split';

@@ -30,2 +29,3 @@ const SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE = 'argument-is-spreadable';

[ERROR_ARRAY_SLICE]: 'Prefer the spread operator over `Array#slice()`.',
[ERROR_ARRAY_TO_SPLICED]: 'Prefer the spread operator over `Array#toSpliced()`.',
[ERROR_STRING_SPLIT]: 'Prefer the spread operator over `String#split(\'\')`.',

@@ -39,24 +39,2 @@ [SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE]: 'First argument is an `array`.',

const arrayFromCallSelector = [
methodCallSelector({
object: 'Array',
method: 'from',
minimumArguments: 1,
maximumArguments: 3,
}),
// Allow `Array.from({length})`
'[arguments.0.type!="ObjectExpression"]',
].join('');
const arrayConcatCallSelector = methodCallSelector('concat');
const arraySliceCallSelector = [
methodCallSelector({
method: 'slice',
minimumArguments: 0,
maximumArguments: 1,
}),
'[callee.object.type!="ArrayExpression"]',
].join('');
const ignoredSliceCallee = [

@@ -70,7 +48,2 @@ 'arrayBuffer',

const stringSplitCallSelector = methodCallSelector({
method: 'split',
argumentsLength: 1,
});
const isArrayLiteral = node => node.type === 'ArrayExpression';

@@ -263,9 +236,2 @@ const isArrayLiteralHasTrailingComma = (node, sourceCode) => {

function * removeObject(fixer) {
yield * replaceNodeOrTokenAndSpacesBefore(object, '', fixer, sourceCode);
const commaToken = sourceCode.getTokenAfter(object, isCommaToken);
yield * replaceNodeOrTokenAndSpacesBefore(commaToken, '', fixer, sourceCode);
yield removeSpacesAfter(commaToken, sourceCode, fixer);
}
return function * (fixer) {

@@ -279,11 +245,3 @@ // Fixed code always starts with `[`

if (node.arguments.length === 1) {
yield fixer.replaceText(node, objectText);
return;
}
// `Array.from(object, mapFunction, thisArgument)` -> `[...object].map(mapFunction, thisArgument)`
yield fixer.replaceText(node.callee.object, objectText);
yield fixer.replaceText(node.callee.property, 'map');
yield * removeObject(fixer);
yield fixer.replaceText(node, objectText);
};

@@ -344,6 +302,17 @@ }

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[arrayFromCallSelector](node) {
// `Array.from()`
context.on('CallExpression', node => {
if (
isMethodCall(node, {
object: 'Array',
method: 'from',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
// Allow `Array.from({length})`
&& node.arguments[0].type !== 'ObjectExpression'
) {
return {

@@ -354,145 +323,201 @@ node,

};
},
[arrayConcatCallSelector](node) {
const {object} = node.callee;
}
});
if (isNotArray(object, context.getScope())) {
return;
}
// `array.concat()`
context.on('CallExpression', node => {
if (!isMethodCall(node, {
method: 'concat',
optionalCall: false,
optionalMember: false,
})) {
return;
}
const scope = context.getScope();
const staticResult = getStaticValue(object, scope);
const {object} = node.callee;
const scope = sourceCode.getScope(object);
if (staticResult && !Array.isArray(staticResult.value)) {
return;
}
if (isNotArray(object, scope)) {
return;
}
const problem = {
node: node.callee.property,
messageId: ERROR_ARRAY_CONCAT,
};
const staticResult = getStaticValue(object, scope);
if (staticResult && !Array.isArray(staticResult.value)) {
return;
}
const fixableArguments = getConcatFixableArguments(node.arguments, scope);
const problem = {
node: node.callee.property,
messageId: ERROR_ARRAY_CONCAT,
};
if (fixableArguments.length > 0 || node.arguments.length === 0) {
problem.fix = fixConcat(node, sourceCode, fixableArguments);
return problem;
}
const fixableArguments = getConcatFixableArguments(node.arguments, scope);
const [firstArgument, ...restArguments] = node.arguments;
if (firstArgument.type === 'SpreadElement') {
return problem;
}
if (fixableArguments.length > 0 || node.arguments.length === 0) {
problem.fix = fixConcat(node, sourceCode, fixableArguments);
return problem;
}
const fixableArgumentsAfterFirstArgument = getConcatFixableArguments(restArguments, scope);
const suggestions = [
{
messageId: SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE,
isSpreadable: true,
},
{
messageId: SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE,
isSpreadable: false,
},
];
const [firstArgument, ...restArguments] = node.arguments;
if (firstArgument.type === 'SpreadElement') {
return problem;
}
if (!hasSideEffect(firstArgument, sourceCode)) {
suggestions.push({
messageId: SUGGESTION_CONCAT_TEST_ARGUMENT,
testArgument: true,
});
}
const fixableArgumentsAfterFirstArgument = getConcatFixableArguments(restArguments, scope);
const suggestions = [
{
messageId: SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE,
isSpreadable: true,
},
{
messageId: SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE,
isSpreadable: false,
},
];
problem.suggest = suggestions.map(({messageId, isSpreadable, testArgument}) => ({
messageId,
if (!hasSideEffect(firstArgument, sourceCode)) {
suggestions.push({
messageId: SUGGESTION_CONCAT_TEST_ARGUMENT,
testArgument: true,
});
}
problem.suggest = suggestions.map(({messageId, isSpreadable, testArgument}) => ({
messageId,
fix: fixConcat(
node,
sourceCode,
// When apply suggestion, we also merge fixable arguments after the first one
[
{
node: firstArgument,
isSpreadable,
testArgument,
},
...fixableArgumentsAfterFirstArgument,
],
),
}));
if (
fixableArgumentsAfterFirstArgument.length < restArguments.length
&& restArguments.every(({type}) => type !== 'SpreadElement')
) {
problem.suggest.push({
messageId: SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS,
fix: fixConcat(
node,
sourceCode,
// When apply suggestion, we also merge fixable arguments after the first one
[
{
node: firstArgument,
isSpreadable,
testArgument,
},
...fixableArgumentsAfterFirstArgument,
],
node.arguments.map(node => getConcatArgumentSpreadable(node, scope) || {node, isSpreadable: true}),
),
}));
});
}
if (
fixableArgumentsAfterFirstArgument.length < restArguments.length
&& restArguments.every(({type}) => type !== 'SpreadElement')
) {
problem.suggest.push({
messageId: SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS,
fix: fixConcat(
node,
sourceCode,
node.arguments.map(node => getConcatArgumentSpreadable(node, scope) || {node, isSpreadable: true}),
),
});
}
return problem;
});
return problem;
},
[arraySliceCallSelector](node) {
if (isNodeMatches(node.callee.object, ignoredSliceCallee)) {
return;
}
// `array.slice()`
context.on('CallExpression', node => {
if (!(
isMethodCall(node, {
method: 'slice',
minimumArguments: 0,
maximumArguments: 1,
optionalCall: false,
optionalMember: false,
})
&& node.callee.object.type !== 'ArrayExpression'
)) {
return;
}
const [firstArgument] = node.arguments;
if (firstArgument && !isLiteral(firstArgument, 0)) {
return;
}
if (isNodeMatches(node.callee.object, ignoredSliceCallee)) {
return;
}
return {
node: node.callee.property,
messageId: ERROR_ARRAY_SLICE,
fix: methodCallToSpread(node, sourceCode),
};
},
[stringSplitCallSelector](node) {
const [separator] = node.arguments;
if (!isLiteral(separator, '')) {
return;
}
const [firstArgument] = node.arguments;
if (firstArgument && !isLiteral(firstArgument, 0)) {
return;
}
const string = node.callee.object;
const staticValue = getStaticValue(string, context.getScope());
let hasSameResult = false;
if (staticValue) {
const {value} = staticValue;
return {
node: node.callee.property,
messageId: ERROR_ARRAY_SLICE,
fix: methodCallToSpread(node, sourceCode),
};
});
if (typeof value !== 'string') {
return;
}
// `array.toSpliced()`
context.on('CallExpression', node => {
if (!(
isMethodCall(node, {
method: 'toSpliced',
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
&& node.callee.object.type !== 'ArrayExpression'
)) {
return;
}
// eslint-disable-next-line unicorn/prefer-spread
const resultBySplit = value.split('');
const resultBySpread = [...value];
return {
node: node.callee.property,
messageId: ERROR_ARRAY_TO_SPLICED,
fix: methodCallToSpread(node, sourceCode),
};
});
hasSameResult = resultBySplit.length === resultBySpread.length
&& resultBySplit.every((character, index) => character === resultBySpread[index]);
}
// `string.split()`
context.on('CallExpression', node => {
if (!isMethodCall(node, {
method: 'split',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const problem = {
node: node.callee.property,
messageId: ERROR_STRING_SPLIT,
};
const [separator] = node.arguments;
if (!isLiteral(separator, '')) {
return;
}
if (hasSameResult) {
problem.fix = methodCallToSpread(node, sourceCode);
} else {
problem.suggest = [
{
messageId: SUGGESTION_USE_SPREAD,
fix: methodCallToSpread(node, sourceCode),
},
];
const string = node.callee.object;
const staticValue = getStaticValue(string, sourceCode.getScope(string));
let hasSameResult = false;
if (staticValue) {
const {value} = staticValue;
if (typeof value !== 'string') {
return;
}
return problem;
},
};
// eslint-disable-next-line unicorn/prefer-spread
const resultBySplit = value.split('');
const resultBySpread = [...value];
hasSameResult = resultBySplit.length === resultBySpread.length
&& resultBySplit.every((character, index) => character === resultBySpread[index]);
}
const problem = {
node: node.callee.property,
messageId: ERROR_STRING_SPLIT,
};
if (hasSameResult) {
problem.fix = methodCallToSpread(node, sourceCode);
} else {
problem.suggest = [
{
messageId: SUGGESTION_USE_SPREAD,
fix: methodCallToSpread(node, sourceCode),
},
];
}
return problem;
});
};

@@ -506,3 +531,3 @@

docs: {
description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#slice()` and `String#split(\'\')`.',
description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split(\'\')`.',
},

@@ -509,0 +534,0 @@ fixable: 'code',

@@ -5,4 +5,3 @@ 'use strict';

const escapeString = require('./utils/escape-string.js');
const {methodCallSelector} = require('./selectors/index.js');
const {isRegexLiteral, isNewExpression} = require('./ast/index.js');
const {isRegexLiteral, isNewExpression, isMethodCall} = require('./ast/index.js');

@@ -16,7 +15,2 @@ const MESSAGE_ID_USE_REPLACE_ALL = 'method';

const selector = methodCallSelector({
methods: ['replace', 'replaceAll'],
argumentsLength: 2,
});
function getPatternReplacement(node) {

@@ -85,3 +79,12 @@ if (!isRegexLiteral(node)) {

const create = context => ({
[selector](node) {
CallExpression(node) {
if (!isMethodCall(node, {
methods: ['replace', 'replaceAll'],
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const {

@@ -92,3 +95,3 @@ arguments: [pattern],

if (!isRegExpWithGlobalFlag(pattern, context.getScope())) {
if (!isRegExpWithGlobalFlag(pattern, context.sourceCode.getScope(pattern))) {
return;

@@ -95,0 +98,0 @@ }

'use strict';
const {getStaticValue} = require('@eslint-community/eslint-utils');
const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
const {methodCallSelector} = require('./selectors/index.js');
const isNumber = require('./utils/is-number.js');
const {replaceArgument} = require('./fix/index.js');
const {isNumberLiteral} = require('./ast/index.js');
const {isNumberLiteral, isMethodCall} = require('./ast/index.js');

@@ -16,8 +15,2 @@ const MESSAGE_ID_SUBSTR = 'substr';

const selector = methodCallSelector({
methods: ['substr', 'substring'],
includeOptionalMember: true,
includeOptionalCall: true,
});
const getNumericValue = node => {

@@ -49,4 +42,4 @@ if (isNumberLiteral(node)) {

const scope = context.getScope();
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const scope = sourceCode.getScope(node);
const firstArgumentStaticResult = getStaticValue(firstArgument, scope);

@@ -76,3 +69,3 @@ const secondArgumentRange = getParenthesizedRange(secondArgument, sourceCode);

if (argumentNodes.every(node => isNumber(node, context.getScope()))) {
if (argumentNodes.every(node => isNumber(node, scope))) {
const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);

@@ -88,3 +81,3 @@

function * fixSubstringArguments({node, fixer, context, abort}) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const [firstArgument, secondArgument] = node.arguments;

@@ -151,3 +144,7 @@

const create = context => ({
[selector](node) {
CallExpression(node) {
if (!isMethodCall(node, {methods: ['substr', 'substring']})) {
return;
}
const method = node.callee.property.name;

@@ -154,0 +151,0 @@

'use strict';
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
const {methodCallSelector} = require('./selectors/index.js');
const escapeString = require('./utils/escape-string.js');

@@ -8,2 +7,3 @@ const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');

const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
const {isMethodCall, isRegexLiteral} = require('./ast/index.js');

@@ -30,7 +30,2 @@ const MESSAGE_STARTS_WITH = 'prefer-starts-with';

const regexTestSelector = [
methodCallSelector({method: 'test', argumentsLength: 1}),
'[callee.object.regex]',
].join('');
const checkRegex = ({pattern, flags}) => {

@@ -66,6 +61,18 @@ if (flags.includes('i') || flags.includes('m')) {

const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[regexTestSelector](node) {
CallExpression(node) {
if (
!isMethodCall(node, {
method: 'test',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
|| !isRegexLiteral(node.callee.object)
) {
return;
}
const regexNode = node.callee.object;

@@ -89,3 +96,3 @@ const {regex} = regexNode;

if (!isString) {
const staticValue = getStaticValue(target, context.getScope());
const staticValue = getStaticValue(target, sourceCode.getScope(target));

@@ -92,0 +99,0 @@ if (staticValue) {

'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -9,15 +9,14 @@ const MESSAGE_ID = 'prefer-string-trim-start-end';

const selector = [
methodCallSelector({
methods: ['trimLeft', 'trimRight'],
argumentsLength: 0,
includeOptionalMember: true,
}),
' > .callee',
' > .property',
].join(' ');
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[selector](node) {
CallExpression(callExpression) {
if (!isMethodCall(callExpression, {
methods: ['trimLeft', 'trimRight'],
argumentsLength: 0,
optionalCall: false,
})) {
return;
}
const node = callExpression.callee.property;
const method = node.name;

@@ -24,0 +23,0 @@ const replacement = method === 'trimLeft' ? 'trimStart' : 'trimEnd';

@@ -259,3 +259,3 @@ 'use strict';

};
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const ifStatements = new Set();

@@ -266,7 +266,9 @@ const breakStatements = [];

return {
'IfStatement'(node) {
IfStatement(node) {
ifStatements.add(node);
},
'BreakStatement:not([label])'(node) {
breakStatements.push(node);
BreakStatement(node) {
if (!node.label) {
breakStatements.push(node);
}
},

@@ -273,0 +275,0 @@ * 'Program:exit'() {

@@ -14,10 +14,2 @@ 'use strict';

const selector = [
'IfStatement',
':not(IfStatement > .alternate)',
'[test.type!="ConditionalExpression"]',
'[consequent]',
'[alternate]',
].join('');
const isTernary = node => node?.type === 'ConditionalExpression';

@@ -50,3 +42,3 @@

const onlySingleLine = context.options[0] === 'only-single-line';
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const scopeToNamesGeneratedByFixer = new WeakMap();

@@ -179,3 +171,12 @@ const isSafeName = (name, scopes) => scopes.every(scope => {

return {
[selector](node) {
IfStatement(node) {
if (
(node.parent.type === 'IfStatement' && node.parent.alternate === node)
|| node.test.type === 'ConditionalExpression'
|| !node.consequent
|| !node.alternate
) {
return;
}
const consequent = getNodeBody(node.consequent);

@@ -207,3 +208,3 @@ const alternate = getNodeBody(node.alternate);

const scope = context.getScope();
const scope = sourceCode.getScope(node);
problem.fix = function * (fixer) {

@@ -210,0 +211,0 @@ const testText = getText(node.test);

'use strict';
const {findVariable, getFunctionHeadLocation} = require('@eslint-community/eslint-utils');
const {matches, not, memberExpressionSelector} = require('./selectors/index.js');
const {isFunction, isMemberExpression, isMethodCall} = require('./ast/index.js');

@@ -16,30 +16,21 @@ const ERROR_PROMISE = 'promise';

const promiseMethods = ['then', 'catch', 'finally'];
const promisePrototypeMethods = ['then', 'catch', 'finally'];
const isTopLevelCallExpression = node => {
if (node.type !== 'CallExpression') {
return false;
}
const topLevelCallExpression = [
'CallExpression',
not([':function *', 'ClassDeclaration *', 'ClassExpression *']),
].join('');
const iife = [
topLevelCallExpression,
matches([
'[callee.type="FunctionExpression"]',
'[callee.type="ArrowFunctionExpression"]',
]),
'[callee.async!=false]',
'[callee.generator!=true]',
].join('');
const promise = [
topLevelCallExpression,
memberExpressionSelector({
path: 'callee',
properties: promiseMethods,
includeOptional: true,
}),
].join('');
const identifier = [
topLevelCallExpression,
'[callee.type="Identifier"]',
].join('');
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
if (
isFunction(ancestor)
|| ancestor.type === 'ClassDeclaration'
|| ancestor.type === 'ClassExpression'
) {
return false;
}
}
return true;
};
const isPromiseMethodCalleeObject = node =>

@@ -50,6 +41,6 @@ node.parent.type === 'MemberExpression'

&& node.parent.property.type === 'Identifier'
&& promiseMethods.includes(node.parent.property.name)
&& promisePrototypeMethods.includes(node.parent.property.name)
&& node.parent.parent.type === 'CallExpression'
&& node.parent.parent.callee === node.parent;
const isAwaitArgument = node => {
const isAwaitExpressionArgument = node => {
if (node.parent.type === 'ChainExpression') {

@@ -62,32 +53,62 @@ node = node.parent;

// `Promise.{all,allSettled,any,race}([foo()])`
const isInPromiseMethods = node =>
node.parent.type === 'ArrayExpression'
&& node.parent.elements.includes(node)
&& isMethodCall(node.parent.parent, {
object: 'Promise',
methods: ['all', 'allSettled', 'any', 'race'],
argumentsLength: 1,
})
&& node.parent.parent.arguments[0] === node.parent;
/** @param {import('eslint').Rule.RuleContext} context */
function create(context) {
if (context.filename.toLowerCase().endsWith('.cjs')) {
return;
}
return {
[promise](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
CallExpression(node) {
if (
!isTopLevelCallExpression(node)
|| isPromiseMethodCalleeObject(node)
|| isAwaitExpressionArgument(node)
|| isInPromiseMethods(node)
) {
return;
}
return {
node: node.callee.property,
messageId: ERROR_PROMISE,
};
},
[iife](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
// Promises
if (isMemberExpression(node.callee, {
properties: promisePrototypeMethods,
computed: false,
})) {
return {
node: node.callee.property,
messageId: ERROR_PROMISE,
};
}
return {
node,
loc: getFunctionHeadLocation(node.callee, context.getSourceCode()),
messageId: ERROR_IIFE,
};
},
[identifier](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
const {sourceCode} = context;
// IIFE
if (
(node.callee.type === 'FunctionExpression' || node.callee.type === 'ArrowFunctionExpression')
&& node.callee.async
&& !node.callee.generator
) {
return {
node,
loc: getFunctionHeadLocation(node.callee, sourceCode),
messageId: ERROR_IIFE,
};
}
// Identifier
if (node.callee.type !== 'Identifier') {
return;
}
const variable = findVariable(context.getScope(), node.callee);
const variable = findVariable(sourceCode.getScope(node), node.callee);
if (!variable || variable.defs.length !== 1) {

@@ -103,9 +124,3 @@ return;

!value
|| !(
(
value.type === 'ArrowFunctionExpression'
|| value.type === 'FunctionExpression'
|| value.type === 'FunctionDeclaration'
) && !value.generator && value.async
)
|| !(isFunction(value) && !value.generator && value.async)
) {

@@ -112,0 +127,0 @@ return;

'use strict';
const {newExpressionSelector} = require('./selectors/index.js');
const {isNewExpression} = require('./ast/index.js');

@@ -54,7 +54,2 @@ const MESSAGE_ID = 'prefer-type-error';

const selector = [
'ThrowStatement',
newExpressionSelector({name: 'Error', path: 'argument'}),
].join('');
const isTypecheckingIdentifier = (node, callExpression, isMemberExpression) =>

@@ -129,5 +124,6 @@ callExpression !== undefined

const create = () => ({
[selector](node) {
ThrowStatement(node) {
if (
isLone(node)
isNewExpression(node.argument, {name: 'Error'})
&& isLone(node)
&& node.parent.parent

@@ -134,0 +130,0 @@ && isTypechecking(node.parent.parent)

'use strict';
const path = require('node:path');
const {defaultsDeep, upperFirst, lowerFirst} = require('lodash');
const avoidCapture = require('./utils/avoid-capture.js');

@@ -342,3 +341,3 @@ const cartesianProductSamples = require('./utils/cartesian-product-samples.js');

const options = prepareOptions(context.options[0]);
const filenameWithExtension = context.getPhysicalFilename();
const filenameWithExtension = context.physicalFilename;

@@ -544,3 +543,3 @@ // A `class` declaration produces two variables in two scopes:

'Program:exit'() {
'Program:exit'(program) {
if (!options.checkVariables) {

@@ -550,3 +549,3 @@ return;

checkScope(context.getScope());
checkScope(context.sourceCode.getScope(program));
},

@@ -553,0 +552,0 @@ };

'use strict';
const {getStaticValue} = require('@eslint-community/eslint-utils');
const {newExpressionSelector} = require('./selectors/index.js');
const {isNewExpression, isStringLiteral} = require('./ast/index.js');
const {replaceStringLiteral} = require('./fix/index.js');

@@ -15,11 +15,2 @@

const templateLiteralSelector = [
newExpressionSelector({name: 'URL', argumentsLength: 2}),
' > TemplateLiteral.arguments:first-child',
].join('');
const literalSelector = [
newExpressionSelector({name: 'URL', argumentsLength: 2}),
' > Literal.arguments:first-child',
].join('');
const DOT_SLASH = './';

@@ -41,3 +32,3 @@ const TEST_URL_BASES = [

function canAddDotSlash(node, context) {
function canAddDotSlash(node, sourceCode) {
const url = node.value;

@@ -49,3 +40,3 @@ if (url.startsWith(DOT_SLASH) || url.startsWith('.') || url.startsWith('/')) {

const baseNode = node.parent.arguments[1];
const staticValueResult = getStaticValue(baseNode, context.getScope());
const staticValueResult = getStaticValue(baseNode, sourceCode.getScope(node));

@@ -62,3 +53,3 @@ if (

function canRemoveDotSlash(node, context) {
function canRemoveDotSlash(node, sourceCode) {
const rawValue = node.raw.slice(1, -1);

@@ -70,3 +61,3 @@ if (!rawValue.startsWith(DOT_SLASH)) {

const baseNode = node.parent.arguments[1];
const staticValueResult = getStaticValue(baseNode, context.getScope());
const staticValueResult = getStaticValue(baseNode, sourceCode.getScope(node));

@@ -83,4 +74,4 @@ if (

function addDotSlash(node, context) {
if (!canAddDotSlash(node, context)) {
function addDotSlash(node, sourceCode) {
if (!canAddDotSlash(node, sourceCode)) {
return;

@@ -92,4 +83,4 @@ }

function removeDotSlash(node, context) {
if (!canRemoveDotSlash(node, context)) {
function removeDotSlash(node, sourceCode) {
if (!canRemoveDotSlash(node, sourceCode)) {
return;

@@ -109,3 +100,10 @@ }

if (style === 'never') {
listeners[templateLiteralSelector] = function (node) {
listeners.TemplateLiteral = function (node) {
if (!(
isNewExpression(node.parent, {name: 'URL', argumentsLength: 2})
&& node.parent.arguments[0] === node
)) {
return;
}
const firstPart = node.quasis[0];

@@ -132,8 +130,13 @@ if (!firstPart.value.raw.startsWith(DOT_SLASH)) {

listeners[literalSelector] = function (node) {
if (typeof node.value !== 'string') {
listeners.Literal = function (node) {
if (!(
isStringLiteral(node)
&& isNewExpression(node.parent, {name: 'URL', argumentsLength: 2})
&& node.parent.arguments[0] === node
)) {
return;
}
const fix = (style === 'never' ? removeDotSlash : addDotSlash)(node, context);
const {sourceCode} = context;
const fix = (style === 'never' ? removeDotSlash : addDotSlash)(node, sourceCode);

@@ -140,0 +143,0 @@ if (!fix) {

'use strict';
const {matches, methodCallSelector, arrayPrototypeMethodSelector} = require('./selectors/index.js');
const {appendArgument} = require('./fix/index.js');
const {isMethodCall} = require('./ast/index.js');
const {isArrayPrototypeProperty} = require('./utils/index.js');

@@ -10,36 +11,43 @@ const MESSAGE_ID = 'require-array-join-separator';

const selector = matches([
// `foo.join()`
methodCallSelector({
method: 'join',
argumentsLength: 0,
includeOptionalMember: true,
}),
// `[].join.call(foo)` and `Array.prototype.join.call(foo)`
[
methodCallSelector({method: 'call', argumentsLength: 1}),
arrayPrototypeMethodSelector({path: 'callee.object', method: 'join'}),
].join(''),
]);
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
return {
[selector](node) {
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
const isPrototypeMethod = node.arguments.length === 1;
return {
loc: {
start: penultimateToken.loc[isPrototypeMethod ? 'end' : 'start'],
end: lastToken.loc.end,
},
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => appendArgument(fixer, node, '\',\'', sourceCode),
};
},
};
};
const create = context => ({
CallExpression(node) {
if (!(
// `foo.join()`
isMethodCall(node, {
method: 'join',
argumentsLength: 0,
optionalCall: false,
})
// `[].join.call(foo)` and `Array.prototype.join.call(foo)`
|| (
isMethodCall(node, {
method: 'call',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& isArrayPrototypeProperty(node.callee.object, {
property: 'join',
})
)
)) {
return;
}
const {sourceCode} = context;
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
const isPrototypeMethod = node.arguments.length === 1;
return {
loc: {
start: penultimateToken.loc[isPrototypeMethod ? 'end' : 'start'],
end: lastToken.loc.end,
},
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => appendArgument(fixer, node, '\',\'', sourceCode),
};
},
});
/** @type {import('eslint').Rule.RuleModule} */

@@ -46,0 +54,0 @@ module.exports = {

'use strict';
const {methodCallSelector, not} = require('./selectors/index.js');
const {appendArgument} = require('./fix/index.js');
const {isMethodCall} = require('./ast/index.js');

@@ -10,33 +10,35 @@ const MESSAGE_ID = 'require-number-to-fixed-digits-argument';

const mathToFixed = [
methodCallSelector({
method: 'toFixed',
argumentsLength: 0,
}),
not('[callee.object.type="NewExpression"]'),
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
return {
[mathToFixed](node) {
const [
openingParenthesis,
closingParenthesis,
] = sourceCode.getLastTokens(node, 2);
const create = context => ({
CallExpression(node) {
if (
!isMethodCall(node, {
method: 'toFixed',
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
|| node.callee.object.type === 'NewExpression'
) {
return;
}
return {
loc: {
start: openingParenthesis.loc.start,
end: closingParenthesis.loc.end,
},
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => appendArgument(fixer, node, '0', sourceCode),
};
},
};
};
const {sourceCode} = context;
const [
openingParenthesis,
closingParenthesis,
] = sourceCode.getLastTokens(node, 2);
return {
loc: {
start: openingParenthesis.loc.start,
end: closingParenthesis.loc.end,
},
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => appendArgument(fixer, node, '0', sourceCode),
};
},
});
/** @type {import('eslint').Rule.RuleModule} */

@@ -43,0 +45,0 @@ module.exports = {

'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const {appendArgument} = require('./fix/index.js');

@@ -14,5 +14,14 @@

function create(context) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[methodCallSelector({method: 'postMessage', argumentsLength: 1})](node) {
CallExpression(node) {
if (!isMethodCall(node, {
method: 'postMessage',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);

@@ -19,0 +28,0 @@ const replacements = [];

@@ -87,2 +87,5 @@ /* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */

},
elems: {
elements: true,
},
env: {

@@ -89,0 +92,0 @@ environment: true,

'use strict';
const {hasSideEffect, isParenthesized, findVariable} = require('@eslint-community/eslint-utils');
const {matches, methodCallSelector} = require('../selectors/index.js');
const isFunctionSelfUsedInside = require('../utils/is-function-self-used-inside.js');
const {isMethodCall} = require('../ast/index.js');
const {isSameIdentifier, isFunctionSelfUsedInside} = require('../utils/index.js');
const getBinaryExpressionSelector = path => [
`[${path}.type="BinaryExpression"]`,
`[${path}.operator="==="]`,
`:matches([${path}.left.type="Identifier"], [${path}.right.type="Identifier"])`,
].join('');
const getFunctionSelector = path => [
`[${path}.generator!=true]`,
`[${path}.async!=true]`,
`[${path}.params.length=1]`,
`[${path}.params.0.type="Identifier"]`,
].join('');
const callbackFunctionSelector = path => matches([
const isSimpleCompare = (node, compareNode) =>
node.type === 'BinaryExpression'
&& node.operator === '==='
&& (
isSameIdentifier(node.left, compareNode)
|| isSameIdentifier(node.right, compareNode)
);
const isSimpleCompareCallbackFunction = node =>
// Matches `foo.findIndex(bar => bar === baz)`
[
`[${path}.type="ArrowFunctionExpression"]`,
getFunctionSelector(path),
getBinaryExpressionSelector(`${path}.body`),
].join(''),
(
node.type === 'ArrowFunctionExpression'
&& !node.async
&& node.params.length === 1
&& isSimpleCompare(node.body, node.params[0])
)
// Matches `foo.findIndex(bar => {return bar === baz})`
// Matches `foo.findIndex(function (bar) {return bar === baz})`
[
`:matches([${path}.type="ArrowFunctionExpression"], [${path}.type="FunctionExpression"])`,
getFunctionSelector(path),
`[${path}.body.type="BlockStatement"]`,
`[${path}.body.body.length=1]`,
`[${path}.body.body.0.type="ReturnStatement"]`,
getBinaryExpressionSelector(`${path}.body.body.0.argument`),
].join(''),
]);
|| (
(node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression')
&& !node.async
&& !node.generator
&& node.params.length === 1
&& node.body.type === 'BlockStatement'
&& node.body.body.length === 1
&& node.body.body[0].type === 'ReturnStatement'
&& isSimpleCompare(node.body.body[0].argument, node.params[0])
);
const isIdentifierNamed = ({type, name}, expectName) => type === 'Identifier' && name === expectName;

@@ -54,76 +52,78 @@

const selector = [
methodCallSelector({
method,
argumentsLength: 1,
}),
callbackFunctionSelector('arguments.0'),
].join('');
function createListeners(context) {
const sourceCode = context.getSourceCode();
function listen(context) {
const {sourceCode} = context;
const {scopeManager} = sourceCode;
return {
[selector](node) {
const [callback] = node.arguments;
const binaryExpression = callback.body.type === 'BinaryExpression'
? callback.body
: callback.body.body[0].argument;
const [parameter] = callback.params;
const {left, right} = binaryExpression;
const {name} = parameter;
context.on('CallExpression', callExpression => {
if (
!isMethodCall(callExpression, {
method,
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
|| !isSimpleCompareCallbackFunction(callExpression.arguments[0])
) {
return;
}
let searchValueNode;
let parameterInBinaryExpression;
if (isIdentifierNamed(left, name)) {
searchValueNode = right;
parameterInBinaryExpression = left;
} else if (isIdentifierNamed(right, name)) {
searchValueNode = left;
parameterInBinaryExpression = right;
} else {
return;
}
const [callback] = callExpression.arguments;
const binaryExpression = callback.body.type === 'BinaryExpression'
? callback.body
: callback.body.body[0].argument;
const [parameter] = callback.params;
const {left, right} = binaryExpression;
const {name} = parameter;
const callbackScope = scopeManager.acquire(callback);
if (
// `parameter` is used somewhere else
findVariable(callbackScope, parameter).references.some(({identifier}) => identifier !== parameterInBinaryExpression)
|| isFunctionSelfUsedInside(callback, callbackScope)
) {
return;
}
let searchValueNode;
let parameterInBinaryExpression;
if (isIdentifierNamed(left, name)) {
searchValueNode = right;
parameterInBinaryExpression = left;
} else if (isIdentifierNamed(right, name)) {
searchValueNode = left;
parameterInBinaryExpression = right;
} else {
return;
}
const method = node.callee.property;
const problem = {
node: method,
messageId: ERROR,
suggest: [],
};
const callbackScope = scopeManager.acquire(callback);
if (
// `parameter` is used somewhere else
findVariable(callbackScope, parameter).references.some(({identifier}) => identifier !== parameterInBinaryExpression)
|| isFunctionSelfUsedInside(callback, callbackScope)
) {
return;
}
const fix = function * (fixer) {
let text = sourceCode.getText(searchValueNode);
if (isParenthesized(searchValueNode, sourceCode) && !isParenthesized(callback, sourceCode)) {
text = `(${text})`;
}
const methodNode = callExpression.callee.property;
const problem = {
node: methodNode,
messageId: ERROR,
suggest: [],
};
yield fixer.replaceText(method, replacement);
yield fixer.replaceText(callback, text);
};
if (hasSideEffect(searchValueNode, sourceCode)) {
problem.suggest.push({messageId: SUGGESTION, fix});
} else {
problem.fix = fix;
const fix = function * (fixer) {
let text = sourceCode.getText(searchValueNode);
if (isParenthesized(searchValueNode, sourceCode) && !isParenthesized(callback, sourceCode)) {
text = `(${text})`;
}
return problem;
},
};
yield fixer.replaceText(methodNode, replacement);
yield fixer.replaceText(callback, text);
};
if (hasSideEffect(searchValueNode, sourceCode)) {
problem.suggest.push({messageId: SUGGESTION, fix});
} else {
problem.fix = fix;
}
return problem;
});
}
return {messages, createListeners};
return {messages, listen};
}
module.exports = simpleArraySearchRule;

@@ -76,59 +76,57 @@ 'use strict';

return {
'Literal, TemplateElement'(node) {
const {type, value, raw} = node;
context.on(['Literal', 'TemplateElement'], node => {
const {type, value, raw} = node;
let string;
if (type === 'Literal') {
string = value;
} else if (!isIgnoredTag(node)) {
string = value.raw;
}
let string;
if (type === 'Literal') {
string = value;
} else if (!isIgnoredTag(node)) {
string = value.raw;
}
if (!string || typeof string !== 'string') {
return;
}
if (!string || typeof string !== 'string') {
return;
}
const replacement = replacements.find(({regex}) => regex.test(string));
const replacement = replacements.find(({regex}) => regex.test(string));
if (!replacement) {
return;
}
if (!replacement) {
return;
}
const {fix: autoFix, message = defaultMessage, match, suggest, regex} = replacement;
const problem = {
const {fix: autoFix, message = defaultMessage, match, suggest, regex} = replacement;
const problem = {
node,
message,
data: {
match,
suggest,
},
};
const fixed = string.replace(regex, suggest);
const fix = type === 'Literal'
? fixer => fixer.replaceText(
node,
message,
data: {
match,
suggest,
escapeString(fixed, raw[0]),
)
: fixer => replaceTemplateElement(
fixer,
node,
escapeTemplateElementRaw(fixed),
);
if (autoFix) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: SUGGESTION_MESSAGE_ID,
fix,
},
};
];
}
const fixed = string.replace(regex, suggest);
const fix = type === 'Literal'
? fixer => fixer.replaceText(
node,
escapeString(fixed, raw[0]),
)
: fixer => replaceTemplateElement(
fixer,
node,
escapeTemplateElementRaw(fixed),
);
if (autoFix) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: SUGGESTION_MESSAGE_ID,
fix,
},
];
}
return problem;
},
};
return problem;
});
};

@@ -135,0 +133,0 @@

@@ -40,3 +40,3 @@ 'use strict';

const isBracesRequired = context.options[0] !== 'avoid';
const sourceCode = context.getSourceCode();
const {sourceCode} = context;

@@ -43,0 +43,0 @@ return {

@@ -6,3 +6,3 @@ 'use strict';

const {replaceTemplateElement} = require('./fix/index.js');
const {callExpressionSelector, methodCallSelector} = require('./selectors/index.js');
const {isMethodCall, isCallExpression} = require('./ast/index.js');

@@ -14,15 +14,33 @@ const MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE = 'template-indent';

const jestInlineSnapshotSelector = [
callExpressionSelector({name: 'expect', path: 'callee.object', argumentsLength: 1}),
methodCallSelector({method: 'toMatchInlineSnapshot', argumentsLength: 1}),
' > TemplateLiteral.arguments:first-child',
].join('');
const isJestInlineSnapshot = node =>
isMethodCall(node.parent, {
method: 'toMatchInlineSnapshot',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
&& node.parent.arguments[0] === node
&& isCallExpression(node.parent.callee.object, {
name: 'expect',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
});
const parsedEsquerySelectors = new Map();
const parseEsquerySelector = selector => {
if (!parsedEsquerySelectors.has(selector)) {
parsedEsquerySelectors.set(selector, esquery.parse(selector));
}
return parsedEsquerySelectors.get(selector);
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const options = {
tags: ['outdent', 'dedent', 'gql', 'sql', 'html', 'styled'],
functions: ['dedent', 'stripIndent'],
selectors: [jestInlineSnapshotSelector],
selectors: [],
comments: ['HTML', 'indent'],

@@ -34,10 +52,4 @@ ...context.options[0],

const selectors = [
...options.tags.map(tagName => `TaggedTemplateExpression[tag.name="${tagName}"] > .quasi`),
...options.functions.map(functionName => `CallExpression[callee.name="${functionName}"] > .arguments`),
...options.selectors,
];
/** @param {import('@babel/core').types.TemplateLiteral} node */
const indentTemplateLiteralNode = node => {
const getProblem = node => {
const delimiter = '__PLACEHOLDER__' + Math.random();

@@ -85,3 +97,3 @@ const joined = node.quasis

context.report({
return {
node,

@@ -92,22 +104,55 @@ messageId: MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE,

.map((replacement, index) => replaceTemplateElement(fixer, node.quasis[index], replacement)),
});
};
};
const shouldIndent = node => {
if (options.comments.length > 0) {
const previousToken = sourceCode.getTokenBefore(node, {includeComments: true});
if (previousToken?.type === 'Block' && options.comments.includes(previousToken.value.trim().toLowerCase())) {
return true;
}
}
if (isJestInlineSnapshot(node)) {
return true;
}
if (
options.tags.length > 0
&& node.parent.type === 'TaggedTemplateExpression'
&& node.parent.quasi === node
&& node.parent.tag.type === 'Identifier'
&& options.tags.includes(node.parent.tag.name)
) {
return true;
}
if (
options.functions.length > 0
&& node.parent.type === 'CallExpression'
&& node.parent.arguments.includes(node)
&& node.parent.callee.type === 'Identifier'
&& options.functions.includes(node.parent.callee.name)
) {
return true;
}
if (options.selectors.length > 0) {
const ancestors = sourceCode.getAncestors(node).reverse();
if (options.selectors.some(selector => esquery.matches(node, parseEsquerySelector(selector), ancestors))) {
return true;
}
}
return false;
};
return {
/** @param {import('@babel/core').types.TemplateLiteral} node */
TemplateLiteral(node) {
if (options.comments.length > 0) {
const previousToken = sourceCode.getTokenBefore(node, {includeComments: true});
if (previousToken?.type === 'Block' && options.comments.includes(previousToken.value.trim().toLowerCase())) {
indentTemplateLiteralNode(node);
return;
}
if (!shouldIndent(node)) {
return;
}
const ancestry = context.getAncestors().reverse();
const shouldIndent = selectors.some(selector => esquery.matches(node, esquery.parse(selector), ancestry));
if (shouldIndent) {
indentTemplateLiteralNode(node);
}
return getProblem(node);
},

@@ -114,0 +159,0 @@ };

'use strict';
const {matches} = require('./selectors/index.js');
const {switchCallExpressionToNewExpression} = require('./fix/index.js');

@@ -12,29 +11,31 @@

const selector = [
'ThrowStatement',
' > ',
'CallExpression.argument',
matches([
// `throw FooError()`
[
'[callee.type="Identifier"]',
`[callee.name=/${customError.source}/]`,
].join(''),
// `throw lib.FooError()`
[
'[callee.type="MemberExpression"]',
'[callee.computed!=true]',
'[callee.property.type="Identifier"]',
`[callee.property.name=/${customError.source}/]`,
].join(''),
]),
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector]: node => ({
node,
messageId,
fix: fixer => switchCallExpressionToNewExpression(node, context.getSourceCode(), fixer),
}),
CallExpression(node) {
if (!(
node.parent.type === 'ThrowStatement'
&& node.parent.argument === node
)) {
return;
}
const {callee} = node;
if (!(
(callee.type === 'Identifier' && customError.test(callee.name))
|| (
callee.type === 'MemberExpression'
&& !callee.computed
&& callee.property.type === 'Identifier'
&& customError.test(callee.property.name)
)
)) {
return;
}
return {
node,
messageId,
fix: fixer => switchCallExpressionToNewExpression(node, context.sourceCode, fixer),
};
},
});

@@ -41,0 +42,0 @@

@@ -59,3 +59,3 @@ 'use strict';

return {
'Program:exit': () => this.track(context.getScope()),
'Program:exit': program => this.track(context.sourceCode.getScope(program)),
};

@@ -62,0 +62,0 @@ }

'use strict';
// Keep logic sync with `../selector/not-left-hand-side.js`
const isLeftHandSide = node =>
(node.parent.type === 'AssignmentExpression' && node.parent.left === node)
(
(node.parent.type === 'AssignmentExpression' || node.parent.type === 'AssignmentPattern')
&& node.parent.left === node
)
|| (node.parent.type === 'UpdateExpression' && node.parent.argument === node)
|| (node.parent.type === 'ArrayPattern' && node.parent.elements.includes(node))
|| (
node.parent.type === 'Property'
&& node.parent.value === node
&& node.parent.parent.type === 'ObjectPattern'
&& node.parent.parent.properties.includes(node.parent)
)
|| (
node.parent.type === 'UnaryExpression'

@@ -9,0 +18,0 @@ && node.parent.operator === 'delete'

'use strict';
module.exports = ({parent}) => !parent || parent.type === 'ExpressionStatement';
const {isExpressionStatement} = require('../ast/index.js');
module.exports = node => isExpressionStatement(node.parent);

@@ -36,36 +36,31 @@ 'use strict';

function reportListenerProblems(listener, context) {
// Listener arguments can be `codePath, node` or `node`
return function (...listenerArguments) {
let problems = listener(...listenerArguments);
function reportListenerProblems(problems, context) {
if (!problems) {
return;
}
if (!problems) {
return;
}
if (!isIterable(problems)) {
problems = [problems];
}
if (!isIterable(problems)) {
problems = [problems];
for (const problem of problems) {
if (problem.fix) {
problem.fix = wrapFixFunction(problem.fix);
}
for (const problem of problems) {
if (problem.fix) {
problem.fix = wrapFixFunction(problem.fix);
}
if (Array.isArray(problem.suggest)) {
for (const suggest of problem.suggest) {
if (suggest.fix) {
suggest.fix = wrapFixFunction(suggest.fix);
}
if (Array.isArray(problem.suggest)) {
for (const suggest of problem.suggest) {
if (suggest.fix) {
suggest.fix = wrapFixFunction(suggest.fix);
}
suggest.data = {
...problem.data,
...suggest.data,
};
}
suggest.data = {
...problem.data,
...suggest.data,
};
}
}
context.report(problem);
}
};
context.report(problem);
}
}

@@ -81,6 +76,34 @@

const wrapped = context => {
const listeners = create(context);
const listeners = {};
const addListener = (selector, listener) => {
listeners[selector] ??= [];
listeners[selector].push(listener);
};
if (!listeners) {
return {};
const contextProxy = new Proxy(context, {
get(target, property, receiver) {
if (property === 'on') {
return (selectorOrSelectors, listener) => {
const selectors = Array.isArray(selectorOrSelectors) ? selectorOrSelectors : [selectorOrSelectors];
for (const selector of selectors) {
addListener(selector, listener);
}
};
}
if (property === 'onExit') {
return (selectorOrSelectors, listener) => {
const selectors = Array.isArray(selectorOrSelectors) ? selectorOrSelectors : [selectorOrSelectors];
for (const selector of selectors) {
addListener(`${selector}:exit`, listener);
}
};
}
return Reflect.get(target, property, receiver);
},
});
for (const [selector, listener] of Object.entries(create(contextProxy) ?? {})) {
addListener(selector, listener);
}

@@ -90,3 +113,11 @@

Object.entries(listeners)
.map(([selector, listener]) => [selector, reportListenerProblems(listener, context)]),
.map(([selector, listeners]) => [
selector,
// Listener arguments can be `codePath, node` or `node`
(...listenerArguments) => {
for (const listener of listeners) {
reportListenerProblems(listener(...listenerArguments), context);
}
},
]),
);

@@ -112,8 +143,9 @@ };

const listeners = create(context);
const {parserServices} = context.sourceCode;
// `vue-eslint-parser`
if (context.parserServices?.defineTemplateBodyVisitor) {
if (parserServices?.defineTemplateBodyVisitor) {
return visitScriptBlock
? context.parserServices.defineTemplateBodyVisitor(listeners, listeners)
: context.parserServices.defineTemplateBodyVisitor(listeners);
? parserServices.defineTemplateBodyVisitor(listeners, listeners)
: parserServices.defineTemplateBodyVisitor(listeners);
}

@@ -120,0 +152,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc