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

eslint-plugin-unicorn - npm Package Compare versions

Comparing version 34.0.1 to 48.0.0

configs/all.js

122

index.js
'use strict';
const createDeprecatedRules = require('./rules/utils/create-deprecated-rules.js');
const {loadRules} = require('./rules/utils/rule.js');
const recommendedConfig = require('./configs/recommended.js');
const allRulesEnabledConfig = require('./configs/all.js');
const {name, version} = require('./package.json');
const deprecatedRules = createDeprecatedRules({
// {ruleId: ReplacementRuleId | ReplacementRuleId[]}, if no replacement, use `{ruleId: []}`
'import-index': [],
'no-array-instanceof': 'unicorn/no-instanceof-array',
'no-fn-reference-in-iterator': 'unicorn/no-array-callback-reference',
'no-reduce': 'unicorn/no-array-reduce',
'no-unsafe-regex': [],
'prefer-dataset': 'unicorn/prefer-dom-node-dataset',

@@ -16,2 +21,3 @@ 'prefer-event-key': 'unicorn/prefer-keyboard-event-key',

'prefer-node-remove': 'unicorn/prefer-dom-node-remove',
'prefer-object-has-own': 'prefer-object-has-own',
'prefer-replace-all': 'unicorn/prefer-string-replace-all',

@@ -21,116 +27,18 @@ 'prefer-starts-ends-with': 'unicorn/prefer-string-starts-ends-with',

'prefer-trim-start-end': 'unicorn/prefer-string-trim-start-end',
'regex-shorthand': 'unicorn/better-regex'
'regex-shorthand': 'unicorn/better-regex',
});
module.exports = {
meta: {
name,
version,
},
rules: {
...loadRules(),
...deprecatedRules
...deprecatedRules,
},
configs: {
recommended: {
env: {
es6: true
},
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module'
},
plugins: [
'unicorn'
],
rules: {
'unicorn/better-regex': 'error',
'unicorn/catch-error-name': 'error',
'unicorn/consistent-destructuring': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/custom-error-definition': 'off',
'unicorn/empty-brace-spaces': 'error',
'unicorn/error-message': 'error',
'unicorn/escape-case': 'error',
'unicorn/expiring-todo-comments': 'error',
'unicorn/explicit-length-check': 'error',
'unicorn/filename-case': 'error',
'unicorn/import-index': 'off',
'unicorn/import-style': 'error',
'unicorn/new-for-builtins': 'error',
'unicorn/no-abusive-eslint-disable': 'error',
'unicorn/no-array-callback-reference': 'error',
'unicorn/no-array-for-each': 'error',
'unicorn/no-array-method-this-argument': 'error',
'unicorn/no-array-push-push': 'error',
'unicorn/no-array-reduce': 'error',
'unicorn/no-console-spaces': 'error',
'unicorn/no-document-cookie': 'error',
'unicorn/no-for-loop': 'error',
'unicorn/no-hex-escape': 'error',
'unicorn/no-instanceof-array': 'error',
'unicorn/no-keyword-prefix': 'off',
'unicorn/no-lonely-if': 'error',
'no-nested-ternary': 'off',
'unicorn/no-nested-ternary': 'error',
'unicorn/no-new-array': 'error',
'unicorn/no-new-buffer': 'error',
'unicorn/no-null': 'error',
'unicorn/no-object-as-default-parameter': 'error',
'unicorn/no-process-exit': 'error',
'unicorn/no-static-only-class': 'error',
'unicorn/no-this-assignment': 'error',
'unicorn/no-unreadable-array-destructuring': 'error',
'unicorn/no-unsafe-regex': 'off',
'unicorn/no-unused-properties': 'off',
'unicorn/no-useless-undefined': 'error',
'unicorn/no-zero-fractions': 'error',
'unicorn/number-literal-case': 'error',
'unicorn/numeric-separators-style': 'error',
'unicorn/prefer-add-event-listener': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-index-of': '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-date-now': 'error',
'unicorn/prefer-default-parameters': 'error',
'unicorn/prefer-dom-node-append': 'error',
'unicorn/prefer-dom-node-dataset': 'error',
'unicorn/prefer-dom-node-remove': 'error',
'unicorn/prefer-dom-node-text-content': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-keyboard-event-key': 'error',
'unicorn/prefer-math-trunc': 'error',
'unicorn/prefer-modern-dom-apis': 'error',
'unicorn/prefer-module': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-node-protocol': 'error',
'unicorn/prefer-number-properties': 'error',
// TODO: Enable this by default when targeting a Node.js version that supports `Object.hasOwn`.
'unicorn/prefer-object-has-own': 'off',
'unicorn/prefer-optional-catch-binding': 'error',
'unicorn/prefer-prototype-methods': 'error',
'unicorn/prefer-query-selector': 'error',
'unicorn/prefer-reflect-apply': 'error',
'unicorn/prefer-regexp-test': 'error',
'unicorn/prefer-set-has': 'error',
'unicorn/prefer-spread': 'error',
// TODO: Enable this by default when targeting Node.js 16.
'unicorn/prefer-string-replace-all': 'off',
'unicorn/prefer-string-slice': 'error',
'unicorn/prefer-string-starts-ends-with': 'error',
'unicorn/prefer-string-trim-start-end': 'error',
'unicorn/prefer-switch': 'error',
'unicorn/prefer-ternary': 'error',
// TODO: Enable this by default when targeting Node.js 14.
'unicorn/prefer-top-level-await': 'off',
'unicorn/prefer-type-error': 'error',
'unicorn/prevent-abbreviations': 'error',
'unicorn/require-array-join-separator': 'error',
'unicorn/require-number-to-fixed-digits-argument': 'error',
'unicorn/require-post-message-target-origin': 'error',
'unicorn/string-content': 'off',
'unicorn/throw-new-error': 'error'
}
}
}
recommended: recommendedConfig,
all: allRulesEnabledConfig,
},
};
{
"name": "eslint-plugin-unicorn",
"version": "34.0.1",
"description": "Various awesome ESLint rules",
"version": "48.0.0",
"description": "More than 100 powerful ESLint rules",
"license": "MIT",

@@ -14,16 +14,25 @@ "repository": "sindresorhus/eslint-plugin-unicorn",

"engines": {
"node": ">=12"
"node": ">=16"
},
"scripts": {
"test": "xo && nyc ava",
"create-rule": "node ./scripts/create-rule.mjs && npm run generate-rules-table && npm run generate-usage-example",
"create-rule": "node ./scripts/create-rule.mjs && npm run fix:eslint-docs",
"fix": "run-p --continue-on-error fix:*",
"fix:eslint-docs": "eslint-doc-generator",
"fix:js": "npm run lint:js -- --fix",
"fix:md": "npm run lint:md -- --fix",
"integration": "node ./test/integration/test.mjs",
"lint": "run-p --continue-on-error lint:*",
"lint:eslint-docs": "npm run fix:eslint-docs -- --check",
"lint:js": "xo",
"lint:md": "markdownlint \"**/*.md\"",
"lint:package-json": "npmPkgJsonLint .",
"run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs",
"integration": "node ./test/integration/test.mjs",
"smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.js",
"generate-rules-table": "node ./scripts/generate-rules-table.mjs",
"generate-usage-example": "node ./scripts/generate-usage-example.mjs"
"test": "npm-run-all --continue-on-error lint test:*",
"test:js": "c8 ava"
},
"files": [
"index.js",
"rules"
"rules",
"configs"
],

@@ -41,41 +50,50 @@ "keywords": [

"dependencies": {
"ci-info": "^3.2.0",
"@babel/helper-validator-identifier": "^7.22.5",
"@eslint-community/eslint-utils": "^4.4.0",
"ci-info": "^3.8.0",
"clean-regexp": "^1.0.0",
"eslint-template-visitor": "^2.3.2",
"eslint-utils": "^3.0.0",
"is-builtin-module": "^3.1.0",
"esquery": "^1.5.0",
"indent-string": "^4.0.0",
"is-builtin-module": "^3.2.1",
"jsesc": "^3.0.2",
"lodash": "^4.17.21",
"pluralize": "^8.0.0",
"read-pkg-up": "^7.0.1",
"regexp-tree": "^0.1.23",
"reserved-words": "^0.1.2",
"safe-regex": "^2.1.1",
"semver": "^7.3.5"
"regexp-tree": "^0.1.27",
"regjsparser": "^0.10.0",
"semver": "^7.5.4",
"strip-indent": "^3.0.0"
},
"devDependencies": {
"@babel/code-frame": "7.12.13",
"@babel/core": "7.14.6",
"@babel/eslint-parser": "7.14.7",
"@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": "^4.26.1",
"@typescript-eslint/parser": "^5.61.0",
"ava": "^3.15.0",
"chalk": "^4.1.1",
"enquirer": "2.3.6",
"eslint": "^7.28.0",
"c8": "^8.0.0",
"chalk": "^5.3.0",
"enquirer": "^2.3.6",
"eslint": "^8.44.0",
"eslint-ava-rule-tester": "^4.0.0",
"eslint-plugin-eslint-plugin": "^3.1.0",
"eslint-remote-tester": "^1.2.0",
"execa": "^5.1.1",
"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": "^1.0.1",
"execa": "^7.1.1",
"listr": "^0.14.3",
"lodash-es": "4.17.21",
"mem": "8.1.1",
"nyc": "^15.1.0",
"lodash-es": "^4.17.21",
"markdownlint-cli": "^0.35.0",
"mem": "^9.0.2",
"npm-package-json-lint": "^7.0.0",
"npm-run-all": "^4.1.5",
"outdent": "^0.8.0",
"pify": "^5.0.0",
"typescript": "^4.3.2",
"vue-eslint-parser": "^7.6.0",
"xo": "^0.40.2"
"typescript": "^5.1.6",
"vue-eslint-parser": "^9.3.1",
"xo": "^0.54.2",
"yaml": "^2.3.1"
},
"peerDependencies": {
"eslint": ">=7.28.0"
"eslint": ">=8.44.0"
},

@@ -88,3 +106,3 @@ "ava": {

},
"nyc": {
"c8": {
"reporter": [

@@ -96,34 +114,26 @@ "text",

"xo": {
"plugins": [
"eslint-plugin"
],
"extends": [
"plugin:eslint-plugin/all"
"plugin:internal-rules/all"
],
"ignores": [
"test/integration/{fixtures,fixtures-local}/**",
".cache-eslint-remote-tester",
"eslint-remote-tester-results"
"eslint-remote-tester-results",
"test/integration/{fixtures,fixtures-local}/**"
],
"rules": {
"unicorn/expiring-todo-comments": "off",
"unicorn/no-null": "error",
"unicorn/prevent-abbreviations": [
"unicorn/prefer-array-flat": [
"error",
{
"replacements": {
"ref": {
"reference": true
}
}
"functions": [
"flat",
"flatten"
]
}
]
],
"import/order": "off"
},
"overrides": [
{
"files": "rules/utils/*.js",
"rules": {
"eslint-plugin/prefer-object-rule": "off"
}
},
{
"files": [

@@ -137,6 +147,16 @@ "**/*.js"

"strict": "error",
"unicorn/prefer-module": "off",
"eslint-plugin/require-meta-schema": "off",
"eslint-plugin/require-meta-has-suggestions": "off",
"eslint-plugin/require-meta-docs-url": "off",
"unicorn/prefer-module": "off"
}
},
{
"files": [
"rules/*.js"
],
"plugins": [
"eslint-plugin"
],
"extends": [
"plugin:eslint-plugin/all"
],
"rules": {
"eslint-plugin/require-meta-docs-description": [

@@ -147,7 +167,22 @@ "error",

}
]
],
"eslint-plugin/require-meta-docs-url": "off",
"eslint-plugin/require-meta-has-suggestions": "off",
"eslint-plugin/require-meta-schema": "off"
}
}
]
},
"npmpackagejsonlint": {
"rules": {
"prefer-caret-version-devDependencies": [
"error",
{
"exceptions": [
"eslint-plugin-internal-rules"
]
}
]
}
}
}

@@ -1,6 +0,7 @@

# 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)
# 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)
<!-- markdownlint-disable-next-line no-inline-html -->
<img src="https://cloud.githubusercontent.com/assets/170270/18659176/1cc373d0-7f33-11e6-890f-0ba35362ee7e.jpg" width="180" align="right">
> Various awesome ESLint rules
> More than 100 powerful ESLint rules

@@ -13,4 +14,4 @@ You might want to check out [XO](https://github.com/xojs/xo), which includes this plugin.

```console
$ npm install --save-dev eslint eslint-plugin-unicorn
```sh
npm install --save-dev eslint eslint-plugin-unicorn
```

@@ -20,6 +21,6 @@

Configure it in `package.json`.
Use a [preset config](#preset-configs) or configure each rule in `package.json`.
<!-- Do not manually modify this table. Run: `npm run generate-usage-example` -->
<!-- USAGE_EXAMPLE_START -->
If you don't use the preset, ensure you use the same `env` and `parserOptions` config as below.
```json

@@ -30,6 +31,6 @@ {

"env": {
"es6": true
"es2024": true
},
"parserOptions": {
"ecmaVersion": 2021,
"ecmaVersion": "latest",
"sourceType": "module"

@@ -42,87 +43,3 @@ },

"unicorn/better-regex": "error",
"unicorn/catch-error-name": "error",
"unicorn/consistent-destructuring": "error",
"unicorn/consistent-function-scoping": "error",
"unicorn/custom-error-definition": "off",
"unicorn/empty-brace-spaces": "error",
"unicorn/error-message": "error",
"unicorn/escape-case": "error",
"unicorn/expiring-todo-comments": "error",
"unicorn/explicit-length-check": "error",
"unicorn/filename-case": "error",
"unicorn/import-index": "off",
"unicorn/import-style": "error",
"unicorn/new-for-builtins": "error",
"unicorn/no-abusive-eslint-disable": "error",
"unicorn/no-array-callback-reference": "error",
"unicorn/no-array-for-each": "error",
"unicorn/no-array-method-this-argument": "error",
"unicorn/no-array-push-push": "error",
"unicorn/no-array-reduce": "error",
"unicorn/no-console-spaces": "error",
"unicorn/no-document-cookie": "error",
"unicorn/no-for-loop": "error",
"unicorn/no-hex-escape": "error",
"unicorn/no-instanceof-array": "error",
"unicorn/no-keyword-prefix": "off",
"unicorn/no-lonely-if": "error",
"no-nested-ternary": "off",
"unicorn/no-nested-ternary": "error",
"unicorn/no-new-array": "error",
"unicorn/no-new-buffer": "error",
"unicorn/no-null": "error",
"unicorn/no-object-as-default-parameter": "error",
"unicorn/no-process-exit": "error",
"unicorn/no-static-only-class": "error",
"unicorn/no-this-assignment": "error",
"unicorn/no-unreadable-array-destructuring": "error",
"unicorn/no-unsafe-regex": "off",
"unicorn/no-unused-properties": "off",
"unicorn/no-useless-undefined": "error",
"unicorn/no-zero-fractions": "error",
"unicorn/number-literal-case": "error",
"unicorn/numeric-separators-style": "error",
"unicorn/prefer-add-event-listener": "error",
"unicorn/prefer-array-find": "error",
"unicorn/prefer-array-flat": "error",
"unicorn/prefer-array-flat-map": "error",
"unicorn/prefer-array-index-of": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-at": "off",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-default-parameters": "error",
"unicorn/prefer-dom-node-append": "error",
"unicorn/prefer-dom-node-dataset": "error",
"unicorn/prefer-dom-node-remove": "error",
"unicorn/prefer-dom-node-text-content": "error",
"unicorn/prefer-includes": "error",
"unicorn/prefer-keyboard-event-key": "error",
"unicorn/prefer-math-trunc": "error",
"unicorn/prefer-modern-dom-apis": "error",
"unicorn/prefer-module": "error",
"unicorn/prefer-negative-index": "error",
"unicorn/prefer-node-protocol": "error",
"unicorn/prefer-number-properties": "error",
"unicorn/prefer-object-has-own": "off",
"unicorn/prefer-optional-catch-binding": "error",
"unicorn/prefer-prototype-methods": "error",
"unicorn/prefer-query-selector": "error",
"unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-regexp-test": "error",
"unicorn/prefer-set-has": "error",
"unicorn/prefer-spread": "error",
"unicorn/prefer-string-replace-all": "off",
"unicorn/prefer-string-slice": "error",
"unicorn/prefer-string-starts-ends-with": "error",
"unicorn/prefer-string-trim-start-end": "error",
"unicorn/prefer-switch": "error",
"unicorn/prefer-ternary": "error",
"unicorn/prefer-top-level-await": "off",
"unicorn/prefer-type-error": "error",
"unicorn/prevent-abbreviations": "error",
"unicorn/require-array-join-separator": "error",
"unicorn/require-number-to-fixed-digits-argument": "error",
"unicorn/require-post-message-target-origin": "error",
"unicorn/string-content": "off",
"unicorn/throw-new-error": "error"
"unicorn/…": "error"
}

@@ -132,115 +49,141 @@ }

```
<!-- USAGE_EXAMPLE_END -->
## Rules
Each rule has emojis denoting:
<!-- Do not manually modify this list. Run: `npm run fix:eslint-docs` -->
<!-- begin auto-generated rules list -->
* ✅ if it belongs to the `recommended` configuration
* 🔧 if some problems reported by the rule are automatically fixable by the `--fix` [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) option
* 💡 if some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions)
💼 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs) enabled in.\
✅ Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs).\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
<!-- Do not manually modify this table. Run: `npm run generate-rules-table` -->
<!-- RULES_TABLE_START -->
| 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. | ✅ | 🔧 | |
| Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 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-index](docs/rules/import-index.md) | Enforce importing index files with `.`. | | 🔧 | |
| [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 `Array#forEach(…)`. | ✅ | 🔧 | |
| [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-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-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-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-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) | Forbid classes that only have static members. | ✅ | 🔧 | |
| [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | |
| [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | ✅ | 🔧 | |
| [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-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(…)` over the first 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()` over `Array#findIndex()` 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(…)`. | ✅ | 🔧 | 💡 |
| [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | | 🔧 | 💡 |
| [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 `.setAttribute(…)`. | ✅ | 🔧 | |
| [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-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | |
| [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-module](docs/rules/prefer-module.md) | Prefer JavaScript modules (ESM) over CommonJS. | ✅ | 🔧 | 💡 |
| [prefer-negative-index](docs/rules/prefer-negative-index.md) | Prefer negative index over `.length - index` for `{String,Array,TypedArray}#slice()`, `Array#splice()` and `Array#at()`. | ✅ | 🔧 | |
| [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-has-own](docs/rules/prefer-object-has-own.md) | Prefer `Object.hasOwn(…)` over `Object.prototype.hasOwnProperty.call(…)`. | | 🔧 | |
| [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-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(…)`, `Array#concat(…)` and `Array#slice()`. | ✅ | 🔧 | 💡 |
| [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. | ✅ | 🔧 | |
| [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. | | 🔧 | 💡 |
| [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | ✅ | 🔧 | |
<!-- end auto-generated rules list -->
<!-- RULES_TABLE_END -->
### Deprecated Rules
## Deprecated Rules
See [docs/deprecated-rules.md](docs/deprecated-rules.md)
## Recommended config
## Preset configs
This plugin exports a [`recommended` config](index.js) that enforces good practices.
See the [ESLint docs](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) for more information about extending config files.
Enable it in your `package.json` with the `extends` option:
**Note**: Preset configs will also enable the correct [parser options](https://eslint.org/docs/user-guide/configuring/language-options#specifying-parser-options) and [environment](https://eslint.org/docs/user-guide/configuring/language-options#specifying-environments).
### Recommended config
This plugin exports a [`recommended` config](configs/recommended.js) that enforces good practices.
```json

@@ -255,16 +198,26 @@ {

See the [ESLint docs](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) for more information about extending config files.
### All config
**Note**: This config will also enable the correct [parser options](https://eslint.org/docs/user-guide/configuring/language-options#specifying-parser-options) and [environment](https://eslint.org/docs/user-guide/configuring/language-options#specifying-environments).
This plugin exports an [`all` config](configs/all.js) that makes use of all rules (except for deprecated ones).
```json
{
"name": "my-awesome-project",
"eslintConfig": {
"extends": "plugin:unicorn/all"
}
}
```
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Adam Babcock](https://github.com/MrHen)
- [Fisker Cheung](https://github.com/fisker)
- [Bryan Mishkin](https://github.com/bmish)
- [futpib](https://github.com/futpib)
- [Fisker Cheung](https://github.com/fisker)
###### Former
### Former
- [Jeroen Engels](https://github.com/jfmengels)
- [Sam Verschueren](https://github.com/SamVerschueren)
- [Adam Babcock](https://github.com/MrHen)
'use strict';
const cleanRegexp = require('clean-regexp');
const {optimize} = require('regexp-tree');
const quoteString = require('./utils/quote-string.js');
const {newExpressionSelector} = require('./selectors/index.js');
const escapeString = require('./utils/escape-string.js');
const {isStringLiteral, isNewExpression, isRegexLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'better-regex';
const MESSAGE_ID_PARSE_ERROR = 'better-regex/parse-error';
const messages = {
[MESSAGE_ID]: '{{original}} can be optimized to {{optimized}}.'
[MESSAGE_ID]: '{{original}} can be optimized to {{optimized}}.',
[MESSAGE_ID_PARSE_ERROR]: 'Problem parsing {{original}}: {{error}}',
};
const newRegExp = [
newExpressionSelector({name: 'RegExp', min: 1}),
'[arguments.0.type="Literal"]'
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {

@@ -27,3 +25,7 @@ const {sortCharacterClasses} = context.options[0] || {};

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

@@ -44,7 +46,7 @@

node,
messageId: MESSAGE_ID_PARSE_ERROR,
data: {
original,
error: error.message
error: error.message,
},
message: 'Problem parsing {{original}}: {{error}}'
};

@@ -57,3 +59,3 @@ }

return {
const problem = {
node,

@@ -63,11 +65,32 @@ messageId: MESSAGE_ID,

original,
optimized
optimized,
},
fix: fixer => fixer.replaceText(node, optimized)
};
if (
node.parent.type === 'MemberExpression'
&& node.parent.object === node
&& !node.parent.optional
&& !node.parent.computed
&& node.parent.property.type === 'Identifier'
&& (
node.parent.property.name === 'toString'
|| node.parent.property.name === 'source'
)
) {
return problem;
}
return Object.assign(problem, {
fix: fixer => fixer.replaceText(node, optimized),
});
},
[newRegExp]: node => {
NewExpression(node) {
if (!isNewExpression(node, {name: 'RegExp', minimumArguments: 1})) {
return;
}
const [patternNode, flagsNode] = node.arguments;
if (typeof patternNode.value !== 'string') {
if (!isStringLiteral(patternNode)) {
return;

@@ -77,7 +100,5 @@ }

const oldPattern = patternNode.value;
const flags = flagsNode &&
flagsNode.type === 'Literal' &&
typeof flagsNode.value === 'string' ?
flagsNode.value :
'';
const flags = isStringLiteral(flagsNode)
? flagsNode.value
: '';

@@ -92,11 +113,11 @@ const newPattern = cleanRegexp(oldPattern, flags);

original: oldPattern,
optimized: newPattern
optimized: newPattern,
},
fix: fixer => fixer.replaceText(
patternNode,
quoteString(newPattern)
)
escapeString(newPattern, patternNode.raw.charAt(0)),
),
};
}
}
},
};

@@ -108,11 +129,13 @@ };

type: 'object',
additionalProperties: false,
properties: {
sortCharacterClasses: {
type: 'boolean',
default: true
}
}
}
default: true,
},
},
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -123,8 +146,8 @@ create,

docs: {
description: 'Improve regexes by making them shorter, consistent, and safer.'
description: 'Improve regexes by making them shorter, consistent, and safer.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'use strict';
const {findVariable} = require('eslint-utils');
const {findVariable} = require('@eslint-community/eslint-utils');
const avoidCapture = require('./utils/avoid-capture.js');
const renameVariable = require('./utils/rename-variable.js');
const {matches, methodCallSelector} = require('./selectors/index.js');
const {renameVariable} = require('./fix/index.js');
const {isMethodCall} = require('./ast/index.js');
const MESSAGE_ID = 'catch-error-name';
const messages = {
[MESSAGE_ID]: 'The catch parameter `{{originalName}}` should be named `{{fixedName}}`.'
[MESSAGE_ID]: 'The catch parameter `{{originalName}}` should be named `{{fixedName}}`.',
};
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({name: 'then', length: 2}),
methodCallSelector({name: 'catch', length: 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;
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {ecmaVersion} = context.parserOptions;
const options = {
name: 'error',
ignore: [],
...context.options[0]
...context.options[0],
};
const {name: expectedName} = options;
const ignore = options.ignore.map(
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
);
const isNameAllowed = name =>
name === expectedName ||
ignore.some(regexp => regexp.test(name)) ||
name.endsWith(expectedName) ||
name.endsWith(expectedName.charAt(0).toUpperCase() + expectedName.slice(1));
name === expectedName
|| ignore.some(regexp => regexp.test(name))
|| name.endsWith(expectedName)
|| name.endsWith(expectedName.charAt(0).toUpperCase() + expectedName.slice(1));
return {
[selector]: node => {
Identifier(node) {
if (
!(node.parent.type === 'CatchClause' && node.parent.param === node)
&& !isPromiseCatchParameter(node)
) {
return;
}
const originalName = node.name;
if (
isNameAllowed(originalName) ||
isNameAllowed(originalName.replace(/_+$/g, ''))
isNameAllowed(originalName)
|| isNameAllowed(originalName.replace(/_+$/g, ''))
) {

@@ -64,3 +70,3 @@ return;

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

@@ -70,3 +76,3 @@

// But can't reproduce, just ignore this case
/* istanbul ignore next */
/* c8 ignore next 3 */
if (!variable) {

@@ -82,7 +88,7 @@ return;

variable.scope,
...variable.references.map(({from}) => from)
...variable.references.map(({from}) => from),
];
const fixedName = avoidCapture(expectedName, scopes, ecmaVersion);
const fixedName = avoidCapture(expectedName, scopes);
return {
const problem = {
node,

@@ -92,7 +98,12 @@ messageId: MESSAGE_ID,

originalName,
fixedName
fixedName: fixedName || expectedName,
},
fix: fixer => renameVariable(variable, fixedName, fixer)
};
}
if (fixedName) {
problem.fix = fixer => renameVariable(variable, fixedName, fixer);
}
return problem;
},
};

@@ -104,14 +115,16 @@ };

type: 'object',
additionalProperties: false,
properties: {
name: {
type: 'string'
type: 'string',
},
ignore: {
type: 'array',
uniqueItems: true
}
}
}
uniqueItems: true,
},
},
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -122,8 +135,8 @@ create,

docs: {
description: 'Enforce a specific parameter name in catch clauses.'
description: 'Enforce a specific parameter name in catch clauses.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'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 => {

@@ -39,4 +23,4 @@ while (expression) {

return expression.type === 'Identifier' ||
expression.type === 'ThisExpression';
return expression.type === 'Identifier'
|| expression.type === 'ThisExpression';
};

@@ -56,23 +40,39 @@

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {ecmaVersion} = context.parserOptions;
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),
objectPattern: node.id
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) {

@@ -83,3 +83,3 @@ return;

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

@@ -92,5 +92,5 @@ // Property is destructured outside the current scope

const destructurings = objectPattern.properties.filter(property =>
property.type === 'Property' &&
property.key.type === 'Identifier' &&
property.value.type === 'Identifier'
property.type === 'Property'
&& property.key.type === 'Identifier'
&& property.value.type === 'Identifier',
);

@@ -101,8 +101,8 @@ const lastProperty = objectPattern.properties[objectPattern.properties.length - 1];

const expression = source.getText(node);
const member = source.getText(node.property);
const expression = sourceCode.getText(node);
const member = sourceCode.getText(node.property);
// Member might already be destructured
const destructuredMember = destructurings.find(property =>
property.key.name === member
property.key.name === member,
);

@@ -117,3 +117,3 @@

// Destructured member collides with an existing identifier
if (avoidCapture(member, [memberScope], ecmaVersion) !== member) {
if (avoidCapture(member, [memberScope]) !== member) {
return;

@@ -127,3 +127,3 @@ }

node,
messageId: MESSAGE_ID
messageId: MESSAGE_ID,
};

@@ -141,3 +141,3 @@ }

expression,
property: newMember
property: newMember,
},

@@ -151,13 +151,14 @@ * fix(fixer) {

if (!destructuredMember) {
yield lastProperty ?
fixer.insertTextAfter(lastProperty, `, ${newMember}`) :
fixer.replaceText(objectPattern, `{${newMember}}`);
yield lastProperty
? fixer.insertTextAfter(lastProperty, `, ${newMember}`)
: fixer.replaceText(objectPattern, `{${newMember}}`);
}
}
}]
},
}],
};
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -168,11 +169,11 @@ create,

docs: {
description: 'Use destructured variables over properties.'
description: 'Use destructured variables over properties.',
},
fixable: 'code',
hasSuggestions: true,
messages: {
[MESSAGE_ID]: 'Use destructured variables over properties.',
[MESSAGE_ID_SUGGEST]: 'Replace `{{expression}}` with destructured property `{{property}}`.'
[MESSAGE_ID_SUGGEST]: 'Replace `{{expression}}` with destructured property `{{property}}`.',
},
hasSuggestions: true
}
},
};
'use strict';
const {getFunctionHeadLocation, getFunctionNameWithKind} = require('eslint-utils');
const getReferences = require('./utils/get-references.js');
const {getFunctionHeadLocation, getFunctionNameWithKind} = require('@eslint-community/eslint-utils');
const {
getReferences,
isNodeMatches,
} = require('./utils/index.js');
const {
functionTypes,
} = require('./ast/index.js');
const MESSAGE_ID = 'consistent-function-scoping';
const messages = {
[MESSAGE_ID]: 'Move {{functionNameWithKind}} to the outer scope.'
[MESSAGE_ID]: 'Move {{functionNameWithKind}} to the outer scope.',
};

@@ -23,3 +29,3 @@

// Skip recursive function name
if (definition && definition.type === 'FunctionName' && resolved.name === definition.name.name) {
if (definition?.type === 'FunctionName' && resolved.name === definition.name.name) {
return false;

@@ -40,4 +46,4 @@ }

if (
!identifier.parent ||
identifier.parent.type !== 'FunctionDeclaration'
!identifier.parent
|| identifier.parent.type !== 'FunctionDeclaration'
) {

@@ -50,3 +56,3 @@ return false;

// If we have a scope, the earlier checks should have worked so ignore them here
/* istanbul ignore next: Hard to test */
/* c8 ignore next 3 */
if (identifierScope) {

@@ -57,3 +63,3 @@ return false;

const identifierParentScope = scopeManager.acquire(identifier.parent);
/* istanbul ignore next: Hard to test */
/* c8 ignore next 3 */
if (!identifierParentScope) {

@@ -77,5 +83,5 @@ return false;

.some(variable =>
hitReference(variable.references) ||
hitDefinitions(variable.defs) ||
hitIdentifier(variable.identifiers)
hitReference(variable.references)
|| hitDefinitions(variable.defs)
|| hitIdentifier(variable.identifiers),
);

@@ -85,3 +91,3 @@ }

// https://reactjs.org/docs/hooks-reference.html
const reactHooks = new Set([
const reactHooks = [
'useState',

@@ -96,26 +102,22 @@ 'useEffect',

'useLayoutEffect',
'useDebugValue'
]);
'useDebugValue',
].flatMap(hookName => [hookName, `React.${hookName}`]);
const isReactHook = scope =>
scope.block &&
scope.block.parent &&
scope.block.parent.callee &&
scope.block.parent.callee.type === 'Identifier' &&
reactHooks.has(scope.block.parent.callee.name);
scope.block?.parent?.callee
&& isNodeMatches(scope.block.parent.callee, reactHooks);
const isArrowFunctionWithThis = scope =>
scope.type === 'function' &&
scope.block &&
scope.block.type === 'ArrowFunctionExpression' &&
(scope.thisFound || scope.childScopes.some(scope => isArrowFunctionWithThis(scope)));
scope.type === 'function'
&& scope.block?.type === 'ArrowFunctionExpression'
&& (scope.thisFound || scope.childScopes.some(scope => isArrowFunctionWithThis(scope)));
const iifeFunctionTypes = new Set([
'FunctionExpression',
'ArrowFunctionExpression'
'ArrowFunctionExpression',
]);
const isIife = node => node &&
iifeFunctionTypes.has(node.type) &&
node.parent &&
node.parent.type === 'CallExpression' &&
node.parent.callee === node;
const isIife = node =>
iifeFunctionTypes.has(node.type)
&& node.parent.type === 'CallExpression'
&& node.parent.callee === node;

@@ -148,6 +150,6 @@ function checkNode(node, scopeManager) {

if (
!parentScope ||
parentScope.type === 'global' ||
isReactHook(parentScope) ||
isIife(parentNode)
!parentScope
|| parentScope.type === 'global'
|| isReactHook(parentScope)
|| isIife(parentNode)
) {

@@ -160,5 +162,6 @@ return true;

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {checkArrowFunctions} = {checkArrowFunctions: true, ...context.options[0]};
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {scopeManager} = sourceCode;

@@ -168,37 +171,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),
},
};
});
};

@@ -209,11 +212,13 @@

type: 'object',
additionalProperties: false,
properties: {
checkArrowFunctions: {
type: 'boolean',
default: true
}
}
}
default: true,
},
},
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -224,7 +229,7 @@ create,

docs: {
description: 'Move function definitions to the highest possible scope.'
description: 'Move function definitions to the highest possible scope.',
},
schema,
messages
}
messages,
},
};

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

const messages = {
[MESSAGE_ID_INVALID_EXPORT]: 'Exported error name should match error class'
[MESSAGE_ID_INVALID_EXPORT]: 'Exported error name should match error class',
};

@@ -36,10 +36,10 @@

const isSuperExpression = node =>
node.type === 'ExpressionStatement' &&
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'Super';
node.type === 'ExpressionStatement'
&& node.expression.type === 'CallExpression'
&& node.expression.callee.type === 'Super';
const isAssignmentExpression = (node, name) => {
if (
node.type !== 'ExpressionStatement' ||
node.expression.type !== 'AssignmentExpression'
node.type !== 'ExpressionStatement'
|| node.expression.type !== 'AssignmentExpression'
) {

@@ -58,19 +58,8 @@ return false;

const isPropertyDefinition = (node, name) => {
const {type, computed, key} = node;
if (type !== 'PropertyDefinition' && type !== 'ClassProperty') {
return false;
}
const isPropertyDefinition = (node, name) =>
node.type === 'PropertyDefinition'
&& !node.computed
&& node.key.type === 'Identifier'
&& node.key.name === name;
if (computed) {
return false;
}
if (key.type !== 'Identifier') {
return false;
}
return key.name === name;
};
function * customErrorDefinition(context, node) {

@@ -91,3 +80,3 @@ if (!hasValidSuperClass(node)) {

node: node.id,
message: `Invalid class name, use \`${className}\`.`
message: `Invalid class name, use \`${className}\`.`,
};

@@ -105,4 +94,4 @@ }

range[0],
range[0] + 1
], getConstructorMethod(name))
range[0] + 1,
], getConstructorMethod(name)),
};

@@ -127,3 +116,3 @@ return;

node: constructorBodyNode,
message: 'Missing call to `super()` in constructor.'
message: 'Missing call to `super()` in constructor.',
};

@@ -141,3 +130,3 @@ } else if (messageExpressionIndex !== -1) {

superExpression.range[0],
superExpression.range[0] + 6
superExpression.range[0] + 6,
], rhs.raw || rhs.name);

@@ -148,5 +137,5 @@ }

messageExpressionIndex === 0 ? constructorBodyNode.range[0] : constructorBody[messageExpressionIndex - 1].range[1],
expression.range[1]
expression.range[1],
]);
}
},
};

@@ -159,6 +148,6 @@ }

if (!nameProperty || !nameProperty.value || nameProperty.value.value !== name) {
if (!nameProperty?.value || nameProperty.value.value !== name) {
yield {
node: nameProperty && nameProperty.value ? nameProperty.value : constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`
node: nameProperty?.value ?? constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`,
};

@@ -168,4 +157,4 @@ }

yield {
node: nameExpression ? nameExpression.expression.right : constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`
node: nameExpression?.expression.right ?? constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`,
};

@@ -202,14 +191,26 @@ }

messageId: MESSAGE_ID_INVALID_EXPORT,
fix: fixer => fixer.replaceText(node.left.property, errorName)
fix: fixer => fixer.replaceText(node.left.property, errorName),
};
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
return {
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)
};
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);
}
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -220,7 +221,7 @@ create,

docs: {
description: 'Enforce correct `Error` subclassing.'
description: 'Enforce correct `Error` subclassing.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isOpeningBraceToken} = require('eslint-utils');
const {matches} = require('./selectors/index.js');
const {isOpeningBraceToken} = require('@eslint-community/eslint-utils');
const MESSAGE_ID = 'empty-brace-spaces';
const messages = {
[MESSAGE_ID]: 'Do not add spaces between braces.'
[MESSAGE_ID]: 'Do not add spaces between braces.',
};
const selector = matches([
'BlockStatement[body.length=0]',
'ClassBody[body.length=0]',
'ObjectExpression[properties.length=0]',
// Experimental https://github.com/tc39/proposal-record-tuple
'RecordExpression[properties.length=0]',
// Experimental https://github.com/tc39/proposal-class-static-block
'StaticBlock[body.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 => {
return {
[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);
context.on([
'BlockStatement',
'ClassBody',
'StaticBlock',
], node => {
if (node.body.length > 0) {
return;
}
if (!/^\s+$/.test(textBetween)) {
return;
}
return getProblem(node, context);
});
return {
loc: {
start: openingBrace.loc.end,
end: closingBrace.loc.start
},
messageId: MESSAGE_ID,
fix: fixer => fixer.removeRange([start, end])
};
context.on([
'ObjectExpression',
// Experimental https://github.com/tc39/proposal-record-tuple
'RecordExpression',
], node => {
if (node.properties.length > 0) {
return;
}
};
return getProblem(node, context);
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -55,7 +67,7 @@ create,

docs: {
description: 'Enforce no spaces between braces.'
description: 'Enforce no spaces between braces.',
},
fixable: 'whitespace',
messages
}
messages,
},
};
'use strict';
const {getStaticValue} = require('eslint-utils');
const {callOrNewExpressionSelector} = require('./selectors/index.js');
const {getStaticValue} = require('@eslint-community/eslint-utils');
const isShadowed = require('./utils/is-shadowed.js');
const {isCallOrNewExpression} = require('./ast/index.js');

@@ -11,75 +12,85 @@ const MESSAGE_ID_MISSING_MESSAGE = 'missing-message';

[MESSAGE_ID_EMPTY_MESSAGE]: 'Error message should not be an empty string.',
[MESSAGE_ID_NOT_STRING]: 'Error message should be a string.'
[MESSAGE_ID_NOT_STRING]: 'Error message should be a string.',
};
const selector = callOrNewExpressionSelector({
names: [
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
'Error',
'EvalError',
'RangeError',
'ReferenceError',
'SyntaxError',
'TypeError',
'URIError',
'InternalError',
'AggregateError'
]
});
const builtinErrors = [
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
'Error',
'EvalError',
'RangeError',
'ReferenceError',
'SyntaxError',
'TypeError',
'URIError',
'InternalError',
'AggregateError',
];
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
return {
[selector](expression) {
const constructorName = expression.callee.name;
const messageArgumentIndex = constructorName === 'AggregateError' ? 1 : 0;
const callArguments = expression.arguments;
context.on(['CallExpression', 'NewExpression'], expression => {
if (!isCallOrNewExpression(expression, {
names: builtinErrors,
optional: false,
})) {
return;
}
// If message is `SpreadElement` or there is `SpreadElement` before message
if (callArguments.some((node, index) => index <= messageArgumentIndex && node.type === 'SpreadElement')) {
return;
}
const scope = context.sourceCode.getScope(expression);
if (isShadowed(scope, expression.callee)) {
return;
}
const node = callArguments[messageArgumentIndex];
if (!node) {
return {
node: expression,
messageId: MESSAGE_ID_MISSING_MESSAGE,
data: {constructorName}
};
}
const constructorName = expression.callee.name;
const messageArgumentIndex = constructorName === 'AggregateError' ? 1 : 0;
const callArguments = expression.arguments;
// These types can't be string, and `getStaticValue` may don't know the value
// Add more types, if issue reported
if (node.type === 'ArrayExpression' || node.type === 'ObjectExpression') {
return {
node,
messageId: MESSAGE_ID_NOT_STRING
};
}
// If message is `SpreadElement` or there is `SpreadElement` before message
if (callArguments.some((node, index) => index <= messageArgumentIndex && node.type === 'SpreadElement')) {
return;
}
const staticResult = getStaticValue(node, context.getScope());
const node = callArguments[messageArgumentIndex];
if (!node) {
return {
node: expression,
messageId: MESSAGE_ID_MISSING_MESSAGE,
data: {constructorName},
};
}
// We don't know the value of `message`
if (!staticResult) {
return;
}
// These types can't be string, and `getStaticValue` may don't know the value
// Add more types, if issue reported
if (node.type === 'ArrayExpression' || node.type === 'ObjectExpression') {
return {
node,
messageId: MESSAGE_ID_NOT_STRING,
};
}
const {value} = staticResult;
if (typeof value !== 'string') {
return {
node,
messageId: MESSAGE_ID_NOT_STRING
};
}
const staticResult = getStaticValue(node, scope);
if (value === '') {
return {
node,
messageId: MESSAGE_ID_EMPTY_MESSAGE
};
}
// We don't know the value of `message`
if (!staticResult) {
return;
}
};
const {value} = staticResult;
if (typeof value !== 'string') {
return {
node,
messageId: MESSAGE_ID_NOT_STRING,
};
}
if (value === '') {
return {
node,
messageId: MESSAGE_ID_EMPTY_MESSAGE,
};
}
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -90,6 +101,6 @@ create,

docs: {
description: 'Enforce passing a `message` value when creating a built-in error.'
description: 'Enforce passing a `message` value when creating a built-in error.',
},
messages
}
messages,
},
};
'use strict';
const replaceTemplateElement = require('./utils/replace-template-element.js');
const {replaceTemplateElement} = require('./fix/index.js');
const {isRegexLiteral, isStringLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'escape-case';
const messages = {
[MESSAGE_ID]: 'Use uppercase characters for the value of the escape sequence.'
[MESSAGE_ID]: 'Use uppercase characters for the value of the escape sequence.',
};

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

messageId: MESSAGE_ID,
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed)
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed),
};

@@ -24,31 +25,31 @@ }

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

@@ -59,7 +60,7 @@ create,

docs: {
description: 'Require escape sequences to use uppercase values.'
description: 'Require escape sequences to use uppercase values.',
},
fixable: 'code',
messages
}
messages,
},
};

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

const ci = require('ci-info');
const baseRule = require('eslint/lib/rules/no-warning-comments');
const getBuiltinRule = require('./utils/get-builtin-rule.js');
const baseRule = getBuiltinRule('no-warning-comments');
// `unicorn/` prefix is added to avoid conflicts with core rule
const MESSAGE_ID_AVOID_MULTIPLE_DATES = 'unicorn/avoidMultipleDates';
const MESSAGE_ID_EXPIRED_TODO = 'unicorn/expiredTodo';
const MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS =
'unicorn/avoidMultiplePackageVersions';
const MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS
= 'unicorn/avoidMultiplePackageVersions';
const MESSAGE_ID_REACHED_PACKAGE_VERSION = 'unicorn/reachedPackageVersion';

@@ -22,3 +24,3 @@ const MESSAGE_ID_HAVE_PACKAGE = 'unicorn/havePackage';

// Override of core rule message with a more specific one - no prefix
const MESSSAGE_ID_CORE_RULE_UNEXPECTED_COMMENT = 'unexpectedComment';
const MESSAGE_ID_CORE_RULE_UNEXPECTED_COMMENT = 'unexpectedComment';
const messages = {

@@ -46,7 +48,10 @@ [MESSAGE_ID_AVOID_MULTIPLE_DATES]:

...baseRule.meta.messages,
[MESSSAGE_ID_CORE_RULE_UNEXPECTED_COMMENT]:
'Unexpected \'{{matchedTerm}}\' comment without any conditions: \'{{comment}}\'.'
[MESSAGE_ID_CORE_RULE_UNEXPECTED_COMMENT]:
'Unexpected \'{{matchedTerm}}\' comment without any conditions: \'{{comment}}\'.',
};
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);

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

...packageJson.dependencies,
...packageJson.devDependencies
...packageJson.devDependencies,
};

@@ -105,3 +110,3 @@

type: 'dates',
value: argumentString
value: argumentString,
};

@@ -118,4 +123,4 @@ }

name,
condition
}
condition,
},
};

@@ -138,4 +143,4 @@ }

condition,
version
}
version,
},
};

@@ -150,4 +155,4 @@ }

condition,
version
}
version,
},
};

@@ -165,4 +170,4 @@ }

condition: condition.trim(),
version: version.trim()
}
version: version.trim(),
},
};

@@ -175,3 +180,3 @@ }

type: 'unknowns',
value: argumentString
value: argumentString,
};

@@ -197,4 +202,3 @@ }

function reachedDate(past) {
const now = new Date().toISOString().slice(0, 10);
function reachedDate(past, now) {
return Date.parse(past) < Date.parse(now);

@@ -204,3 +208,4 @@ }

function tryToCoerceVersion(rawVersion) {
/* istanbul ignore if: version in `package.json` and comment can't be empty */
// `version` in `package.json` and comment can't be empty
/* c8 ignore next 3 */
if (!rawVersion) {

@@ -219,3 +224,3 @@ return false;

'~',
'^'
'^',
];

@@ -229,3 +234,4 @@ const foundTrailingNoise = leadingNoises.find(noise => version.startsWith(noise));

const parts = version.split(' ');
/* istanbul ignore if: We don't have this `package.json` to test */
// We don't have this `package.json` to test
/* c8 ignore next 3 */
if (parts.length > 1) {

@@ -235,3 +241,4 @@ version = parts[0];

/* istanbul ignore if: We don't have this `package.json` to test */
// We don't have this `package.json` to test
/* c8 ignore next 3 */
if (semver.valid(version)) {

@@ -246,3 +253,4 @@ return version;

} catch {
/* istanbul ignore next: We don't have this `package.json` to test */
// We don't have this `package.json` to test
/* c8 ignore next 3 */
return false;

@@ -255,6 +263,7 @@ }

'>': semver.gt,
'>=': semver.gte
'>=': semver.gte,
}[operator];
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {

@@ -266,10 +275,11 @@ const options = {

allowWarningComments: true,
...context.options[0]
date: new Date().toISOString().slice(0, 10),
...context.options[0],
};
const ignoreRegexes = options.ignore.map(
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
);
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const comments = sourceCode.getAllComments();

@@ -288,4 +298,4 @@ const unusedComments = comments

...comment,
value: line
}))
value: line,
})),
).filter(comment => processComment(comment));

@@ -298,10 +308,6 @@

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

@@ -330,3 +336,3 @@ const rules = baseRule.create(fakeContext);

engines = [],
unknowns = []
unknowns = [],
} = parsed;

@@ -341,11 +347,11 @@

expirationDates: dates.join(', '),
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});
} else if (dates.length === 1) {
uses++;
const [date] = dates;
const [expirationDate] = dates;
const shouldIgnore = options.ignoreDatesOnPullRequests && ci.isPR;
if (!shouldIgnore && reachedDate(date)) {
if (!shouldIgnore && reachedDate(expirationDate, options.date)) {
context.report({

@@ -355,5 +361,5 @@ loc: comment.loc,

data: {
expirationDate: date,
message: parseTodoMessage(comment.value)
}
expirationDate,
message: parseTodoMessage(comment.value),
},
});

@@ -372,4 +378,4 @@ }

.join(', '),
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});

@@ -390,4 +396,4 @@ } else if (packageVersions.length === 1) {

comparison: `${condition}${version}`,
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});

@@ -406,6 +412,6 @@ }

if (isInclusion) {
const [trigger, messageId] =
dependency.condition === 'in' ?
[hasTargetPackage, MESSAGE_ID_HAVE_PACKAGE] :
[!hasTargetPackage, MESSAGE_ID_DONT_HAVE_PACKAGE];
const [trigger, messageId]
= dependency.condition === 'in'
? [hasTargetPackage, MESSAGE_ID_HAVE_PACKAGE]
: [!hasTargetPackage, MESSAGE_ID_DONT_HAVE_PACKAGE];

@@ -418,4 +424,4 @@ if (trigger) {

package: dependency.name,
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});

@@ -430,3 +436,3 @@ }

/* istanbul ignore if: Can't test in Node.js */
/* c8 ignore start */
if (!hasTargetPackage || !targetPackageVersion) {

@@ -436,2 +442,3 @@ // Can't compare `¯\_(ツ)_/¯`

}
/* c8 ignore end */

@@ -446,4 +453,4 @@ const compare = semverComparisonForOperator(dependency.condition);

comparison: `${dependency.name} ${dependency.condition} ${dependency.version}`,
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});

@@ -461,3 +468,3 @@ }

/* istanbul ignore if: Can't test in this repo */
/* c8 ignore next 3 */
if (!hasTargetEngine) {

@@ -469,3 +476,3 @@ continue;

const targetPackageEngineVersion = tryToCoerceVersion(
targetPackageRawEngineVersion
targetPackageRawEngineVersion,
);

@@ -481,4 +488,4 @@

comparison: `node${engine.condition}${engine.version}`,
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});

@@ -496,3 +503,3 @@ }

0,
comparisonIndex
comparisonIndex,
)}@${unknown.slice(comparisonIndex)}`;

@@ -508,4 +515,4 @@

fix: testString,
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});

@@ -526,4 +533,4 @@ continue;

fix: withoutWhitespace,
message: parseTodoMessage(comment.value)
}
message: parseTodoMessage(comment.value),
},
});

@@ -540,3 +547,3 @@ continue;

rules.Program(); // eslint-disable-line new-cap
}
},
};

@@ -548,2 +555,3 @@ };

type: 'object',
additionalProperties: false,
properties: {

@@ -553,22 +561,26 @@ terms: {

items: {
type: 'string'
}
type: 'string',
},
},
ignore: {
type: 'array',
uniqueItems: true
uniqueItems: true,
},
ignoreDatesOnPullRequests: {
type: 'boolean',
default: true
default: true,
},
allowWarningComments: {
type: 'boolean',
default: false
}
default: false,
},
date: {
type: 'string',
format: 'date',
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -579,7 +591,7 @@ create,

docs: {
description: 'Add expiration conditions to TODO comments.'
description: 'Add expiration conditions to TODO comments.',
},
schema,
messages
}
messages,
},
};
'use strict';
const {isParenthesized, getStaticValue} = require('eslint-utils');
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
const {checkVueTemplate} = require('./utils/rule.js');
const isLiteralValue = require('./utils/is-literal-value.js');
const isLogicalExpression = require('./utils/is-logical-expression.js');
const {isBooleanNode, getBooleanAncestor} = require('./utils/boolean.js');
const {memberExpressionSelector} = require('./selectors/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isLiteral, isMemberExpression, isNumberLiteral} = require('./ast/index.js');

@@ -15,13 +15,13 @@ const TYPE_NON_ZERO = 'non-zero';

[TYPE_ZERO]: 'Use `.{{property}} {{code}}` when checking {{property}} is zero.',
[MESSAGE_ID_SUGGESTION]: 'Replace `.{{property}}` with `.{{property}} {{code}}`.'
[MESSAGE_ID_SUGGESTION]: 'Replace `.{{property}}` with `.{{property}} {{code}}`.',
};
const isCompareRight = (node, operator, value) =>
node.type === 'BinaryExpression' &&
node.operator === operator &&
isLiteralValue(node.right, value);
node.type === 'BinaryExpression'
&& node.operator === operator
&& isLiteral(node.right, value);
const isCompareLeft = (node, operator, value) =>
node.type === 'BinaryExpression' &&
node.operator === operator &&
isLiteralValue(node.left, value);
node.type === 'BinaryExpression'
&& node.operator === operator
&& isLiteral(node.left, value);
const nonZeroStyles = new Map([

@@ -32,4 +32,4 @@ [

code: '> 0',
test: node => isCompareRight(node, '>', 0)
}
test: node => isCompareRight(node, '>', 0),
},
],

@@ -40,20 +40,11 @@ [

code: '!== 0',
test: node => isCompareRight(node, '!==', 0)
}
test: node => isCompareRight(node, '!==', 0),
},
],
[
'greater-than-or-equal',
{
code: '>= 1',
test: node => isCompareRight(node, '>=', 1)
}
]
]);
const zeroStyle = {
code: '=== 0',
test: node => isCompareRight(node, '===', 0)
test: node => isCompareRight(node, '===', 0),
};
const lengthSelector = memberExpressionSelector(['length', 'size']);
function getLengthCheckNode(node) {

@@ -65,13 +56,13 @@ node = node.parent;

// `foo.length === 0`
isCompareRight(node, '===', 0) ||
isCompareRight(node, '===', 0)
// `foo.length == 0`
isCompareRight(node, '==', 0) ||
|| isCompareRight(node, '==', 0)
// `foo.length < 1`
isCompareRight(node, '<', 1) ||
|| isCompareRight(node, '<', 1)
// `0 === foo.length`
isCompareLeft(node, '===', 0) ||
|| isCompareLeft(node, '===', 0)
// `0 == foo.length`
isCompareLeft(node, '==', 0) ||
|| isCompareLeft(node, '==', 0)
// `1 > foo.length`
isCompareLeft(node, '>', 1)
|| isCompareLeft(node, '>', 1)
) {

@@ -84,17 +75,17 @@ return {isZeroLengthCheck: true, node};

// `foo.length !== 0`
isCompareRight(node, '!==', 0) ||
isCompareRight(node, '!==', 0)
// `foo.length != 0`
isCompareRight(node, '!=', 0) ||
|| isCompareRight(node, '!=', 0)
// `foo.length > 0`
isCompareRight(node, '>', 0) ||
|| isCompareRight(node, '>', 0)
// `foo.length >= 1`
isCompareRight(node, '>=', 1) ||
|| isCompareRight(node, '>=', 1)
// `0 !== foo.length`
isCompareLeft(node, '!==', 0) ||
|| isCompareLeft(node, '!==', 0)
// `0 !== foo.length`
isCompareLeft(node, '!=', 0) ||
|| isCompareLeft(node, '!=', 0)
// `0 < foo.length`
isCompareLeft(node, '<', 0) ||
|| isCompareLeft(node, '<', 0)
// `1 <= foo.length`
isCompareLeft(node, '<=', 1)
|| isCompareLeft(node, '<=', 1)
) {

@@ -107,9 +98,18 @@ return {isZeroLengthCheck: false, node};

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) {
const options = {
'non-zero': 'greater-than',
...context.options[0]
...context.options[0],
};
const nonZeroStyle = nonZeroStyles.get(options['non-zero']);
const sourceCode = context.getSourceCode();
const {sourceCode} = context;

@@ -124,5 +124,5 @@ function getProblem({node, isZeroLengthCheck, lengthNode, autoFix}) {

if (
!isParenthesized(node, sourceCode) &&
node.type === 'UnaryExpression' &&
node.parent.type === 'UnaryExpression'
!isParenthesized(node, sourceCode)
&& node.type === 'UnaryExpression'
&& (node.parent.type === 'UnaryExpression' || node.parent.type === 'AwaitExpression')
) {

@@ -132,3 +132,6 @@ fixed = `(${fixed})`;

const fix = fixer => fixer.replaceText(node, fixed);
const fix = function * (fixer) {
yield fixer.replaceText(node, fixed);
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
};

@@ -138,3 +141,3 @@ const problem = {

messageId: isZeroLengthCheck ? TYPE_ZERO : TYPE_NON_ZERO,
data: {code, property: lengthNode.property.name}
data: {code, property: lengthNode.property.name},
};

@@ -148,5 +151,4 @@

messageId: MESSAGE_ID_SUGGESTION,
data: problem.data,
fix
}
fix,
},
];

@@ -159,8 +161,15 @@ }

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)) {

@@ -185,3 +194,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;

@@ -196,3 +211,3 @@ node = lengthNode;

}
}
},
};

@@ -204,11 +219,13 @@ }

type: 'object',
additionalProperties: false,
properties: {
'non-zero': {
enum: [...nonZeroStyles.keys()],
default: 'greater-than'
}
}
}
default: 'greater-than',
},
},
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -219,3 +236,3 @@ create: checkVueTemplate(create),

docs: {
description: 'Enforce explicitly comparing the `length` or `size` property of a value.'
description: 'Enforce explicitly comparing the `length` or `size` property of a value.',
},

@@ -225,4 +242,4 @@ fixable: 'code',

messages,
hasSuggestions: true
}
hasSuggestions: true,
},
};
'use strict';
const path = require('path');
const path = require('node:path');
const {camelCase, kebabCase, snakeCase, upperFirst} = require('lodash');

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

[MESSAGE_ID]: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.',
[MESSAGE_ID_EXTENSION]: 'File extension `{{extension}}` is not in lowercase. Rename it to `{{filename}}`.'
[MESSAGE_ID_EXTENSION]: 'File extension `{{extension}}` is not in lowercase. Rename it to `{{filename}}`.',
};

@@ -18,3 +18,3 @@

const PLACEHOLDER_REGEX = new RegExp(PLACEHOLDER, 'i');
const isIgnoredChar = char => !/^[a-z\d-_$]$/i.test(char);
const isIgnoredChar = char => !/^[a-z\d-_]$/i.test(char);
const ignoredByDefault = new Set(['index.js', 'index.mjs', 'index.cjs', 'index.ts', 'index.tsx', 'index.vue']);

@@ -47,16 +47,16 @@ const isLowerCase = string => string === string.toLowerCase();

fn: camelCase,
name: 'camel case'
name: 'camel case',
},
kebabCase: {
fn: kebabCase,
name: 'kebab case'
name: 'kebab case',
},
snakeCase: {
fn: snakeCase,
name: 'snake case'
name: 'snake case',
},
pascalCase: {
fn: pascalCase,
name: 'pascal case'
}
name: 'pascal case',
},
};

@@ -96,3 +96,3 @@

const {
samples: combinations
samples: combinations,
} = cartesianProductSamples(replacements);

@@ -114,3 +114,3 @@

if (lastWord && lastWord.ignored === isIgnored) {
if (lastWord?.ignored === isIgnored) {
lastWord.word += char;

@@ -120,3 +120,3 @@ } else {

word: char,
ignored: isIgnored
ignored: isIgnored,
};

@@ -129,3 +129,3 @@ words.push(lastWord);

leading,
words
words,
};

@@ -140,14 +140,5 @@ }

*/
function englishishJoinWords(words) {
if (words.length === 1) {
return words[0];
}
const englishishJoinWords = words => new Intl.ListFormat('en-US', {type: 'disjunction'}).format(words);
if (words.length === 2) {
return `${words[0]} or ${words[1]}`;
}
return `${words.slice(0, -1).join(', ')}, or ${words[words.length - 1]}`;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {

@@ -164,6 +155,6 @@ const options = context.options[0] || {};

const chosenCasesFunctions = chosenCases.map(case_ => ignoreNumbers(cases[case_].fn));
const filenameWithExtension = context.getPhysicalFilename();
const filenameWithExtension = context.physicalFilename;
if (filenameWithExtension === '<input>' || filenameWithExtension === '<text>') {
return {};
return;
}

@@ -189,3 +180,3 @@

messageId: MESSAGE_ID_EXTENSION,
data: {filename: filename + extension.toLowerCase(), extension}
data: {filename: filename + extension.toLowerCase(), extension},
};

@@ -199,3 +190,3 @@ }

leading,
extension
extension,
});

@@ -210,6 +201,6 @@

chosenCases: englishishJoinWords(chosenCases.map(x => cases[x].name)),
renamedFilenames: englishishJoinWords(renamedFilenames.map(x => `\`${x}\``))
}
renamedFilenames: englishishJoinWords(renamedFilenames.map(x => `\`${x}\``)),
},
};
}
},
};

@@ -228,11 +219,11 @@ };

'kebabCase',
'pascalCase'
]
'pascalCase',
],
},
ignore: {
type: 'array',
uniqueItems: true
}
uniqueItems: true,
},
},
additionalProperties: false
additionalProperties: false,
},

@@ -244,27 +235,28 @@ {

camelCase: {
type: 'boolean'
type: 'boolean',
},
snakeCase: {
type: 'boolean'
type: 'boolean',
},
kebabCase: {
type: 'boolean'
type: 'boolean',
},
pascalCase: {
type: 'boolean'
}
type: 'boolean',
},
},
additionalProperties: false
additionalProperties: false,
},
ignore: {
type: 'array',
uniqueItems: true
}
uniqueItems: true,
},
},
additionalProperties: false
}
]
}
additionalProperties: false,
},
],
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -275,7 +267,7 @@ create,

docs: {
description: 'Enforce a case style for filenames.'
description: 'Enforce a case style for filenames.',
},
schema,
messages
}
messages,
},
};
'use strict';
const {isCommaToken} = require('eslint-utils');
const {isCommaToken} = require('@eslint-community/eslint-utils');

@@ -7,3 +7,3 @@ function appendArgument(fixer, node, text, sourceCode) {

// But parentheses of `NewExpression` could be omitted, add this check to prevent accident use on it
/* istanbul ignore next */
/* c8 ignore next 3 */
if (node.type !== 'CallExpression') {

@@ -10,0 +10,0 @@ throw new Error(`Unexpected node "${node.type}".`);

'use strict';
module.exports = {
// Utilities
extendFixRange: require('./extend-fix-range.js'),
removeParentheses: require('./remove-parentheses.js'),
appendArgument: require('./append-argument.js'),
removeArgument: require('./remove-argument.js'),
replaceArgument: require('./replace-argument.js'),
switchNewExpressionToCallExpression: require('./switch-new-expression-to-call-expression.js'),
switchCallExpressionToNewExpression: require('./switch-call-expression-to-new-expression.js'),
removeMemberExpressionProperty: require('./remove-member-expression-property.js'),
removeMethodCall: require('./remove-method-call.js')
removeMethodCall: require('./remove-method-call.js'),
replaceTemplateElement: require('./replace-template-element.js'),
replaceReferenceIdentifier: require('./replace-reference-identifier.js'),
renameVariable: require('./rename-variable.js'),
replaceNodeOrTokenAndSpacesBefore: require('./replace-node-or-token-and-spaces-before.js'),
removeSpacesAfter: require('./remove-spaces-after.js'),
fixSpaceAroundKeyword: require('./fix-space-around-keywords.js'),
replaceStringLiteral: require('./replace-string-literal.js'),
addParenthesizesToReturnOrThrowExpression: require('./add-parenthesizes-to-return-or-throw-expression.js'),
};
'use strict';
const {isCommaToken} = require('eslint-utils');
const {isCommaToken} = require('@eslint-community/eslint-utils');
const {getParentheses} = require('../utils/parentheses.js');

@@ -20,3 +20,3 @@

// If the removed argument is the only argument, the trailing comma must be removed too
/* istanbul ignore next: Not reachable for now */
/* c8 ignore start */
if (callExpression.arguments.length === 1) {

@@ -28,2 +28,3 @@ const tokenAfter = sourceCode.getTokenBefore(lastToken);

}
/* c8 ignore end */

@@ -30,0 +31,0 @@ return fixer.replaceTextRange([start, end], '');

'use strict';
const isNewExpressionWithParentheses = require('../utils/is-new-expression-with-parentheses.js');
const {isParenthesized} = require('../utils/parentheses.js');
const isOnSameLine = require('../utils/is-on-same-line.js');
const addParenthesizesToReturnOrThrowExpression = require('./add-parenthesizes-to-return-or-throw-expression.js');
const removeSpaceAfter = require('./remove-spaces-after.js');
function * fixReturnStatementArgument(newExpression, sourceCode, fixer) {
const {parent} = newExpression;
if (
parent.type !== 'ReturnStatement' ||
parent.argument !== newExpression ||
isParenthesized(newExpression, sourceCode)
) {
return;
}
function * switchNewExpressionToCallExpression(newExpression, sourceCode, fixer) {
const newToken = sourceCode.getFirstToken(newExpression);
yield fixer.remove(newToken);
yield removeSpaceAfter(newToken, sourceCode, fixer);
const returnStatement = parent;
const returnToken = sourceCode.getFirstToken(returnStatement);
const classNode = newExpression.callee;
// Ideally, we should use first parenthesis of the `callee`, and should check spaces after the `new` token
// But adding extra parentheses is harmless, no need to be too complicated
if (returnToken.loc.start.line === classNode.loc.start.line) {
return;
if (!isNewExpressionWithParentheses(newExpression, sourceCode)) {
yield fixer.insertTextAfter(newExpression, '()');
}
yield fixer.insertTextAfter(returnToken, ' (');
yield fixer.insertTextAfter(newExpression, ')');
}
function * switchNewExpressionToCallExpression(node, sourceCode, fixer) {
const [start] = node.range;
let end = start + 3; // `3` = length of `new`
const textAfter = sourceCode.text.slice(end);
const [leadingSpaces] = textAfter.match(/^\s*/);
end += leadingSpaces.length;
yield fixer.removeRange([start, end]);
if (!isNewExpressionWithParentheses(node, sourceCode)) {
yield fixer.insertTextAfter(node, '()');
}
/*

@@ -51,5 +27,9 @@ Remove `new` from this code will makes the function return `undefined`

*/
yield * fixReturnStatementArgument(node, sourceCode, fixer);
if (!isOnSameLine(newToken, newExpression.callee) && !isParenthesized(newExpression, sourceCode)) {
// Ideally, we should use first parenthesis of the `callee`, and should check spaces after the `new` token
// But adding extra parentheses is harmless, no need to be too complicated
yield * addParenthesizesToReturnOrThrowExpression(fixer, newExpression.parent, sourceCode);
}
}
module.exports = switchNewExpressionToCallExpression;
'use strict';
const {defaultsDeep} = require('lodash');
const {getStringIfConstant} = require('eslint-utils');
const eslintTemplateVisitor = require('eslint-template-visitor');
const {callExpressionSelector} = require('./selectors/index.js');
const {getStringIfConstant} = require('@eslint-community/eslint-utils');
const {isCallExpression} = require('./ast/index.js');
const MESSAGE_ID = 'importStyle';
const messages = {
[MESSAGE_ID]: 'Use {{allowedStyles}} import for module `{{moduleName}}`.'
[MESSAGE_ID]: 'Use {{allowedStyles}} import for module `{{moduleName}}`.',
};

@@ -103,54 +102,26 @@

// An exotic custom parser or a bug in one could cover it too.
/* istanbul ignore next */
/* c8 ignore next */
return [];
};
const joinOr = words => {
return 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
const defaultStyles = {
chalk: {
default: true
default: true,
},
path: {
default: true
default: true,
},
util: {
named: true
}
named: true,
},
};
const templates = eslintTemplateVisitor({
parserOptions: {
sourceType: 'module',
ecmaVersion: 2018
}
});
const variableDeclarationVariable = templates.variableDeclarationVariable();
const assignmentTargetVariable = templates.variable();
const moduleNameVariable = templates.variable();
const assignedDynamicImportTemplate = templates.template`async () => {
${variableDeclarationVariable} ${assignmentTargetVariable} = await import(${moduleNameVariable});
}`.narrow('BlockStatement > :has(AwaitExpression)');
const assignedRequireTemplate = templates.template`
${variableDeclarationVariable} ${assignmentTargetVariable} = require(${moduleNameVariable});
`;
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {

@@ -164,9 +135,9 @@ let [

checkExportFrom = false,
checkRequire = true
} = {}
checkRequire = true,
} = {},
] = context.options;
styles = extendDefaultStyles ?
defaultsDeep({}, styles, defaultStyles) :
styles;
styles = extendDefaultStyles
? defaultsDeep({}, styles, defaultStyles)
: styles;

@@ -176,6 +147,8 @@ styles = new Map(

([moduleName, styles]) =>
[moduleName, new Set(Object.entries(styles).filter(([, isAllowed]) => isAllowed).map(([style]) => style))]
)
[moduleName, new Set(Object.entries(styles).filter(([, isAllowed]) => isAllowed).map(([style]) => style))],
),
);
const {sourceCode} = context;
const report = (node, moduleName, actualImportStyles, allowedImportStyles, isRequire = false) => {

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

// whether `'x'` is a compiled ES6 module (with `default` key) or a CommonJS module and `require`
// does not provide any automatic interop for this, so the user may have to use either of theese.
// does not provide any automatic interop for this, so the user may have to use either of these.
if (isRequire && allowedImportStyles.has('default') && !allowedImportStyles.has('namespace')) {

@@ -203,4 +176,4 @@ effectiveAllowedImportStyles = new Set(allowedImportStyles);

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

@@ -211,161 +184,177 @@

messageId: MESSAGE_ID,
data
data,
});
};
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);
});
[assignedDynamicImportTemplate](node) {
const assignmentTargetNode = assignedDynamicImportTemplate.context.getMatch(assignmentTargetVariable);
const moduleNameNode = assignedDynamicImportTemplate.context.getMatch(moduleNameVariable);
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', length: 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);
});
[assignedRequireTemplate](node) {
const assignmentTargetNode = assignedRequireTemplate.context.getMatch(assignmentTargetVariable);
const moduleNameNode = assignedRequireTemplate.context.getMatch(moduleNameVariable);
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 templates.visitor(visitor);
};
const schema = [
{
type: 'object',
properties: {
checkImport: {
type: 'boolean'
const schema = {
type: 'array',
additionalItems: false,
items: [
{
type: 'object',
additionalProperties: false,
properties: {
checkImport: {
type: 'boolean',
},
checkDynamicImport: {
type: 'boolean',
},
checkExportFrom: {
type: 'boolean',
},
checkRequire: {
type: 'boolean',
},
extendDefaultStyles: {
type: 'boolean',
},
styles: {
$ref: '#/definitions/moduleStyles',
},
},
checkDynamicImport: {
type: 'boolean'
},
],
definitions: {
moduleStyles: {
type: 'object',
additionalProperties: {
$ref: '#/definitions/styles',
},
checkExportFrom: {
type: 'boolean'
},
styles: {
anyOf: [
{
enum: [
false,
],
},
{
$ref: '#/definitions/booleanObject',
},
],
},
booleanObject: {
type: 'object',
additionalProperties: {
type: 'boolean',
},
checkRequire: {
type: 'boolean'
},
extendDefaultStyles: {
type: 'boolean'
},
styles: {
$ref: '#/items/0/definitions/moduleStyles'
}
},
additionalProperties: false,
definitions: {
moduleStyles: {
type: 'object',
additionalProperties: {
$ref: '#/items/0/definitions/styles'
}
},
styles: {
anyOf: [
{
enum: [
false
]
},
{
$ref: '#/items/0/definitions/booleanObject'
}
]
},
booleanObject: {
type: 'object',
additionalProperties: {
type: 'boolean'
}
}
}
}
];
},
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -376,7 +365,7 @@ create,

docs: {
description: 'Enforce specific import styles per module.'
description: 'Enforce specific import styles per module.',
},
schema,
messages
}
messages,
},
};
'use strict';
const {GlobalReferenceTracker} = require('./utils/global-reference-tracker.js');
const builtins = require('./utils/builtins.js');
const isShadowed = require('./utils/is-shadowed.js');
const {callExpressionSelector, newExpressionSelector} = require('./selectors/index.js');
const {switchNewExpressionToCallExpression} = require('./fix/index.js');
const {
switchCallExpressionToNewExpression,
switchNewExpressionToCallExpression,
} = require('./fix/index.js');
const messages = {
enforce: 'Use `new {{name}}()` instead of `{{name}}()`.',
disallow: 'Use `{{name}}()` instead of `new {{name}}()`.'
disallow: 'Use `{{name}}()` instead of `new {{name}}()`.',
};
const create = context => {
const sourceCode = context.getSourceCode();
function enforceNewExpression({node, path: [name]}, sourceCode) {
if (name === 'Object') {
const {parent} = node;
if (
parent.type === 'BinaryExpression'
&& (parent.operator === '===' || parent.operator === '!==')
&& (parent.left === node || parent.right === node)
) {
return;
}
}
return {
[callExpressionSelector(builtins.enforceNew)]: node => {
const {callee, parent} = node;
if (isShadowed(context.getScope(), callee)) {
return;
}
node,
messageId: 'enforce',
data: {name},
fix: fixer => switchCallExpressionToNewExpression(node, sourceCode, fixer),
};
}
const {name} = callee;
function enforceCallExpression({node, path: [name]}, sourceCode) {
const problem = {
node,
messageId: 'disallow',
data: {name},
};
if (
name === 'Object' &&
parent &&
parent.type === 'BinaryExpression' &&
(parent.operator === '===' || parent.operator === '!==') &&
(parent.left === node || parent.right === node)
) {
return;
}
if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
problem.fix = function * (fixer) {
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
};
}
return {
node,
messageId: 'enforce',
data: {name},
fix: fixer => fixer.insertTextBefore(node, 'new ')
};
},
[newExpressionSelector(builtins.disallowNew)]: node => {
const {callee} = node;
return problem;
}
if (isShadowed(context.getScope(), callee)) {
return;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {sourceCode} = context;
const newExpressionTracker = new GlobalReferenceTracker({
objects: builtins.disallowNew,
type: GlobalReferenceTracker.CONSTRUCT,
handle: reference => enforceCallExpression(reference, sourceCode),
});
const callExpressionTracker = new GlobalReferenceTracker({
objects: builtins.enforceNew,
type: GlobalReferenceTracker.CALL,
handle: reference => enforceNewExpression(reference, sourceCode),
});
const {name} = callee;
const problem = {
node,
messageId: 'disallow',
data: {name}
};
return {
* 'Program:exit'(program) {
const scope = sourceCode.getScope(program);
if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
problem.fix = function * (fixer) {
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
};
}
return problem;
}
yield * newExpressionTracker.track(scope);
yield * callExpressionTracker.track(scope);
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -71,7 +80,7 @@ create,

docs: {
description: 'Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`.'
description: 'Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`.',
},
fixable: 'code',
messages
}
messages,
},
};

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

const messages = {
[MESSAGE_ID]: 'Specify the rules you want to disable.'
[MESSAGE_ID]: 'Specify the rules you want to disable.',
};

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

/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({

@@ -19,4 +20,4 @@ * Program(node) {

if (
result && // It's a eslint-disable comment
!result.groups.ruleId // But it did not specify any rules
result // It's a eslint-disable comment
&& !result.groups.ruleId // But it did not specify any rules
) {

@@ -29,13 +30,14 @@ yield {

...comment.loc.start,
column: -1
column: -1,
},
end: comment.loc.end
end: comment.loc.end,
},
messageId: MESSAGE_ID
messageId: MESSAGE_ID,
};
}
}
}
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -46,6 +48,6 @@ create,

docs: {
description: 'Enforce specifying rules to disable in `eslint-disable` comments.'
description: 'Enforce specifying rules to disable in `eslint-disable` comments.',
},
messages
}
messages,
},
};
'use strict';
const {isParenthesized} = require('eslint-utils');
const {methodCallSelector, notFunctionSelector} = require('./selectors/index.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const {isParenthesized} = require('@eslint-community/eslint-utils');
const {isMethodCall} = require('./ast/index.js');
const {isNodeMatches, isNodeValueNotFunction} = require('./utils/index.js');

@@ -14,58 +14,127 @@ const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';

[REPLACE_WITH_NAME_MESSAGE_ID]: 'Replace function `{{name}}` with `… => {{name}}({{parameters}})`.',
[REPLACE_WITHOUT_NAME_MESSAGE_ID]: 'Replace function with `… => …({{parameters}})`.'
[REPLACE_WITHOUT_NAME_MESSAGE_ID]: 'Replace function with `… => …({{parameters}})`.',
};
const iteratorMethods = [
['every'],
[
'filter', {
extraSelector: '[callee.object.name!="Vue"]'
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;
}
],
['find'],
['findIndex'],
['flatMap'],
[
'forEach', {
returnsUndefined: true
if (isNodeMatches(node.callee.object, ignoredCallee)) {
return false;
}
],
['map'],
[
'reduce', {
parameters: [
'accumulator',
'element',
'index',
'array'
],
minParameters: 2,
ignore: []
if (node.callee.object.type === 'CallExpression' && isNodeMatches(node.callee.object.callee, ignoredCallee)) {
return false;
}
],
[
'reduceRight', {
parameters: [
'accumulator',
'element',
'index',
'array'
],
minParameters: 2,
ignore: []
const [callback] = node.arguments;
if (callback.type === 'Identifier' && ignore.includes(callback.name)) {
return false;
}
],
['some']
].map(([method, options]) => {
options = {
parameters: ['element', 'index', 'array'],
ignore: ['Boolean'],
minParameters: 1,
extraSelector: '',
returnsUndefined: false,
...options
};
return [method, options];
});
return !test || test(node);
},
}]));
const ignoredCallee = [

@@ -81,3 +150,5 @@ // http://bluebirdjs.com/docs/api/promise.map.html

'async',
'this'
'this',
'$',
'jQuery',
];

@@ -90,6 +161,2 @@

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

@@ -100,5 +167,5 @@ node,

name,
method
method,
},
suggest: []
suggest: [],
};

@@ -114,6 +181,6 @@

name,
parameters: suggestionParameters
parameters: suggestionParameters,
},
fix: fixer => {
const sourceCode = context.getSourceCode();
fix(fixer) {
const {sourceCode} = context;
let nodeText = sourceCode.getText(node);

@@ -126,7 +193,7 @@ if (isParenthesized(node, sourceCode) || type === 'ConditionalExpression') {

node,
returnsUndefined ?
`(${suggestionParameters}) => { ${nodeText}(${suggestionParameters}); }` :
`(${suggestionParameters}) => ${nodeText}(${suggestionParameters})`
returnsUndefined
? `(${suggestionParameters}) => { ${nodeText}(${suggestionParameters}); }`
: `(${suggestionParameters}) => ${nodeText}(${suggestionParameters})`,
);
}
},
};

@@ -140,39 +207,47 @@

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

@@ -183,7 +258,7 @@ create,

docs: {
description: 'Prevent passing a function reference directly to iterator methods.'
description: 'Prevent passing a function reference directly to iterator methods.',
},
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {
isParenthesized,
isArrowToken,
isCommaToken,
isSemicolonToken,
isClosingParenToken,
findVariable
} = require('eslint-utils');
const {methodCallSelector, referenceIdentifierSelector} = require('./selectors/index.js');
findVariable,
hasSideEffect,
} = require('@eslint-community/eslint-utils');
const {extendFixRange} = require('./fix/index.js');
const needsSemicolon = require('./utils/needs-semicolon.js');
const shouldAddParenthesesToExpressionStatementExpression = require('./utils/should-add-parentheses-to-expression-statement-expression.js');
const {getParentheses} = require('./utils/parentheses.js');
const extendFixRange = require('./utils/extend-fix-range.js');
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
const {getParentheses, getParenthesizedRange} = require('./utils/parentheses.js');
const isFunctionSelfUsedInside = require('./utils/is-function-self-used-inside.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const assertToken = require('./utils/assert-token.js');
const {fixSpaceAroundKeyword, removeParentheses} = require('./fix/index.js');
const {isArrowFunctionBody, isMethodCall, isReferenceIdentifier, functionTypes} = require('./ast/index.js');
const MESSAGE_ID = 'no-array-for-each';
const MESSAGE_ID_ERROR = 'no-array-for-each/error';
const MESSAGE_ID_SUGGESTION = 'no-array-for-each/suggestion';
const messages = {
[MESSAGE_ID]: 'Use `for…of` instead of `Array#forEach(…)`.'
[MESSAGE_ID_ERROR]: 'Use `for…of` instead of `.forEach(…)`.',
[MESSAGE_ID_SUGGESTION]: 'Switch to `for…of`.',
};
const arrayForEachCallSelector = methodCallSelector({
name: 'forEach',
includeOptionalCall: true,
includeOptionalMember: true
});
const continueAbleNodeTypes = new Set([

@@ -35,5 +33,10 @@ 'WhileStatement',

'ForOfStatement',
'ForInStatement'
'ForInStatement',
]);
const stripChainExpression = node =>
(node.parent.type === 'ChainExpression' && node.parent.expression === node)
? node.parent
: node;
function isReturnStatementInContinueAbleNodes(returnStatement, callbackFunction) {

@@ -53,4 +56,5 @@ for (let node = returnStatement; node && node !== callbackFunction; node = node.parent) {

switch (parent.type) {
case 'IfStatement':
case 'IfStatement': {
return parent.consequent === returnStatement || parent.alternate === returnStatement;
}

@@ -63,7 +67,9 @@ // These parent's body need switch to `BlockStatement` too, but since they are "continueAble", won't fix

// case 'DoWhileStatement':
case 'WithStatement':
case 'WithStatement': {
return parent.body === returnStatement;
}
default:
default: {
return false;
}
}

@@ -73,26 +79,33 @@ }

function getFixFunction(callExpression, functionInfo, context) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const [callback] = callExpression.arguments;
const parameters = callback.params;
const array = callExpression.callee.object;
const iterableObject = callExpression.callee.object;
const {returnStatements} = functionInfo.get(callback);
const isOptionalObject = callExpression.callee.optional;
const ancestor = stripChainExpression(callExpression).parent;
const objectText = sourceCode.getText(iterableObject);
const getForOfLoopHeadText = () => {
const [elementText, indexText] = parameters.map(parameter => sourceCode.getText(parameter));
const useEntries = parameters.length === 2;
const shouldUseEntries = parameters.length === 2;
let text = 'for (';
text += isFunctionParameterVariableReassigned(callback, context) ? 'let' : 'const';
text += isFunctionParameterVariableReassigned(callback, sourceCode) ? 'let' : 'const';
text += ' ';
text += useEntries ? `[${indexText}, ${elementText}]` : elementText;
text += shouldUseEntries ? `[${indexText}, ${elementText}]` : elementText;
text += ' of ';
let arrayText = sourceCode.getText(array);
if (isParenthesized(array, sourceCode)) {
arrayText = `(${arrayText})`;
}
const shouldAddParenthesesToObject
= isParenthesized(iterableObject, sourceCode)
|| (
// `1?.forEach()` -> `(1).entries()`
isOptionalObject
&& shouldUseEntries
&& shouldAddParenthesesToMemberExpressionObject(iterableObject, sourceCode)
);
text += arrayText;
text += shouldAddParenthesesToObject ? `(${objectText})` : objectText;
if (useEntries) {
if (shouldUseEntries) {
text += '.entries()';

@@ -108,13 +121,3 @@ }

const [start] = callExpression.range;
let end;
if (callback.body.type === 'BlockStatement') {
end = callback.body.range[0];
} else {
// In this case, parentheses are not included in body location, so we look for `=>` token
// foo.forEach(bar => ({bar}))
// ^
const arrowToken = sourceCode.getTokenBefore(callback.body, isArrowToken);
end = arrowToken.range[1];
}
const [end] = getParenthesizedRange(callback.body, sourceCode);
return [start, end];

@@ -127,3 +130,3 @@ };

expected: 'return',
ruleId: 'no-array-for-each'
ruleId: 'no-array-for-each',
});

@@ -143,5 +146,5 @@

let textAfter = '';
const shouldAddParentheses =
!isParenthesized(returnStatement.argument, sourceCode) &&
shouldAddParenthesesToExpressionStatementExpression(returnStatement.argument);
const shouldAddParentheses
= !isParenthesized(returnStatement.argument, sourceCode)
&& shouldAddParenthesesToExpressionStatementExpression(returnStatement.argument);
if (shouldAddParentheses) {

@@ -202,5 +205,10 @@ textBefore = `(${textBefore}`;

return function * (fixer) {
// `(( foo.forEach(bar => bar) ))`
yield * removeParentheses(callExpression, fixer, sourceCode);
// Replace these with `for (const … of …) `
// foo.forEach(bar => bar)
// ^^^^^^^^^^^^^^^^^^ (space after `=>` didn't included)
// ^^^^^^^^^^^^^^^^^^^^^^
// foo.forEach(bar => (bar))
// ^^^^^^^^^^^^^^^^^^^^^^
// foo.forEach(bar => {})

@@ -219,3 +227,3 @@ // ^^^^^^^^^^^^^^^^^^^^^^

penultimateToken,
lastToken
lastToken,
] = sourceCode.getLastTokens(callExpression, 2);

@@ -239,10 +247,21 @@

const expressionStatementLastToken = sourceCode.getLastToken(callExpression.parent);
// Remove semicolon if it's not needed anymore
// foo.forEach(bar => {});
// ^
if (shouldRemoveExpressionStatementLastToken(expressionStatementLastToken)) {
yield fixer.remove(expressionStatementLastToken, fixer);
if (ancestor.type === 'ExpressionStatement') {
const expressionStatementLastToken = sourceCode.getLastToken(ancestor);
// Remove semicolon if it's not needed anymore
// foo.forEach(bar => {});
// ^
if (shouldRemoveExpressionStatementLastToken(expressionStatementLastToken)) {
yield fixer.remove(expressionStatementLastToken, fixer);
}
} else if (ancestor.type === 'ArrowFunctionExpression') {
yield fixer.insertTextBefore(callExpression, '{ ');
yield fixer.insertTextAfter(callExpression, ' }');
}
yield * fixSpaceAroundKeyword(fixer, callExpression.parent, sourceCode);
if (isOptionalObject) {
yield fixer.insertTextBefore(callExpression, `if (${objectText}) `);
}
// Prevent possible variable conflicts

@@ -263,4 +282,4 @@ yield * extendFixRange(fixer, callExpression.parent.range);

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

@@ -278,9 +297,9 @@ for (const variable of variables) {

const variableName = definition.name.name;
const [arrayStart, arrayEnd] = array.range;
const [callExpressionStart, callExpressionEnd] = callExpression.range;
for (const identifier of allIdentifiers) {
const {name, range: [start, end]} = identifier;
if (
name !== variableName ||
start < arrayStart ||
end > arrayEnd
name !== variableName
|| start < callExpressionStart
|| end > callExpressionEnd
) {

@@ -300,38 +319,25 @@ continue;

function isFunctionParameterVariableReassigned(callbackFunction, context) {
return context.getDeclaredVariables(callbackFunction)
function isFunctionParameterVariableReassigned(callbackFunction, sourceCode) {
return sourceCode.getDeclaredVariables(callbackFunction)
.filter(variable => variable.defs[0].type === 'Parameter')
.some(variable => {
const {references} = variable;
return references.some(reference => {
const node = reference.identifier;
const {parent} = node;
return parent.type === 'UpdateExpression' ||
(parent.type === 'AssignmentExpression' && parent.left === node);
});
});
.some(variable =>
variable.references.some(reference => !reference.init && reference.isWrite()),
);
}
function isFixable(callExpression, {scope, functionInfo, allIdentifiers, context}) {
const sourceCode = context.getSourceCode();
function isFixable(callExpression, {scope, functionInfo, allIdentifiers, sourceCode}) {
// Check `CallExpression`
if (
callExpression.optional ||
isParenthesized(callExpression, sourceCode) ||
callExpression.arguments.length !== 1
) {
if (callExpression.optional || callExpression.arguments.length !== 1) {
return false;
}
// Check `CallExpression.parent`
if (callExpression.parent.type !== 'ExpressionStatement') {
// Check ancestors, we only fix `ExpressionStatement`
const callOrChainExpression = stripChainExpression(callExpression);
if (
callOrChainExpression.parent.type !== 'ExpressionStatement'
&& !isArrowFunctionBody(callOrChainExpression)
) {
return false;
}
// Check `CallExpression.callee`
/* istanbul ignore next: Because of `ChainExpression` wrapper, `foo?.forEach()` is already failed on previous check, keep this just for safety */
if (callExpression.callee.optional) {
return false;
}
// Check `CallExpression.arguments[0]`;

@@ -341,5 +347,5 @@ const [callback] = callExpression.arguments;

// Leave non-function type to `no-array-callback-reference` rule
(callback.type !== 'FunctionExpression' && callback.type !== 'ArrowFunctionExpression') ||
callback.async ||
callback.generator
(callback.type !== 'FunctionExpression' && callback.type !== 'ArrowFunctionExpression')
|| callback.async
|| callback.generator
) {

@@ -352,5 +358,9 @@ return false;

if (
!(parameters.length === 1 || parameters.length === 2) ||
parameters.some(({type, typeAnnotation}) => type === 'RestElement' || typeAnnotation) ||
!isFunctionParametersSafeToFix(callback, {scope, array: callExpression, allIdentifiers, context})
!(parameters.length === 1 || parameters.length === 2)
// `array.forEach((element = defaultValue) => {})`
|| (parameters.length === 1 && parameters[0].type === 'AssignmentPattern')
// https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1814
|| (parameters.length === 2 && parameters[1].type !== 'Identifier')
|| parameters.some(({type, typeAnnotation}) => type === 'RestElement' || typeAnnotation)
|| !isFunctionParametersSafeToFix(callback, {scope, callExpression, allIdentifiers, sourceCode})
) {

@@ -375,5 +385,9 @@ return false;

'React.Children',
'Children'
'Children',
'R',
// https://www.npmjs.com/package/p-iteration
'pIteration',
];
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {

@@ -384,49 +398,82 @@ const functionStack = [];

const functionInfo = new Map();
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);
},
[arrayForEachCallSelector](node) {
if (isNodeMatches(node.callee.object, ignoredObjects)) {
return;
}
}
});
callExpressions.push({
node,
scope: context.getScope()
});
},
* 'Program:exit'() {
for (const {node, scope} of callExpressions) {
const problem = {
node: node.callee.property,
messageId: MESSAGE_ID
};
context.on('ReturnStatement', node => {
const currentFunction = functionStack[functionStack.length - 1];
if (!currentFunction) {
return;
}
if (isFixable(node, {scope, allIdentifiers, functionInfo, context})) {
problem.fix = getFixFunction(node, functionInfo, context);
}
const {returnStatements} = functionInfo.get(currentFunction);
returnStatements.push(node);
});
context.on('CallExpression', node => {
if (
!isMethodCall(node, {
method: 'forEach',
})
|| isNodeMatches(node.callee.object, ignoredObjects)
) {
return;
}
callExpressions.push({
node,
scope: sourceCode.getScope(node),
});
});
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;
}
};
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -437,7 +484,8 @@ create,

docs: {
description: 'Prefer `for…of` over `Array#forEach(…)`.'
description: 'Prefer `for…of` over the `forEach` method.',
},
fixable: 'code',
messages
}
hasSuggestions: true,
messages,
},
};
'use strict';
const {hasSideEffect} = require('eslint-utils');
const {methodCallSelector, notFunctionSelector} = require('./selectors/index.js');
const {hasSideEffect} = require('@eslint-community/eslint-utils');
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');

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

[SUGGESTION_REMOVE]: 'Remove the second argument.',
[SUGGESTION_BIND]: 'Use a bound function.'
[SUGGESTION_BIND]: 'Use a bound function.',
};

@@ -29,2 +30,3 @@

'Vue.filter',
'R.filter',

@@ -34,7 +36,19 @@ 'lodash.find',

'underscore.find',
'R.find',
'lodash.findLast',
'_.findLast',
'underscore.findLast',
'R.findLast',
'lodash.findIndex',
'_.findIndex',
'underscore.findIndex',
'R.findIndex',
'lodash.findLastIndex',
'_.findLastIndex',
'underscore.findLastIndex',
'R.findLastIndex',
'lodash.flatMap',

@@ -47,2 +61,3 @@ '_.flatMap',

'Children.forEach',
'R.forEach',

@@ -56,25 +71,9 @@ 'lodash.map',

'$.map',
'R.map',
'lodash.some',
'_.some',
'underscore.some'
'underscore.some',
];
const selector = [
methodCallSelector({
names: [
'every',
'filter',
'find',
'findIndex',
'flatMap',
'forEach',
'map',
'some'
],
length: 2
}),
notFunctionSelector('arguments.0')
].join('');
function removeThisArgument(callExpression, sourceCode) {

@@ -92,8 +91,8 @@ return fixer => removeArgument(fixer, callExpression.arguments[1], sourceCode);

const isParenthesized = callbackParentheses.length > 0;
const callbackLastToken = isParenthesized ?
callbackParentheses[callbackParentheses.length - 1] :
callback;
const callbackLastToken = isParenthesized
? callbackParentheses[callbackParentheses.length - 1]
: callback;
if (
!isParenthesized &&
shouldAddParenthesesToMemberExpressionObject(callback, sourceCode)
!isParenthesized
&& shouldAddParenthesesToMemberExpressionObject(callback, sourceCode)
) {

@@ -112,11 +111,31 @@ yield fixer.insertTextBefore(callbackLastToken, '(');

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;

@@ -128,3 +147,3 @@ const [callback, thisArgument] = callExpression.arguments;

messageId: ERROR,
data: {method}
data: {method},
};

@@ -140,4 +159,4 @@

messageId: SUGGESTION_REMOVE,
fix: removeThisArgument(callExpression, sourceCode)
}
fix: removeThisArgument(callExpression, sourceCode),
},
];

@@ -154,15 +173,16 @@ } else {

messageId: SUGGESTION_REMOVE,
fix: removeThisArgument(callExpression, sourceCode)
fix: removeThisArgument(callExpression, sourceCode),
},
{
messageId: SUGGESTION_BIND,
fix: useBoundFunction(callExpression, sourceCode)
}
fix: useBoundFunction(callExpression, sourceCode),
},
];
return problem;
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -173,8 +193,8 @@ create,

docs: {
description: 'Disallow using the `this` argument in array methods.'
description: 'Disallow using the `this` argument in array methods.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {hasSideEffect, isCommaToken, isSemicolonToken} = require('eslint-utils');
const {methodCallSelector} = require('./selectors/index.js');
const {hasSideEffect, isCommaToken, isSemicolonToken} = require('@eslint-community/eslint-utils');
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');

@@ -12,29 +13,20 @@ const ERROR = 'error';

[ERROR]: 'Do not call `Array#push()` multiple times.',
[SUGGESTION]: 'Merge with previous one.'
[SUGGESTION]: 'Merge with previous one.',
};
const arrayPushExpressionStatement = [
'ExpressionStatement',
methodCallSelector({path: 'expression', name: '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;
}
/* istanbul 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');
}

@@ -45,10 +37,21 @@

ignore: [],
...context.options[0]
...context.options[0],
};
const ignoredObjects = ['stream', 'this', 'this.stream', ...ignore];
const sourceCode = context.getSourceCode();
const ignoredObjects = [
'stream',
'this',
'this.stream',
'process.stdin',
'process.stdout',
'process.stderr',
...ignore,
];
const {sourceCode} = context;
return {
[selector](secondExpression) {
const secondCall = secondExpression.expression;
CallExpression(secondCall) {
if (!isArrayPushCall(secondCall)) {
return;
}
const secondCallArray = secondCall.callee.object;

@@ -60,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;

@@ -73,3 +79,3 @@

node: secondCall.callee.property,
messageId: ERROR
messageId: ERROR,
};

@@ -83,14 +89,16 @@

yield (
isCommaToken(penultimateToken) ?
fixer.insertTextAfter(penultimateToken, ` ${text}`) :
fixer.insertTextBefore(lastToken, firstCall.arguments.length > 0 ? `, ${text}` : text)
isCommaToken(penultimateToken)
? fixer.insertTextAfter(penultimateToken, ` ${text}`)
: fixer.insertTextBefore(lastToken, firstCall.arguments.length > 0 ? `, ${text}` : text)
);
}
const shouldKeepSemicolon = !isSemicolonToken(sourceCode.getLastToken(firstExpression)) &&
isSemicolonToken(sourceCode.getLastToken(secondExpression));
const firstExpression = firstCall.parent;
const secondExpression = secondCall.parent;
const shouldKeepSemicolon = !isSemicolonToken(sourceCode.getLastToken(firstExpression))
&& isSemicolonToken(sourceCode.getLastToken(secondExpression));
yield fixer.replaceTextRange(
[firstExpression.range[1], secondExpression.range[1]],
shouldKeepSemicolon ? ';' : ''
shouldKeepSemicolon ? ';' : '',
);

@@ -103,4 +111,4 @@ };

messageId: SUGGESTION,
fix
}
fix,
},
];

@@ -112,3 +120,3 @@ } else {

return problem;
}
},
};

@@ -120,12 +128,13 @@ }

type: 'object',
additionalProperties: false,
properties: {
ignore: {
type: 'array',
uniqueItems: true
}
uniqueItems: true,
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -136,9 +145,9 @@ create,

docs: {
description: 'Enforce combining multiple `Array#push()` into one call.'
description: 'Enforce combining multiple `Array#push()` into one call.',
},
fixable: 'code',
hasSuggestions: true,
schema,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {arrayPrototypeMethodSelector, notFunctionSelector, matches} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const {isNodeValueNotFunction, isArrayPrototypeProperty} = require('./utils/index.js');
const MESSAGE_ID = 'no-reduce';
const messages = {
[MESSAGE_ID]: '`Array#{{method}}()` is not allowed'
[MESSAGE_ID]: '`Array#{{method}}()` is not allowed',
};
const prototypeSelector = method => [
methodCallSelector(method),
arrayPrototypeMethodSelector({
path: 'callee.object',
names: ['reduce', 'reduceRight']
})
].join('');
const selector = matches([
const cases = [
// `array.{reduce,reduceRight}()`
[
methodCallSelector({names: ['reduce', 'reduceRight'], min: 1, max: 2}),
notFunctionSelector('arguments.0'),
' > .callee > .property'
].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,
isSimpleOperation(callExpression) {
const [callback] = callExpression.arguments;
return (
callback
&& (
// `array.reduce((accumulator, element) => accumulator + element)`
(callback.type === 'ArrowFunctionExpression' && callback.body.type === 'BinaryExpression')
// `array.reduce((accumulator, element) => {return accumulator + element;})`
// `array.reduce(function (accumulator, element){return accumulator + element;})`
|| (
(callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')
&& callback.body.type === 'BlockStatement'
&& callback.body.body.length === 1
&& callback.body.body[0].type === 'ReturnStatement'
&& callback.body.body[0].argument.type === 'BinaryExpression'
)
)
);
},
},
// `[].{reduce,reduceRight}.call()` and `Array.{reduce,reduceRight}.call()`
[
prototypeSelector('call'),
notFunctionSelector('arguments.1'),
' > .callee > .object > .property'
].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,
},
// `[].{reduce,reduceRight}.apply()` and `Array.{reduce,reduceRight}.apply()`
[
prototypeSelector('apply'),
' > .callee > .object > .property'
].join('')
]);
{
test: callExpression =>
isMethodCall(callExpression, {
method: 'apply',
optionalCall: false,
optionalMember: false,
})
&& isArrayPrototypeProperty(callExpression.callee.object, {
properties: ['reduce', 'reduceRight'],
}),
getMethodNode: callExpression => callExpression.callee.object.property,
},
];
const create = () => {
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
allowSimpleOperations: {
type: 'boolean',
default: true,
},
},
},
];
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {allowSimpleOperations} = {allowSimpleOperations: true, ...context.options[0]};
return {
[selector](node) {
return {
node,
messageId: MESSAGE_ID,
data: {method: node.name}
};
}
* CallExpression(callExpression) {
for (const {test, getMethodNode, isSimpleOperation} of cases) {
if (!test(callExpression)) {
continue;
}
if (allowSimpleOperations && isSimpleOperation?.(callExpression)) {
continue;
}
const methodNode = getMethodNode(callExpression);
yield {
node: methodNode,
messageId: MESSAGE_ID,
data: {method: methodNode.name},
};
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -54,6 +121,7 @@ create,

docs: {
description: 'Disallow `Array#reduce()` and `Array#reduceRight()`.'
description: 'Disallow `Array#reduce()` and `Array#reduceRight()`.',
},
messages
}
schema,
messages,
},
};
'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const toLocation = require('./utils/to-location.js');
const {isStringLiteral, isMethodCall} = require('./ast/index.js');
const MESSAGE_ID = 'no-console-spaces';
const messages = {
[MESSAGE_ID]: 'Do not use {{position}} space between `console.{{method}}` parameters.'
[MESSAGE_ID]: 'Do not use {{position}} space between `console.{{method}}` parameters.',
};
const methods = [
'log',
'debug',
'info',
'warn',
'error'
];
const selector = methodCallSelector({
names: methods,
min: 1,
object: 'console'
});
// Find exactly one leading space, allow exactly one space

@@ -30,8 +16,9 @@ const hasLeadingSpace = value => value.length > 1 && value.charAt(0) === ' ' && value.charAt(1) !== ' ';

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const getProblem = (node, method, position) => {
const index = position === 'leading' ?
node.range[0] + 1 :
node.range[1] - 2;
const index = position === 'leading'
? node.range[0] + 1
: node.range[1] - 2;
const range = [index, index + 1];

@@ -43,3 +30,3 @@

data: {method, position},
fix: fixer => fixer.removeRange(range)
fix: fixer => fixer.removeRange(range),
};

@@ -49,3 +36,21 @@ };

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;

@@ -55,7 +60,3 @@ const {arguments: messages} = node;

for (const [index, node] of messages.entries()) {
const {type, value} = node;
if (
!(type === 'Literal' && typeof value === 'string') &&
type !== 'TemplateLiteral'
) {
if (!isStringLiteral(node) && node.type !== 'TemplateLiteral') {
continue;

@@ -74,6 +75,7 @@ }

}
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -84,7 +86,7 @@ create,

docs: {
description: 'Do not use leading/trailing space between `console.log` parameters.'
description: 'Do not use leading/trailing space between `console.log` parameters.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const getPropertyName = require('./utils/get-property-name.js');
const {GlobalReferenceTracker} = require('./utils/global-reference-tracker.js');
const MESSAGE_ID = 'no-document-cookie';
const messages = {
[MESSAGE_ID]: 'Do not use `document.cookie` directly.'
[MESSAGE_ID]: 'Do not use `document.cookie` directly.',
};
const selector = [
'AssignmentExpression',
'>',
'MemberExpression.left',
'[object.type="Identifier"]',
'[object.name="document"]'
].join('');
const tracker = new GlobalReferenceTracker({
object: 'document.cookie',
filter: ({node}) => node.parent.type === 'AssignmentExpression' && node.parent.left === node,
handle: ({node}) => ({node, messageId: MESSAGE_ID}),
});
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
return {
[selector](node) {
if (getPropertyName(node, context.getScope()) !== 'cookie') {
return;
}
return {
node,
messageId: MESSAGE_ID
};
}
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
create: context => tracker.createListeners(context),
meta: {
type: 'problem',
docs: {
description: 'Do not use `document.cookie` directly.'
description: 'Do not use `document.cookie` directly.',
},
messages
}
messages,
},
};
'use strict';
const {isClosingParenToken, getStaticValue} = require('eslint-utils');
const isLiteralValue = require('./utils/is-literal-value.js');
const {isClosingParenToken, getStaticValue} = require('@eslint-community/eslint-utils');
const avoidCapture = require('./utils/avoid-capture.js');
const getChildScopesRecursive = require('./utils/get-child-scopes-recursive.js');
const getScopes = require('./utils/get-scopes.js');
const singular = require('./utils/singular.js');
const toLocation = require('./utils/to-location.js');
const getReferences = require('./utils/get-references.js');
const {isLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'no-for-loop';
const messages = {
[MESSAGE_ID]: 'Use a `for-of` loop instead of this `for` loop.'
[MESSAGE_ID]: 'Use a `for-of` loop instead of this `for` loop.',
};
const defaultElementName = 'element';
const isLiteralZero = node => isLiteralValue(node, 0);
const isLiteralOne = node => isLiteralValue(node, 1);
const isLiteralZero = node => isLiteral(node, 0);
const isLiteralOne = node => isLiteral(node, 1);
const isIdentifierWithName = (node, name) => node && node.type === 'Identifier' && node.name === name;
const isIdentifierWithName = (node, name) => node?.type === 'Identifier' && node.name === name;

@@ -24,4 +25,4 @@ const getIndexIdentifierName = forStatement => {

if (
!variableDeclaration ||
variableDeclaration.type !== 'VariableDeclaration'
!variableDeclaration
|| variableDeclaration.type !== 'VariableDeclaration'
) {

@@ -52,3 +53,3 @@ return;

lesser: binaryExpression.left,
greater: binaryExpression.right
greater: binaryExpression.right,
};

@@ -60,3 +61,3 @@ }

lesser: binaryExpression.right,
greater: binaryExpression.left
greater: binaryExpression.left,
};

@@ -84,4 +85,4 @@ }

if (
greater.object.type !== 'Identifier' ||
greater.property.type !== 'Identifier'
greater.object.type !== 'Identifier'
|| greater.property.type !== 'Identifier'
) {

@@ -109,5 +110,5 @@ return;

const isLiteralOnePlusIdentifierWithName = (node, identifierName) => {
if (node && node.type === 'BinaryExpression' && node.operator === '+') {
return (isIdentifierWithName(node.left, identifierName) && isLiteralOne(node.right)) ||
(isIdentifierWithName(node.right, identifierName) && isLiteralOne(node.left));
if (node?.type === 'BinaryExpression' && node.operator === '+') {
return (isIdentifierWithName(node.left, identifierName) && isLiteralOne(node.right))
|| (isIdentifierWithName(node.right, identifierName) && isLiteralOne(node.left));
}

@@ -130,4 +131,4 @@

if (
update.type === 'AssignmentExpression' &&
isIdentifierWithName(update.left, indexIdentifierName)
update.type === 'AssignmentExpression'
&& isIdentifierWithName(update.left, indexIdentifierName)
) {

@@ -146,24 +147,22 @@ if (update.operator === '+=') {

const isOnlyArrayOfIndexVariableRead = (arrayReferences, indexIdentifierName) => {
return arrayReferences.every(reference => {
const node = reference.identifier.parent;
const isOnlyArrayOfIndexVariableRead = (arrayReferences, indexIdentifierName) => arrayReferences.every(reference => {
const node = reference.identifier.parent;
if (node.type !== 'MemberExpression') {
return false;
}
if (node.type !== 'MemberExpression') {
return false;
}
if (node.property.name !== indexIdentifierName) {
return false;
}
if (node.property.name !== indexIdentifierName) {
return false;
}
if (
node.parent.type === 'AssignmentExpression' &&
node.parent.left === node
) {
return false;
}
if (
node.parent.type === 'AssignmentExpression'
&& node.parent.left === node
) {
return false;
}
return true;
});
};
return true;
});

@@ -181,3 +180,3 @@ const getRemovalRange = (node, sourceCode) => {

sourceCode.getIndexFromLoc({line, column: 0}),
sourceCode.getIndexFromLoc({line: line + 1, column: 0})
sourceCode.getIndexFromLoc({line: line + 1, column: 0}),
] : declarationNode.range;

@@ -191,3 +190,3 @@ }

node.range[0],
declarationNode.declarations[1].range[0]
declarationNode.declarations[1].range[0],
];

@@ -198,3 +197,3 @@ }

declarationNode.declarations[index - 1].range[1],
node.range[1]
node.range[1],
];

@@ -259,27 +258,20 @@ };

const isIndexVariableAssignedToInTheLoopBody = (indexVariable, bodyScope) => {
return indexVariable.references
const isIndexVariableAssignedToInTheLoopBody = (indexVariable, bodyScope) =>
indexVariable.references
.filter(reference => scopeContains(bodyScope, reference.from))
.some(inBodyReference => inBodyReference.isWrite());
};
const someVariablesLeakOutOfTheLoop = (forStatement, variables, forScope) => {
return variables.some(variable => {
return !variable.references.every(reference => {
return scopeContains(forScope, reference.from) ||
nodeContains(forStatement, reference.identifier);
});
});
};
const someVariablesLeakOutOfTheLoop = (forStatement, variables, forScope) =>
variables.some(
variable => !variable.references.every(
reference => scopeContains(forScope, reference.from) || nodeContains(forStatement, reference.identifier),
),
);
const getReferencesInChildScopes = (scope, name) => {
const references = scope.references.filter(reference => reference.identifier.name === name);
return [
...references,
...scope.childScopes.flatMap(s => getReferencesInChildScopes(s, name))
];
};
const getReferencesInChildScopes = (scope, name) =>
getReferences(scope).filter(reference => reference.identifier.name === name);
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {scopeManager, text: sourceCodeText} = sourceCode;

@@ -302,3 +294,3 @@

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

@@ -346,3 +338,3 @@ if (staticResult && !Array.isArray(staticResult.value)) {

loc: toLocation([start, end], sourceCode),
messageId: MESSAGE_ID
messageId: MESSAGE_ID,
};

@@ -359,4 +351,4 @@

});
const elementNode = elementReference && elementReference.identifier.parent.parent;
const elementIdentifierName = elementNode && elementNode.id.name;
const elementNode = elementReference?.identifier.parent.parent;
const elementIdentifierName = elementNode?.id.name;
const elementVariable = elementIdentifierName && resolveIdentifierName(elementIdentifierName, bodyScope);

@@ -371,4 +363,4 @@

const index = indexIdentifierName;
const element = elementIdentifierName ||
avoidCapture(singular(arrayIdentifierName) || defaultElementName, getChildScopesRecursive(bodyScope), context.parserOptions.ecmaVersion);
const element = elementIdentifierName
|| avoidCapture(singular(arrayIdentifierName) || defaultElementName, getScopes(bodyScope));
const array = arrayIdentifierName;

@@ -392,3 +384,3 @@

elementNode.id.typeAnnotation,
-1 // Skip leading `:`
-1, // Skip leading `:`
).trim();

@@ -417,3 +409,3 @@ } else {

node.init.range[0],
node.update.range[1]
node.update.range[1],
], replacement);

@@ -428,5 +420,5 @@

if (elementNode) {
yield removeDeclaration ?
fixer.removeRange(getRemovalRange(elementNode, sourceCode)) :
fixer.replaceText(elementNode.init, element);
yield removeDeclaration
? fixer.removeRange(getRemovalRange(elementNode, sourceCode))
: fixer.replaceText(elementNode.init, element);
}

@@ -437,6 +429,7 @@ };

return problem;
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -447,8 +440,8 @@ create,

docs: {
description: 'Do not use a `for` loop that can be replaced with a `for-of` loop.'
description: 'Do not use a `for` loop that can be replaced with a `for-of` loop.',
},
fixable: 'code',
messages,
hasSuggestion: true
}
hasSuggestion: true,
},
};
'use strict';
const replaceTemplateElement = require('./utils/replace-template-element.js');
const {replaceTemplateElement} = require('./fix/index.js');
const {isStringLiteral, isRegexLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'no-hex-escape';
const messages = {
[MESSAGE_ID]: 'Use Unicode escapes instead of hexadecimal escapes.'
[MESSAGE_ID]: 'Use Unicode escapes instead of hexadecimal escapes.',
};

@@ -17,5 +18,5 @@

fix: fixer =>
node.type === 'TemplateElement' ?
replaceTemplateElement(fixer, node, fixedValue) :
fixer.replaceText(node, fixedValue)
node.type === 'TemplateElement'
? replaceTemplateElement(fixer, node, fixedValue)
: fixer.replaceText(node, fixedValue),
};

@@ -25,15 +26,13 @@ }

const create = context => {
return {
Literal: node => {
if (node.regex || typeof node.value === 'string') {
return checkEscape(context, node, node.raw);
}
},
TemplateElement: node => {
return checkEscape(context, node, node.value.raw);
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
Literal(node) {
if (isStringLiteral(node) || isRegexLiteral(node)) {
return checkEscape(context, node, node.raw);
}
};
};
},
TemplateElement: node => checkEscape(context, node, node.value.raw),
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -44,7 +43,7 @@ create,

docs: {
description: 'Enforce the use of Unicode escapes instead of hexadecimal escapes.'
description: 'Enforce the use of Unicode escapes instead of hexadecimal escapes.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isParenthesized, isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
const replaceNodeOrTokenAndSpacesBefore = require('./utils/replace-node-or-token-and-spaces-before.js');
const {checkVueTemplate} = require('./utils/rule.js');
const {getParenthesizedRange} = require('./utils/parentheses.js');
const {replaceNodeOrTokenAndSpacesBefore, fixSpaceAroundKeyword} = require('./fix/index.js');

@@ -9,49 +10,57 @@ const isInstanceofToken = token => token.value === 'instanceof' && token.type === 'Keyword';

const messages = {
[MESSAGE_ID]: 'Use `Array.isArray()` instead of `instanceof Array`.'
[MESSAGE_ID]: 'Use `Array.isArray()` instead of `instanceof Array`.',
};
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 => ({
node,
messageId: MESSAGE_ID,
* fix(fixer) {
const {left, right} = node;
BinaryExpression(node) {
if (!(
node.operator === 'instanceof'
&& node.right.type === 'Identifier'
&& node.right.name === 'Array'
)) {
return;
}
let leftStartNodeOrToken = left;
let leftEndNodeOrToken = left;
if (isParenthesized(left, sourceCode)) {
leftStartNodeOrToken = sourceCode.getTokenBefore(left, isOpeningParenToken);
leftEndNodeOrToken = sourceCode.getTokenAfter(left, isClosingParenToken);
}
const {left, right} = node;
let tokenStore = sourceCode;
let instanceofToken = tokenStore.getTokenAfter(left, isInstanceofToken);
if (!instanceofToken && sourceCode.parserServices.getTemplateBodyTokenStore) {
tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore();
instanceofToken = tokenStore.getTokenAfter(left, isInstanceofToken);
}
yield fixer.insertTextBefore(leftStartNodeOrToken, 'Array.isArray(');
yield fixer.insertTextAfter(leftEndNodeOrToken, ')');
return {
node: instanceofToken,
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
const instanceofToken = sourceCode.getTokenAfter(left, isInstanceofToken);
yield * replaceNodeOrTokenAndSpacesBefore(instanceofToken, '', fixer, sourceCode);
yield * replaceNodeOrTokenAndSpacesBefore(right, '', fixer, sourceCode);
}
})
const range = getParenthesizedRange(left, tokenStore);
yield fixer.insertTextBeforeRange(range, 'Array.isArray(');
yield fixer.insertTextAfterRange(range, ')');
yield * replaceNodeOrTokenAndSpacesBefore(instanceofToken, '', fixer, sourceCode, tokenStore);
yield * replaceNodeOrTokenAndSpacesBefore(right, '', fixer, sourceCode, tokenStore);
},
};
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
create: checkVueTemplate(create),
meta: {
type: 'suggestion',
docs: {
description: 'Require `Array.isArray()` instead of `instanceof Array`.'
description: 'Require `Array.isArray()` instead of `instanceof Array`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const isShorthandPropertyAssignmentPatternLeft = require('./utils/is-shorthand-property-assignment-pattern-left.js');
const MESSAGE_ID = 'noKeywordPrefix';
const messages = {
[MESSAGE_ID]: 'Do not prefix identifiers with keyword `{{keyword}}`.'
[MESSAGE_ID]: 'Do not prefix identifiers with keyword `{{keyword}}`.',
};

@@ -11,13 +12,11 @@

checkProperties = true,
onlyCamelCase = true
} = {}) => {
return {
disallowedPrefixes: (disallowedPrefixes || [
'new',
'class'
]),
checkProperties,
onlyCamelCase
};
};
onlyCamelCase = true,
} = {}) => ({
disallowedPrefixes: (disallowedPrefixes || [
'new',
'class',
]),
checkProperties,
onlyCamelCase,
});

@@ -44,6 +43,6 @@ function findKeywordPrefix(name, options) {

} else if (
effectiveParent.type === 'AssignmentExpression' &&
Boolean(keyword) &&
(effectiveParent.right.type !== 'MemberExpression' || effectiveParent.left.type === 'MemberExpression') &&
effectiveParent.left.property.name === name
effectiveParent.type === 'AssignmentExpression'
&& Boolean(keyword)
&& (effectiveParent.right.type !== 'MemberExpression' || effectiveParent.left.type === 'MemberExpression')
&& effectiveParent.left.property.name === name
) {

@@ -58,3 +57,3 @@ report(node, keyword);

/* istanbul ignore next: Can't find a case to cover this line */
/* c8 ignore next 3 */
if (parent.shorthand && parent.value.left && Boolean(keyword)) {

@@ -70,3 +69,3 @@ report(node, keyword);

// Prevent checking righthand side of destructured object
// Prevent checking right hand side of destructured object
if (parent.key === node && parent.value !== node) {

@@ -103,4 +102,4 @@ return true;

name: node.name,
keyword
}
keyword,
},
});

@@ -111,3 +110,3 @@ }

return {
Identifier: node => {
Identifier(node) {
const {name, parent} = node;

@@ -120,6 +119,6 @@ const keyword = findKeywordPrefix(name, options);

} else if (
parent.type === 'Property' ||
parent.type === 'AssignmentPattern'
parent.type === 'Property'
|| parent.type === 'AssignmentPattern'
) {
if (parent.parent && parent.parent.type === 'ObjectPattern') {
if (parent.parent.type === 'ObjectPattern') {
const finished = checkObjectPattern(report, node, options);

@@ -139,5 +138,6 @@ if (finished) {

if (
Boolean(keyword) &&
!ALLOWED_PARENT_TYPES.has(effectiveParent.type) &&
!(parent.right === node)
Boolean(keyword)
&& !ALLOWED_PARENT_TYPES.has(effectiveParent.type)
&& !(parent.right === node)
&& !isShorthandPropertyAssignmentPatternLeft(node)
) {

@@ -152,11 +152,7 @@ report(node, keyword);

'ImportNamespaceSpecifier',
'ImportDefaultSpecifier'
'ImportDefaultSpecifier',
].includes(parent.type)
) {
// Report only if the local imported identifier is invalid
if (
Boolean(keyword) &&
parent.local &&
parent.local.name === name
) {
if (Boolean(keyword) && parent.local?.name === name) {
report(node, keyword);

@@ -167,8 +163,8 @@ }

} else if (
Boolean(keyword) &&
!ALLOWED_PARENT_TYPES.has(effectiveParent.type)
Boolean(keyword)
&& !ALLOWED_PARENT_TYPES.has(effectiveParent.type)
) {
report(node, keyword);
}
}
},
};

@@ -180,2 +176,3 @@ };

type: 'object',
additionalProperties: false,
properties: {

@@ -186,19 +183,19 @@ disallowedPrefixes: {

{
type: 'string'
}
type: 'string',
},
],
minItems: 0,
uniqueItems: true
uniqueItems: true,
},
checkProperties: {
type: 'boolean'
type: 'boolean',
},
onlyCamelCase: {
type: 'boolean'
}
type: 'boolean',
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -209,7 +206,7 @@ create,

docs: {
description: 'Disallow identifiers starting with `new` or `class`.'
description: 'Disallow identifiers starting with `new` or `class`.',
},
schema,
messages
}
messages,
},
};
'use strict';
const {isParenthesized, isNotSemicolonToken} = require('eslint-utils');
const needsSemicolon = require('./utils/needs-semicolon.js');
const removeSpacesAfter = require('./utils/remove-spaces-after.js');
const {matches} = require('./selectors/index.js');
const {isParenthesized, isNotSemicolonToken} = require('@eslint-community/eslint-utils');
const {needsSemicolon} = require('./utils/index.js');
const {removeSpacesAfter} = require('./fix/index.js');
const MESSAGE_ID = 'no-lonely-if';
const messages = {
[MESSAGE_ID]: 'Unexpected `if` as the only statement in a `if` block without `else`.'
[MESSAGE_ID]: 'Unexpected `if` as the only statement in a `if` block without `else`.',
};
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
// Lower precedence than `&&`
const needParenthesis = node => (
(node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??')) ||
node.type === 'ConditionalExpression' ||
node.type === 'AssignmentExpression' ||
node.type === 'YieldExpression' ||
node.type === 'SequenceExpression'
(node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??'))
|| node.type === 'ConditionalExpression'
|| node.type === 'AssignmentExpression'
|| node.type === 'YieldExpression'
|| node.type === 'SequenceExpression'
);

@@ -58,13 +43,13 @@

const outerIfStatement = (
innerIfStatement.parent.type === 'BlockStatement' ?
innerIfStatement.parent :
innerIfStatement
innerIfStatement.parent.type === 'BlockStatement'
? innerIfStatement.parent
: innerIfStatement
).parent;
const outer = {
...outerIfStatement,
...getIfStatementTokens(outerIfStatement, sourceCode)
...getIfStatementTokens(outerIfStatement, sourceCode),
};
const inner = {
...innerIfStatement,
...getIfStatementTokens(innerIfStatement, sourceCode)
...getIfStatementTokens(innerIfStatement, sourceCode),
};

@@ -90,3 +75,3 @@

inner.closingParenthesisToken,
`)${inner.consequent.type === 'EmptyStatement' ? '' : ' '}`
`)${inner.consequent.type === 'EmptyStatement' ? '' : ' '}`,
);

@@ -100,4 +85,4 @@

if (
isParenthesized(test, sourceCode) ||
!needParenthesis(test)
isParenthesized(test, sourceCode)
|| !needParenthesis(test)
) {

@@ -118,3 +103,3 @@ yield fixer.remove(openingParenthesisToken);

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

@@ -127,16 +112,35 @@ }

const create = context => {
const sourceCode = context.getSourceCode();
return {
[selector](node) {
return {
node,
messageId: MESSAGE_ID,
fix: fix(node, sourceCode)
};
/** @param {import('eslint').Rule.RuleContext} context */
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 {
node: ifStatement,
messageId: MESSAGE_ID,
fix: fix(ifStatement, context.sourceCode),
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -147,7 +151,7 @@ create,

docs: {
description: 'Disallow `if` statements as the only statement in `if` blocks without `else`.'
description: 'Disallow `if` statements as the only statement in `if` blocks without `else`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isParenthesized} = require('eslint-utils');
const {isParenthesized} = require('@eslint-community/eslint-utils');

@@ -8,30 +8,42 @@ const MESSAGE_ID_TOO_DEEP = 'too-deep';

[MESSAGE_ID_TOO_DEEP]: 'Do not nest ternary expressions.',
[MESSAGE_ID_SHOULD_PARENTHESIZED]: 'Nest ternary expression should be parenthesized.'
[MESSAGE_ID_SHOULD_PARENTHESIZED]: 'Nest ternary expression should be parenthesized.',
};
const nestTernarySelector = level => `:not(ConditionalExpression)${' > ConditionalExpression'.repeat(level)}`;
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
ConditionalExpression(node) {
if ([
node.test,
node.consequent,
node.alternate,
].some(node => node.type === 'ConditionalExpression')) {
return;
}
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const ancestors = sourceCode.getAncestors(node).reverse();
const nestLevel = ancestors.findIndex(node => node.type !== 'ConditionalExpression');
return {
[nestTernarySelector(3)]: node => {
// Nesting more than one level not allowed.
return {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, ')')
]
};
}
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} */
module.exports = {

@@ -42,7 +54,7 @@ create,

docs: {
description: 'Disallow nested ternary expressions.'
description: 'Disallow nested ternary expressions.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isParenthesized, getStaticValue} = require('eslint-utils');
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');

@@ -14,10 +15,19 @@ const MESSAGE_ID_ERROR = 'error';

[MESSAGE_ID_ONLY_ELEMENT]: 'The argument is the only element of array.',
[MESSAGE_ID_SPREAD]: 'Spread the argument.'
[MESSAGE_ID_SPREAD]: 'Spread the argument.',
};
const newArraySelector = newExpressionSelector({name: 'Array', length: 1, allowSpreadElement: true});
function getProblem(context, node) {
if (
!isNewExpression(node, {
name: 'Array',
argumentsLength: 1,
allowSpreadElement: true,
})
) {
return;
}
const problem = {
node,
messageId: MESSAGE_ID_ERROR
messageId: MESSAGE_ID_ERROR,
};

@@ -27,3 +37,3 @@

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

@@ -34,5 +44,5 @@ if (isParenthesized(argumentNode, sourceCode)) {

const maybeSemiColon = needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[') ?
';' :
'';
const maybeSemiColon = needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[')
? ';'
: '';

@@ -44,4 +54,4 @@ // We are not sure how many `arguments` passed

messageId: MESSAGE_ID_SPREAD,
fix: fixer => fixer.replaceText(node, `${maybeSemiColon}[${text}]`)
}
fix: fixer => fixer.replaceText(node, `${maybeSemiColon}[${text}]`),
},
];

@@ -51,35 +61,38 @@ return problem;

const result = getStaticValue(argumentNode, context.getScope());
const fromLengthText = `Array.from(${text === 'length' ? '{length}' : `{length: ${text}}`})`;
const scope = sourceCode.getScope(node);
if (isNumber(argumentNode, scope)) {
problem.fix = fixer => fixer.replaceText(node, fromLengthText);
return problem;
}
const onlyElementText = `${maybeSemiColon}[${text}]`;
// We don't know the argument is number or not
if (result === null) {
problem.suggest = [
{
messageId: MESSAGE_ID_LENGTH,
fix: fixer => fixer.replaceText(node, fromLengthText)
},
{
messageId: MESSAGE_ID_ONLY_ELEMENT,
fix: fixer => fixer.replaceText(node, onlyElementText)
}
];
const result = getStaticValue(argumentNode, scope);
if (result !== null && typeof result.value !== 'number') {
problem.fix = fixer => fixer.replaceText(node, onlyElementText);
return problem;
}
problem.fix = fixer => fixer.replaceText(
node,
typeof result.value === 'number' ? fromLengthText : onlyElementText
);
// We don't know the argument is number or not
problem.suggest = [
{
messageId: MESSAGE_ID_LENGTH,
fix: fixer => fixer.replaceText(node, fromLengthText),
},
{
messageId: MESSAGE_ID_ONLY_ELEMENT,
fix: fixer => fixer.replaceText(node, onlyElementText),
},
];
return problem;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[newArraySelector](node) {
NewExpression(node) {
return getProblem(context, node);
}
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -90,8 +103,8 @@ create,

docs: {
description: 'Disallow `new Array()`.'
description: 'Disallow `new Array()`.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {getStaticValue} = require('eslint-utils');
const {newExpressionSelector} = require('./selectors/index.js');
const {getStaticValue} = require('@eslint-community/eslint-utils');
const {switchNewExpressionToCallExpression} = require('./fix/index.js');
const isNumber = require('./utils/is-number.js');
const {isNewExpression} = require('./ast/index.js');

@@ -12,3 +13,3 @@ const ERROR = 'error';

[ERROR_UNKNOWN]: '`new Buffer()` is deprecated, use `Buffer.alloc()` or `Buffer.from()` instead.',
[SUGGESTION]: 'Switch to `Buffer.{{method}}()`.'
[SUGGESTION]: 'Switch to `Buffer.{{replacement}}()`.',
};

@@ -30,12 +31,12 @@

if (isNumber(firstArgument, scope)) {
return 'alloc';
}
const staticResult = getStaticValue(firstArgument, scope);
if (staticResult) {
const {value} = staticResult;
if (typeof value === 'number') {
return 'alloc';
}
if (
typeof value === 'string' ||
Array.isArray(value)
typeof value === 'string'
|| Array.isArray(value)
) {

@@ -54,8 +55,13 @@ return 'from';

/** @param {import('eslint').Rule.RuleContext} context */
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) {

@@ -66,3 +72,3 @@ return {

data: {method},
fix: fix(node, sourceCode, method)
fix: fix(node, sourceCode, method),
};

@@ -74,12 +80,13 @@ }

messageId: ERROR_UNKNOWN,
suggest: ['from', 'alloc'].map(method => ({
suggest: ['from', 'alloc'].map(replacement => ({
messageId: SUGGESTION,
data: {method},
fix: fix(node, sourceCode, method)
}))
data: {replacement},
fix: fix(node, sourceCode, replacement),
})),
};
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -90,8 +97,8 @@ create,

docs: {
description: 'Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`.'
description: 'Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {
not,
matches,
methodCallSelector,
callExpressionSelector
} = require('./selectors/index.js');
isMethodCall,
isCallExpression,
isLiteral,
} = require('./ast/index.js');

@@ -15,63 +14,73 @@ const ERROR_MESSAGE_ID = 'error';

[SUGGESTION_REPLACE_MESSAGE_ID]: 'Replace `null` with `undefined`.',
[SUGGESTION_REMOVE_MESSAGE_ID]: 'Remove `null`.'
[SUGGESTION_REMOVE_MESSAGE_ID]: 'Remove `null`.',
};
const objectCreateSelector = methodCallSelector({
object: 'Object',
name: 'create',
length: 1
});
// `useRef(null)`
// eslint-disable-next-line unicorn/prevent-abbreviations
const useRefSelector = callExpressionSelector({name: 'useRef', length: 1});
// `React.useRef(null)`
// eslint-disable-next-line unicorn/prevent-abbreviations
const reactUseRefSelector = methodCallSelector({
object: 'React',
name: 'useRef',
length: 1
});
const selector = [
'Literal',
'[raw="null"]',
not(`${matches([objectCreateSelector, useRefSelector, reactUseRefSelector])} > .arguments`)
].join('');
const isLooseEqual = node => node.type === 'BinaryExpression' && ['==', '!='].includes(node.operator);
const isStrictEqual = node => node.type === 'BinaryExpression' && ['===', '!=='].includes(node.operator);
const isSecondArgumentOfInsertBefore = node =>
node.parent.type === 'CallExpression' &&
!node.parent.optional &&
node.parent.arguments.length === 2 &&
node.parent.arguments[0].type !== 'SpreadElement' &&
node.parent.arguments[1] === node &&
node.parent.callee.type === 'MemberExpression' &&
!node.parent.callee.computed &&
!node.parent.callee.optional &&
node.parent.callee.property.type === 'Identifier' &&
node.parent.callee.property.name === 'insertBefore';
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {checkStrictEquality} = {
checkStrictEquality: false,
...context.options[0]
...context.options[0],
};
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;
}
if (isSecondArgumentOfInsertBefore(node)) {
return;
}
const {parent} = node;
const problem = {
node,
messageId: ERROR_MESSAGE_ID
messageId: ERROR_MESSAGE_ID,
};

@@ -88,3 +97,3 @@

messageId: SUGGESTION_REPLACE_MESSAGE_ID,
fix: useUndefinedFix
fix: useUndefinedFix,
};

@@ -96,5 +105,5 @@

messageId: SUGGESTION_REMOVE_MESSAGE_ID,
fix: fixer => fixer.remove(node)
fix: fixer => fixer.remove(node),
},
useUndefinedSuggestion
useUndefinedSuggestion,
];

@@ -108,5 +117,5 @@ return problem;

messageId: SUGGESTION_REMOVE_MESSAGE_ID,
fix: fixer => fixer.removeRange([parent.id.range[1], node.range[1]])
fix: fixer => fixer.removeRange([parent.id.range[1], node.range[1]]),
},
useUndefinedSuggestion
useUndefinedSuggestion,
];

@@ -118,3 +127,3 @@ return problem;

return problem;
}
},
};

@@ -126,12 +135,13 @@ };

type: 'object',
additionalProperties: false,
properties: {
checkStrictEquality: {
type: 'boolean',
default: false
}
default: false,
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -142,9 +152,9 @@ create,

docs: {
description: 'Disallow the use of the `null` literal.'
description: 'Disallow the use of the `null` literal.',
},
fixable: 'code',
hasSuggestions: true,
schema,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {isFunction} = require('./ast/index.js');
const MESSAGE_ID = 'noObjectAsDefaultParameter';
const MESSAGE_ID_IDENTIFIER = 'identifier';
const MESSAGE_ID_NON_IDENTIFIER = 'non-identifier';
const messages = {
[MESSAGE_ID]: 'Do not use an object literal as default for parameter `{{parameter}}`.'
[MESSAGE_ID_IDENTIFIER]: 'Do not use an object literal as default for parameter `{{parameter}}`.',
[MESSAGE_ID_NON_IDENTIFIER]: 'Do not use an object literal as default.',
};
const objectParameterSelector = [
':function > AssignmentPattern.params',
'[left.type="Identifier"]',
'[right.type="ObjectExpression"]',
'[right.properties.length>0]'
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
AssignmentPattern(node) {
if (!(
node.right.type === 'ObjectExpression'
&& node.right.properties.length > 0
&& isFunction(node.parent)
&& node.parent.params.includes(node)
)) {
return;
}
const create = () => {
return {
[objectParameterSelector]: node => {
const {left, right} = node;
if (left.type === 'Identifier') {
return {
node: node.left,
messageId: MESSAGE_ID,
data: {parameter: node.left.name}
node: left,
messageId: MESSAGE_ID_IDENTIFIER,
data: {parameter: left.name},
};
}
};
};
return {
node: right,
messageId: MESSAGE_ID_NON_IDENTIFIER,
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -32,6 +46,6 @@ create,

docs: {
description: 'Disallow the use of objects as default parameters.'
description: 'Disallow the use of objects as default parameters.',
},
messages
}
messages,
},
};
'use strict';
const {methodCallSelector, STATIC_REQUIRE_SELECTOR} = require('./selectors/index.js');
const {isStaticRequire, isMethodCall, isLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'no-process-exit';
const messages = {
[MESSAGE_ID]: 'Only use `process.exit()` in CLI apps. Throw an error instead.'
[MESSAGE_ID]: 'Only use `process.exit()` in CLI apps. Throw an error instead.',
};
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',
names: ['on', 'once'],
min: 1
});
const processExitCallSelector = methodCallSelector({
object: 'process',
name: '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;

@@ -45,35 +27,70 @@ 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,
};
}
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -84,6 +101,6 @@ create,

docs: {
description: 'Disallow `process.exit()`.'
description: 'Disallow `process.exit()`.',
},
messages
}
messages,
},
};
'use strict';
const {isSemicolonToken} = require('eslint-utils');
const {isSemicolonToken} = require('@eslint-community/eslint-utils');
const getClassHeadLocation = require('./utils/get-class-head-location.js');
const removeSpacesAfter = require('./utils/remove-spaces-after.js');
const assertToken = require('./utils/assert-token.js');
const {removeSpacesAfter} = require('./fix/index.js');
const MESSAGE_ID = 'no-static-only-class';
const messages = {
[MESSAGE_ID]: 'Use an object instead of a class with only static members.'
[MESSAGE_ID]: 'Use an object instead of a class with only static members.',
};
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 === '=';
const isDeclarationOfExportDefaultDeclaration = node =>
node.type === 'ClassDeclaration' &&
node.parent.type === 'ExportDefaultDeclaration' &&
node.parent.declaration === node;
node.type === 'ClassDeclaration'
&& node.parent.type === 'ExportDefaultDeclaration'
&& node.parent.declaration === node;
// https://github.com/estree/estree/blob/master/stage3/class-features.md#propertydefinition
const isPropertyDefinition = node => node.type === 'PropertyDefinition' ||
// Legacy node type
node.type === 'ClassProperty';
const isPropertyDefinition = node => node.type === 'PropertyDefinition';
const isMethodDefinition = node => node.type === 'MethodDefinition';

@@ -39,7 +29,6 @@

decorators,
key
key,
} = node;
// Avoid matching unexpected node. For example: https://github.com/tc39/proposal-class-static-block
/* istanbul ignore next */
if (!isPropertyDefinition(node) && !isMethodDefinition(node)) {

@@ -49,3 +38,3 @@ return false;

if (!isStatic || isPrivate) {
if (!isStatic || isPrivate || key.type === 'PrivateIdentifier') {
return false;

@@ -56,7 +45,6 @@ }

if (
isDeclare ||
isReadonly ||
typeof accessibility !== 'undefined' ||
(Array.isArray(decorators) && decorators.length > 0) ||
key.type === 'TSPrivateIdentifier'
isDeclare
|| isReadonly
|| accessibility !== undefined
|| (Array.isArray(decorators) && decorators.length > 0)
) {

@@ -72,8 +60,4 @@ return false;

assertToken(staticToken, {
expected: [
{type: 'Keyword', value: 'static'},
// `@babel/eslint-parser` use `{type: 'Identifier', value: 'static'}`
{type: 'Identifier', value: 'static'}
],
ruleId: 'no-static-only-class'
expected: {type: 'Keyword', value: 'static'},
ruleId: 'no-static-only-class',
});

@@ -84,5 +68,5 @@

const maybeSemicolonToken = isPropertyDefinition(node) ?
sourceCode.getLastToken(node) :
sourceCode.getTokenAfter(node);
const maybeSemicolonToken = isPropertyDefinition(node)
? sourceCode.getLastToken(node)
: sourceCode.getTokenAfter(node);
const hasSemicolonToken = isSemicolonToken(maybeSemicolonToken);

@@ -105,5 +89,5 @@

yield (
hasSemicolonToken ?
fixer.replaceText(maybeSemicolonToken, ',') :
fixer.insertTextAfter(node, ',')
hasSemicolonToken
? fixer.replaceText(maybeSemicolonToken, ',')
: fixer.insertTextAfter(node, ',')
);

@@ -120,9 +104,9 @@ }

implements: classImplements,
parent
parent,
} = node;
if (
isDeclare ||
isAbstract ||
(Array.isArray(classImplements) && classImplements.length > 0)
isDeclare
|| isAbstract
|| (Array.isArray(classImplements) && classImplements.length > 0)
) {

@@ -144,7 +128,7 @@ return;

if (
isPropertyDefinition(node) &&
(
node.typeAnnotation ||
isPropertyDefinition(node)
&& (
node.typeAnnotation
// This is a stupid way to check if `value` of `PropertyDefinition` uses `this`
(node.value && sourceCode.getText(node.value).includes('this'))
|| (node.value && sourceCode.getText(node.value).includes('this'))
)

@@ -158,6 +142,6 @@ ) {

const classToken = sourceCode.getFirstToken(node);
/* istanbul ignore next */
/* c8 ignore next */
assertToken(classToken, {
expected: {type: 'Keyword', value: 'class'},
ruleId: 'no-static-only-class'
ruleId: 'no-static-only-class',
});

@@ -179,6 +163,6 @@

if (
type === 'ClassExpression' &&
parent.type === 'ReturnStatement' &&
body.loc.start.line !== parent.loc.start.line &&
sourceCode.text.slice(classToken.range[1], body.range[0]).trim()
type === 'ClassExpression'
&& parent.type === 'ReturnStatement'
&& body.loc.start.line !== parent.loc.start.line
&& sourceCode.text.slice(classToken.range[1], body.range[0]).trim()
) {

@@ -217,20 +201,25 @@ yield fixer.replaceText(classToken, '{');

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),
};
});
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -241,7 +230,7 @@ create,

docs: {
description: 'Forbid classes that only have static members.'
description: 'Disallow classes that only have static members.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {matches} = require('./selectors/index.js');
const MESSAGE_ID = 'no-this-assignment';
const messages = {
[MESSAGE_ID]: 'Do not assign `this` to `{{name}}`.'
[MESSAGE_ID]: 'Do not assign `this` to `{{name}}`.',
};
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 = context => {
context.on('VariableDeclarator', node => getProblem(node.id, node.init));
context.on('AssignmentExpression', node => getProblem(node.left, node.right));
};
const create = () => ({
[selector](node) {
const variable = node.type === 'AssignmentExpression' ? node.left : node.id;
return {
node,
data: {name: variable.name},
messageId: MESSAGE_ID
};
}
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -39,6 +34,6 @@ create,

docs: {
description: 'Disallow assigning `this` to a variable.'
description: 'Disallow assigning `this` to a variable.',
},
messages
}
messages,
},
};
'use strict';
const {isParenthesized} = require('eslint-utils');
const {isParenthesized} = require('@eslint-community/eslint-utils');
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const MESSAGE_ID = 'no-unreadable-array-destructuring';
const messages = {
[MESSAGE_ID]: 'Array destructuring may not contain consecutive ignored values.'
[MESSAGE_ID]: 'Array destructuring may not contain consecutive ignored values.',
};

@@ -13,10 +14,13 @@

/** @param {import('eslint').Rule.RuleContext} context */
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;

@@ -27,3 +31,3 @@ }

node,
messageId: MESSAGE_ID
messageId: MESSAGE_ID,
};

@@ -33,6 +37,6 @@

if (
parent.type === 'VariableDeclarator' &&
parent.id === node &&
parent.init !== null &&
nonNullElements.length === 1
parent.type === 'VariableDeclarator'
&& parent.id === node
&& parent.init !== null
&& nonNullElements.length === 1
) {

@@ -52,4 +56,4 @@ const [element] = nonNullElements;

if (
!isParenthesized(array, sourceCode) &&
shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
!isParenthesized(array, sourceCode)
&& shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
) {

@@ -61,2 +65,4 @@ yield fixer.insertTextBefore(array, '(');

}
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
};

@@ -67,6 +73,7 @@ }

return problem;
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -77,7 +84,7 @@ create,

docs: {
description: 'Disallow unreadable array destructuring.'
description: 'Disallow unreadable array destructuring.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const getScopes = require('./utils/get-scopes.js');
const MESSAGE_ID = 'no-unused-properties';
const messages = {
[MESSAGE_ID]: 'Property `{{name}}` is defined but never used.'
[MESSAGE_ID]: 'Property `{{name}}` is defined but never used.',
};
const getDeclaratorOrPropertyValue = declaratorOrProperty =>
declaratorOrProperty.init ||
declaratorOrProperty.value;
declaratorOrProperty.init
|| declaratorOrProperty.value;
const isMemberExpressionCall = memberExpression =>
memberExpression.parent &&
memberExpression.parent.type === 'CallExpression' &&
memberExpression.parent.callee === memberExpression;
memberExpression.parent.type === 'CallExpression'
&& memberExpression.parent.callee === memberExpression;
const isMemberExpressionAssignment = memberExpression =>
memberExpression.parent &&
memberExpression.parent.type === 'AssignmentExpression';
const isMemberExpressionComputedBeyondPrediction = memberExpression =>
memberExpression.computed &&
memberExpression.property.type !== 'Literal';
memberExpression.computed
&& memberExpression.property.type !== 'Literal';
const specialProtoPropertyKey = {
type: 'Identifier',
name: '__proto__'
name: '__proto__',
};

@@ -54,4 +53,4 @@

const objectPatternMatchesObjectExprPropertyKey = (pattern, key) => {
return pattern.properties.some(property => {
const objectPatternMatchesObjectExprPropertyKey = (pattern, key) =>
pattern.properties.some(property => {
if (property.type === 'RestElement') {

@@ -63,3 +62,2 @@ return true;

});
};

@@ -85,3 +83,5 @@ const isLeafDeclaratorOrProperty = declaratorOrProperty => {

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {sourceCode} = context;
const getPropertyDisplayName = property => {

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

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

@@ -106,4 +106,4 @@

data: {
name: getPropertyDisplayName(property)
}
name: getPropertyDisplayName(property),
},
});

@@ -136,5 +136,5 @@ return;

if (
parent.type === 'VariableDeclarator' &&
parent.parent.type === 'VariableDeclaration' &&
parent.parent.parent.type === 'ExportNamedDeclaration'
parent.type === 'VariableDeclarator'
&& parent.parent.type === 'VariableDeclaration'
&& parent.parent.parent.type === 'ExportNamedDeclaration'
) {

@@ -149,6 +149,6 @@ return {identifier: parent};

if (
isMemberExpressionAssignment(parent) ||
isMemberExpressionCall(parent) ||
isMemberExpressionComputedBeyondPrediction(parent) ||
propertyKeysEqual(parent.property, key)
isMemberExpressionAssignment(parent)
|| isMemberExpressionCall(parent)
|| isMemberExpressionComputedBeyondPrediction(parent)
|| propertyKeysEqual(parent.property, key)
) {

@@ -162,4 +162,4 @@ return {identifier: parent};

if (
parent.type === 'VariableDeclarator' &&
parent.id.type === 'ObjectPattern'
parent.type === 'VariableDeclarator'
&& parent.id.type === 'ObjectPattern'
) {

@@ -174,4 +174,4 @@ if (objectPatternMatchesObjectExprPropertyKey(parent.id, key)) {

if (
parent.type === 'AssignmentExpression' &&
parent.left.type === 'ObjectPattern'
parent.type === 'AssignmentExpression'
&& parent.left.type === 'ObjectPattern'
) {

@@ -223,25 +223,17 @@ if (objectPatternMatchesObjectExprPropertyKey(parent.left, key)) {

const checkChildScopes = scope => {
for (const childScope of scope.childScopes) {
checkScope(childScope);
}
};
return {
'Program:exit'(program) {
const scopes = getScopes(sourceCode.getScope(program));
for (const scope of scopes) {
if (scope.type === 'global') {
continue;
}
const checkScope = scope => {
if (scope.type === 'global') {
return checkChildScopes(scope);
}
checkVariables(scope);
return checkChildScopes(scope);
checkVariables(scope);
}
},
};
return {
'Program:exit'() {
checkScope(context.getScope());
}
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -252,6 +244,6 @@ create,

docs: {
description: 'Disallow unused object properties.'
description: 'Disallow unused object properties.',
},
messages
}
messages,
},
};
'use strict';
const {isCommaToken} = require('eslint-utils');
const replaceNodeOrTokenAndSpacesBefore = require('./utils/replace-node-or-token-and-spaces-before.js');
const {isCommaToken} = require('@eslint-community/eslint-utils');
const {replaceNodeOrTokenAndSpacesBefore} = require('./fix/index.js');
const {isUndefined, isFunction} = require('./ast/index.js');
const messageId = 'no-useless-undefined';
const messages = {
[messageId]: 'Do not use useless `undefined`.'
[messageId]: 'Do not use 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 isUndefined = node => node && node.type === 'Identifier' && node.name === 'undefined';
const compareFunctionNames = new Set([

@@ -57,3 +30,3 @@ 'is',

'strictSame',
'strictNotSame'
'strictNotSame',
]);

@@ -66,6 +39,5 @@ const shouldIgnore = node => {

} else if (
node.type === 'MemberExpression' &&
node.computed === false &&
node.property &&
node.property.type === 'Identifier'
node.type === 'MemberExpression'
&& node.computed === false
&& node.property.type === 'Identifier'
) {

@@ -75,11 +47,23 @@ name = node.property.name;

return compareFunctionNames.has(name) ||
return compareFunctionNames.has(name)
// `array.push(undefined)`
|| name === 'push'
// `array.unshift(undefined)`
|| name === 'unshift'
// `array.includes(undefined)`
|| name === 'includes'
// `set.add(undefined)`
name === 'add' ||
|| name === 'add'
// `set.has(undefined)`
|| name === 'has'
// `map.set(foo, undefined)`
name === 'set' ||
// `array.push(undefined)`
name === 'push' ||
// `array.unshift(undefined)`
name === 'unshift';
|| name === 'set'
// `React.createContext(undefined)`
|| name === 'createContext'
// https://vuejs.org/api/reactivity-core.html#ref
|| name === 'ref';
};

@@ -95,7 +79,20 @@

const isFunctionBindCall = node =>
!node.optional
&& node.callee.type === 'MemberExpression'
&& !node.callee.computed
&& node.callee.property.type === 'Identifier'
&& node.callee.property.name === 'bind';
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());
if (functionNode && functionNode.returnType) {
const functionNode = getFunction(sourceCode.getScope(node));
if (functionNode?.returnType) {
return;

@@ -108,10 +105,9 @@ }

messageId,
fix: fixer => fix(node, fixer)
fix,
};
};
const sourceCode = context.getSourceCode();
const options = {
checkArguments: true,
...context.options[0]
...context.options[0],
};

@@ -122,73 +118,156 @@

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;
const undefinedArguments = [];
for (let index = argumentNodes.length - 1; index >= 0; index--) {
const node = argumentNodes[index];
if (isUndefined(node)) {
undefinedArguments.unshift(node);
} else {
break;
}
}
// `() => 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,
);
}
});
if (undefinedArguments.length === 0) {
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 firstUndefined = undefinedArguments[0];
const lastUndefined = undefinedArguments[undefinedArguments.length - 1];
// `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;
return {
messageId,
loc: {
start: firstUndefined.loc.start,
end: lastUndefined.loc.end
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, '?')
);
}
},
fix: fixer => {
let start = firstUndefined.range[0];
let end = lastUndefined.range[1];
/* CheckFunctionReturnType */ true,
);
}
});
const previousArgument = argumentNodes[argumentNodes.length - undefinedArguments.length - 1];
if (!options.checkArguments) {
return;
}
if (previousArgument) {
start = previousArgument.range[1];
} else {
// If all arguments removed, and there is trailing comma, we need remove it.
const tokenAfter = context.getTokenAfter(lastUndefined);
if (isCommaToken(tokenAfter)) {
end = tokenAfter.range[1];
}
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;
}
}
if (undefinedArguments.length === 0) {
return;
}
const firstUndefined = undefinedArguments[0];
const lastUndefined = undefinedArguments[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];
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;
});
};

@@ -199,11 +278,12 @@

type: 'object',
additionalProperties: false,
properties: {
checkArguments: {
type: 'boolean'
}
type: 'boolean',
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -214,8 +294,8 @@ create,

docs: {
description: 'Disallow useless `undefined`.'
description: 'Disallow useless `undefined`.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'use strict';
const {isParenthesized} = require('eslint-utils');
const {isParenthesized} = require('@eslint-community/eslint-utils');
const needsSemicolon = require('./utils/needs-semicolon.js');
const {isNumber, isDecimalInteger} = require('./utils/numeric.js');
const {isDecimalInteger} = require('./utils/numeric.js');
const toLocation = require('./utils/to-location.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isNumberLiteral} = require('./ast/index.js');

@@ -11,56 +13,58 @@ const MESSAGE_ZERO_FRACTION = 'zero-fraction';

[MESSAGE_ZERO_FRACTION]: 'Don\'t use a zero fraction in the number.',
[MESSAGE_DANGLING_DOT]: 'Don\'t use a dangling dot in the number.'
[MESSAGE_DANGLING_DOT]: 'Don\'t use a dangling dot in the number.',
};
const create = context => {
return {
Literal: node => {
if (!isNumber(node)) {
return;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
Literal(node) {
if (!isNumberLiteral(node)) {
return;
}
// Legacy octal number `0777` and prefixed number `0o1234` cannot have a dot.
const {raw} = node;
const match = raw.match(/^(?<before>[\d_]*)(?<dotAndFractions>\.[\d_]*)(?<after>.*)$/);
if (!match) {
return;
}
// Legacy octal number `0777` and prefixed number `0o1234` cannot have a dot.
const {raw} = node;
const match = raw.match(/^(?<before>[\d_]*)(?<dotAndFractions>\.[\d_]*)(?<after>.*)$/);
if (!match) {
return;
}
const {before, dotAndFractions, after} = match.groups;
const formatted = before + dotAndFractions.replace(/[.0_]+$/g, '') + after;
const {before, dotAndFractions, after} = match.groups;
const fixedDotAndFractions = dotAndFractions.replace(/[.0_]+$/g, '');
const formatted = ((before + fixedDotAndFractions) || '0') + after;
if (formatted === raw) {
return;
}
if (formatted === raw) {
return;
}
const isDanglingDot = dotAndFractions === '.';
// End of fractions
const end = node.range[0] + before.length + dotAndFractions.length;
const start = end - (raw.length - formatted.length);
const sourceCode = context.getSourceCode();
return {
loc: toLocation([start, end], sourceCode),
messageId: isDanglingDot ? MESSAGE_DANGLING_DOT : MESSAGE_ZERO_FRACTION,
fix: fixer => {
let fixed = formatted;
if (
node.parent.type === 'MemberExpression' &&
node.parent.object === node &&
isDecimalInteger(formatted) &&
!isParenthesized(node, sourceCode)
) {
fixed = `(${fixed})`;
const isDanglingDot = dotAndFractions === '.';
// End of fractions
const end = node.range[0] + before.length + dotAndFractions.length;
const start = end - (raw.length - formatted.length);
const {sourceCode} = context;
return {
loc: toLocation([start, end], sourceCode),
messageId: isDanglingDot ? MESSAGE_DANGLING_DOT : MESSAGE_ZERO_FRACTION,
* fix(fixer) {
let fixed = formatted;
if (
node.parent.type === 'MemberExpression'
&& node.parent.object === node
&& isDecimalInteger(formatted)
&& !isParenthesized(node, sourceCode)
) {
fixed = `(${fixed})`;
if (needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, fixed)) {
fixed = `;${fixed}`;
}
if (needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, fixed)) {
fixed = `;${fixed}`;
}
return fixer.replaceText(node, fixed);
}
};
}
};
};
yield fixer.replaceText(node, fixed);
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
},
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -71,7 +75,7 @@ create,

docs: {
description: 'Disallow number literals with zero fractions or dangling dots.'
description: 'Disallow number literals with zero fractions or dangling dots.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isNumber, isBigInt} = require('./utils/numeric.js');
const {checkVueTemplate} = require('./utils/rule.js');
const {isNumberLiteral, isBigIntLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'number-literal-case';
const messages = {
[MESSAGE_ID]: 'Invalid number literal casing.'
[MESSAGE_ID]: 'Invalid number literal casing.',
};

@@ -18,35 +19,35 @@

const create = () => {
return {
Literal: node => {
const {raw} = node;
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
Literal(node) {
const {raw} = node;
let fixed = raw;
if (isNumber(node)) {
fixed = fix(raw);
} else if (isBigInt(node)) {
fixed = fix(raw.slice(0, -1)) + 'n';
}
let fixed = raw;
if (isNumberLiteral(node)) {
fixed = fix(raw);
} else if (isBigIntLiteral(node)) {
fixed = fix(raw.slice(0, -1)) + 'n';
}
if (raw !== fixed) {
return {
node,
messageId: MESSAGE_ID,
fix: fixer => fixer.replaceText(node, fixed)
};
}
if (raw !== fixed) {
return {
node,
messageId: MESSAGE_ID,
fix: fixer => fixer.replaceText(node, fixed),
};
}
};
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
create: checkVueTemplate(create),
meta: {
type: 'suggestion',
docs: {
description: 'Enforce proper case for numeric literals.'
description: 'Enforce proper case for numeric literals.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const numeric = require('./utils/numeric.js');
const {isBigIntLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'numeric-separators-style';
const messages = {
[MESSAGE_ID]: 'Invalid group length in numeric value.'
[MESSAGE_ID]: 'Invalid group length in numeric value.',
};

@@ -52,3 +53,3 @@

sign,
power
power,
} = numeric.parseNumber(value);

@@ -63,3 +64,3 @@

hexadecimal: {minimumDigits: 0, groupLength: 2},
number: {minimumDigits: 5, groupLength: 3}
number: {minimumDigits: 5, groupLength: 3},
};

@@ -72,6 +73,6 @@ const create = context => {

hexadecimal,
number
number,
} = {
onlyIfContainsSeparator: false,
...context.options[0]
...context.options[0],
};

@@ -83,3 +84,3 @@

...defaultOptions.binary,
...binary
...binary,
},

@@ -89,3 +90,3 @@ '0o': {

...defaultOptions.octal,
...octal
...octal,
},

@@ -95,3 +96,3 @@ '0x': {

...defaultOptions.hexadecimal,
...hexadecimal
...hexadecimal,
},

@@ -101,8 +102,8 @@ '': {

...defaultOptions.number,
...number
}
...number,
},
};
return {
Literal: node => {
Literal(node) {
if (!numeric.isNumeric(node) || numeric.isLegacyOctal(node)) {

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

let suffix = '';
if (numeric.isBigInt(node)) {
if (isBigIntLiteral(node)) {
number = raw.slice(0, -1);

@@ -135,6 +136,6 @@ suffix = 'n';

messageId: MESSAGE_ID,
fix: fixer => fixer.replaceText(node, formatted)
fix: fixer => fixer.replaceText(node, formatted),
};
}
}
},
};

@@ -145,5 +146,6 @@ };

type: 'object',
additionalProperties: false,
properties: {
onlyIfContainsSeparator: {
type: 'boolean'
type: 'boolean',
},

@@ -153,3 +155,3 @@ minimumDigits: {

minimum: 0,
default: minimumDigits
default: minimumDigits,
},

@@ -159,6 +161,5 @@ groupLength: {

minimum: 1,
default: groupLength
}
default: groupLength,
},
},
additionalProperties: false
});

@@ -168,14 +169,15 @@

type: 'object',
additionalProperties: false,
properties: {
...Object.fromEntries(
Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)])
Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)]),
),
onlyIfContainsSeparator: {
type: 'boolean',
default: false
}
default: false,
},
},
additionalProperties: false
}];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -186,8 +188,8 @@ create,

docs: {
description: 'Enforce the style of numeric separators by correctly grouping digits.'
description: 'Enforce the style of numeric separators by correctly grouping digits.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'use strict';
const {isParenthesized} = require('eslint-utils');
const domEventsJson = require('./utils/dom-events.json');
const {STATIC_REQUIRE_SOURCE_SELECTOR} = require('./selectors/index.js');
const {isParenthesized} = require('@eslint-community/eslint-utils');
const eventTypes = require('./shared/dom-events.js');
const {isUndefined, isNullLiteral, isStaticRequire} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-add-event-listener';
const messages = {
[MESSAGE_ID]: 'Prefer `{{replacement}}` over `{{method}}`.{{extra}}'
[MESSAGE_ID]: 'Prefer `{{replacement}}` over `{{method}}`.{{extra}}',
};
const extraMessages = {
beforeunload: 'Use `event.preventDefault(); event.returnValue = \'foo\'` to trigger the prompt.',
message: 'Note that there is difference between `SharedWorker#onmessage` and `SharedWorker#addEventListener(\'message\')`.'
message: 'Note that there is difference between `SharedWorker#onmessage` and `SharedWorker#addEventListener(\'message\')`.',
error: 'Note that there is difference between `{window,element}.onerror` and `{window,element}.addEventListener(\'error\')`.',
};
const nestedEvents = Object.values(domEventsJson);
const eventTypes = new Set(nestedEvents.flat());
const getEventMethodName = memberExpression => memberExpression.property.name;

@@ -38,4 +37,4 @@ const getEventTypeName = eventMethodName => eventMethodName.slice('on'.length);

if (
assignedExpression.type !== 'ArrowFunctionExpression' &&
assignedExpression.type !== 'FunctionExpression'
assignedExpression.type !== 'ArrowFunctionExpression'
&& assignedExpression.type !== 'FunctionExpression'
) {

@@ -52,14 +51,5 @@ return false;

const isClearing = node => {
if (node.type === 'Literal') {
return node.raw === 'null';
}
const isClearing = node => isUndefined(node) || isNullLiteral(node);
if (node.type === 'Identifier') {
return node.name === 'undefined';
}
return false;
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {

@@ -78,3 +68,3 @@ const options = context.options[0] || {};

upper: codePathInfo,
returnsSomething: false
returnsSomething: false,
};

@@ -88,4 +78,8 @@ },

[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;

@@ -95,4 +89,4 @@ }

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

@@ -111,7 +105,7 @@ }

const {left: memberExpression, right: assignedExpression} = node;
const {left: memberExpression, right: assignedExpression, operator} = node;
if (
memberExpression.type !== 'MemberExpression' ||
memberExpression.computed
memberExpression.type !== 'MemberExpression'
|| memberExpression.computed
) {

@@ -140,4 +134,4 @@ return;

} else if (
eventTypeName === 'beforeunload' &&
!shouldFixBeforeUnload(assignedExpression, nodeReturnsSomething)
eventTypeName === 'beforeunload'
&& !shouldFixBeforeUnload(assignedExpression, nodeReturnsSomething)
) {

@@ -148,4 +142,11 @@ extra = extraMessages.beforeunload;

extra = extraMessages.message;
} else {
fix = fixer => fixCode(fixer, context.getSourceCode(), node, memberExpression);
} else if (eventTypeName === 'error') {
// Disable `onerror` fix, see #1493
extra = extraMessages.error;
} else if (
operator === '='
&& node.parent.type === 'ExpressionStatement'
&& node.parent.expression === node
) {
fix = fixer => fixCode(fixer, context.sourceCode, node, memberExpression);
}

@@ -159,7 +160,7 @@

method: eventMethodName,
extra: extra ? ` ${extra}` : ''
extra: extra ? ` ${extra}` : '',
},
fix
fix,
};
}
},
};

@@ -171,2 +172,3 @@ };

type: 'object',
additionalProperties: false,
properties: {

@@ -176,11 +178,11 @@ excludedPackages: {

items: {
type: 'string'
type: 'string',
},
uniqueItems: true
}
uniqueItems: true,
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -191,8 +193,8 @@ create,

docs: {
description: 'Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions.'
description: 'Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'use strict';
const {isParenthesized, findVariable} = require('eslint-utils');
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 renameVariable = require('./utils/rename-variable.js');
const avoidCapture = require('./utils/avoid-capture.js');
const getChildScopesRecursive = require('./utils/get-child-scopes-recursive.js');
const singular = require('./utils/singular.js');
const extendFixRange = require('./utils/extend-fix-range.js');
const {removeMemberExpressionProperty, removeMethodCall} = require('./fix/index.js');
extendFixRange,
removeMemberExpressionProperty,
removeMethodCall,
renameVariable,
} = require('./fix/index.js');
const {
isLeftHandSide,
singular,
getScopes,
avoidCapture,
getVariableIdentifiers,
} = require('./utils/index.js');
const {isMethodCall} = require('./ast/index.js');
const ERROR_ZERO_INDEX = 'error-zero-index';
const ERROR_SHIFT = 'error-shift';
const ERROR_POP = 'error-pop';
const ERROR_AT_MINUS_ONE = 'error-at-minus-one';
const ERROR_DESTRUCTURING_DECLARATION = 'error-destructuring-declaration';

@@ -27,2 +31,4 @@ const ERROR_DESTRUCTURING_ASSIGNMENT = 'error-destructuring-assignment';

[ERROR_SHIFT]: 'Prefer `.find(…)` over `.filter(…).shift()`.',
[ERROR_POP]: 'Prefer `.findLast(…)` over `.filter(…).pop()`.',
[ERROR_AT_MINUS_ONE]: 'Prefer `.findLast(…)` over `.filter(…).at(-1)`.',
[ERROR_DESTRUCTURING_DECLARATION]: 'Prefer `.find(…)` over destructuring `.filter(…)`.',

@@ -32,69 +38,13 @@ // Same message as `ERROR_DESTRUCTURING_DECLARATION`, but different case

[SUGGESTION_NULLISH_COALESCING_OPERATOR]: 'Replace `.filter(…)` with `.find(…) ?? …`.',
[SUGGESTION_LOGICAL_OR_OPERATOR]: 'Replace `.filter(…)` with `.find(…) || …`.'
[SUGGESTION_LOGICAL_OR_OPERATOR]: 'Replace `.filter(…)` with `.find(…) || …`.',
};
const filterMethodSelectorOptions = {
name: 'filter',
min: 1,
max: 2
};
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({
name: 'shift',
length: 0
}),
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`

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

(node.type === 'LogicalExpression' && (
node.operator === operator ||
node.operator === operator
// https://tc39.es/proposal-nullish-coalescing/ says

@@ -125,15 +75,15 @@ // `??` has lower precedence than `||`

// `??` has higher precedence than `||`
(operator === '||' && node.operator === '??') ||
(operator === '??' && (node.operator === '||' || node.operator === '&&'))
)) ||
node.type === 'ConditionalExpression' ||
|| (operator === '||' && node.operator === '??')
|| (operator === '??' && (node.operator === '||' || node.operator === '&&'))
))
|| node.type === 'ConditionalExpression'
// Lower than `assignment`, should already parenthesized
/* istanbul ignore next */
node.type === 'AssignmentExpression' ||
node.type === 'YieldExpression' ||
node.type === 'SequenceExpression'
/* c8 ignore next */
|| node.type === 'AssignmentExpression'
|| node.type === 'YieldExpression'
|| node.type === 'SequenceExpression'
);
const getDestructuringLeftAndRight = node => {
/* istanbul ignore next */
/* c8 ignore next 3 */
if (!node) {

@@ -192,3 +142,3 @@ return {};

{operator: '??', messageId: SUGGESTION_NULLISH_COALESCING_OPERATOR},
{operator: '||', messageId: SUGGESTION_LOGICAL_OR_OPERATOR}
{operator: '||', messageId: SUGGESTION_LOGICAL_OR_OPERATOR},
].map(({messageId, operator}) => ({

@@ -200,3 +150,3 @@ messageId,

yield * fixDestructuring(node, sourceCode, fixer);
}
},
}));

@@ -214,115 +164,255 @@ } else {

const isAccessingZeroIndex = node =>
node.parent &&
node.parent.type === 'MemberExpression' &&
node.parent.computed === true &&
node.parent.object === node &&
node.parent.property &&
node.parent.property.type === 'Literal' &&
node.parent.property.raw === '0';
node.parent.type === 'MemberExpression'
&& node.parent.computed === true
&& node.parent.object === node
&& node.parent.property.type === 'Literal'
&& node.parent.property.raw === '0';
const isDestructuringFirstElement = node => {
const {left, right} = getDestructuringLeftAndRight(node.parent);
return left &&
right &&
right === node &&
left.type === 'ArrayPattern' &&
left.elements &&
left.elements.length === 1 &&
left.elements[0].type !== 'RestElement';
return left
&& right
&& right === node
&& left.type === 'ArrayPattern'
&& left.elements.length === 1
&& left.elements[0]
&& left.elements[0].type !== 'RestElement';
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const {
checkFromLast,
} = {
checkFromLast: false,
...context.options[0],
};
return {
[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 [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);
// Prevent possible variable conflicts
yield * extendFixRange(fixer, sourceCode.ast.range);
}
}
const problem = {
node: node.init.callee.property,
messageId: ERROR_DECLARATION
for (const node of zeroIndexNodes) {
yield removeMemberExpressionProperty(fixer, node, sourceCode);
}
for (const node of destructuringNodes) {
yield * fixDestructuring(node, sourceCode, fixer);
}
};
}
// `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');
return problem;
});
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, getChildScopesRecursive(scope), context.parserOptions.ecmaVersion);
yield * renameVariable(variable, singularizedName, fixer);
if (!checkFromLast) {
return;
}
// Prevent possible variable conflicts
yield * extendFixRange(fixer, sourceCode.ast.range);
}
// `array.filter().pop()`
context.on('CallExpression', node => {
if (!(
isMethodCall(node, {
method: 'pop',
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
&& isArrayFilterCall(node.callee.object)
)) {
return;
}
for (const node of zeroIndexNodes) {
yield removeMemberExpressionProperty(fixer, node, sourceCode);
}
return {
node: node.callee.object.callee.property,
messageId: ERROR_POP,
fix: fixer => [
fixer.replaceText(node.callee.object.callee.property, 'findLast'),
...removeMethodCall(fixer, node, sourceCode),
],
};
});
for (const node of destructuringNodes) {
yield * fixDestructuring(node, sourceCode, fixer);
}
};
}
// `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 problem;
}
};
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),
],
};
});
};
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
checkFromLast: {
type: 'boolean',
// TODO: Change default value to `true`, or remove the option when targeting Node.js 18.
default: false,
},
},
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -333,8 +423,9 @@ create,

docs: {
description: 'Prefer `.find(…)` over the first element from `.filter(…)`.'
description: 'Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)`.',
},
fixable: 'code',
hasSuggestions: true,
schema,
messages,
hasSuggestions: true
}
},
};
'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');

@@ -8,18 +8,34 @@

const messages = {
[MESSAGE_ID]: 'Prefer `.flatMap(…)` over `.map(…).flat()`.'
[MESSAGE_ID]: 'Prefer `.flatMap(…)` over `.map(…).flat()`.',
};
const selector = [
methodCallSelector('flat'),
matches([
'[arguments.length=0]',
'[arguments.length=1][arguments.0.type="Literal"][arguments.0.raw="1"]'
]),
methodCallSelector({path: 'callee.object', name: 'map'})
].join('');
const ignored = ['React.Children', 'Children'];
/** @param {import('eslint').Rule.RuleContext} context */
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;

@@ -30,3 +46,3 @@ if (isNodeMatches(mapCallExpression.callee.object, ignored)) {

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

@@ -52,7 +68,8 @@

yield fixer.replaceText(mapProperty, 'flatMap');
}
},
};
}
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -63,7 +80,7 @@ create,

docs: {
description: 'Prefer `.flatMap(…)` over `.map(…).flat()`.'
description: 'Prefer `.flatMap(…)` over `.map(…).flat()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'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');
const MESSAGE_ID = 'prefer-array-flat';
const messages = {
[MESSAGE_ID]: 'Prefer `Array#flat()` over `{{description}}` to flatten an array.'
[MESSAGE_ID]: 'Prefer `Array#flat()` over `{{description}}` to flatten an array.',
};
const isEmptyArrayExpression = node =>
node.type === 'ArrayExpression'
&& node.elements.length === 0;
// `array.flatMap(x => x)`
const arrayFlatMap = {
selector: [
methodCallSelector({
name: 'flatMap',
length: 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,
testFunction(node) {
if (!isMethodCall(node, {
method: 'flatMap',
argumentsLength: 1,
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,
description: 'Array#flatMap()'
description: 'Array#flatMap()',
};
// `array.reduce((a, b) => a.concat(b), [])`
// `array.reduce((a, b) => [...a, ...b], [])`
const arrayReduce = {
selector: [
methodCallSelector({
name: 'reduce',
length: 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({
name: 'concat',
length: 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()'
};
testFunction(node) {
if (!isMethodCall(node, {
method: 'reduce',
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})) {
return false;
}
// `array.reduce((a, b) => [...a, ...b], [])`
const arrayReduce2 = {
selector: [
methodCallSelector({
name: 'reduce',
length: 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,
description: 'Array#reduce()'
description: 'Array#reduce()',
};
// `[].concat(maybeArray)` and `[].concat(...array)`
// `[].concat(maybeArray)`
// `[].concat(...array)`
const emptyArrayConcat = {
selector: [
methodCallSelector({
name: 'concat',
length: 1,
allowSpreadElement: true
}),
emptyArraySelector('callee.object')
].join(''),
getArrayNode: node => {
testFunction(node) {
return isMethodCall(node, {
method: 'concat',
argumentsLength: 1,
allowSpreadElement: true,
optionalCall: false,
optionalMember: false,
})
&& isEmptyArrayExpression(node.callee.object);
},
getArrayNode(node) {
const argumentNode = node.arguments[0];

@@ -107,3 +122,3 @@ return argumentNode.type === 'SpreadElement' ? argumentNode.argument : argumentNode;

description: '[].concat()',
shouldSwitchToArray: node => node.arguments[0].type !== 'SpreadElement'
shouldSwitchToArray: node => node.arguments[0].type !== 'SpreadElement',
};

@@ -115,16 +130,26 @@

const arrayPrototypeConcat = {
selector: [
methodCallSelector({
names: ['apply', 'call'],
length: 2,
allowSpreadElement: true
}),
emptyArraySelector('arguments.0'),
arrayPrototypeMethodSelector({
path: 'callee.object',
name: 'concat'
})
].join(''),
testFunction: node => node.arguments[1].type !== 'SpreadElement' || node.callee.property.name === 'call',
getArrayNode: node => {
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) {
const argumentNode = node.arguments[1];

@@ -134,3 +159,3 @@ return argumentNode.type === 'SpreadElement' ? argumentNode.argument : argumentNode;

description: 'Array.prototype.concat()',
shouldSwitchToArray: node => node.arguments[1].type !== 'SpreadElement' && node.callee.property.name === 'call'
shouldSwitchToArray: node => node.arguments[1].type !== 'SpreadElement' && node.callee.property.name === 'call',
};

@@ -141,8 +166,4 @@

'lodash.flatten',
'underscore.flatten'
'underscore.flatten',
];
const anyCall = {
selector: callExpressionSelector({length: 1}),
getArrayNode: node => node.arguments[0]
};

@@ -154,3 +175,3 @@ function fix(node, array, sourceCode, shouldSwitchToArray) {

return fixer => {
return function * (fixer) {
let fixed = getParenthesizedText(array, sourceCode);

@@ -162,4 +183,4 @@ if (shouldSwitchToArray) {

} else if (
!isParenthesized(array, sourceCode) &&
shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
!isParenthesized(array, sourceCode)
&& shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
) {

@@ -176,3 +197,5 @@ fixed = `(${fixed})`;

return fixer.replaceText(node, fixed);
yield fixer.replaceText(node, fixed);
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
};

@@ -184,7 +207,5 @@ }

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

@@ -194,43 +215,47 @@ const cases = [

arrayReduce,
arrayReduce2,
emptyArrayConcat,
arrayPrototypeConcat,
{
...anyCall,
testFunction: node => isNodeMatches(node.callee, functions),
description: node => `${functions.find(nameOrPath => isNodeMatchesNameOrPath(node.callee, nameOrPath)).trim()}()`
}
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()}()`,
},
];
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,12 +266,13 @@

type: 'object',
additionalProperties: false,
properties: {
functions: {
type: 'array',
uniqueItems: true
}
uniqueItems: true,
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -257,8 +283,8 @@ create,

docs: {
description: 'Prefer `Array#flat()` over legacy techniques to flatten arrays.'
description: 'Prefer `Array#flat()` over legacy techniques to flatten arrays.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'use strict';
const simpleArraySearchRule = require('./shared/simple-array-search-rule.js');
const {messages, createListeners} = simpleArraySearchRule({
const indexOfOverFindIndexRule = simpleArraySearchRule({
method: 'findIndex',
replacement: 'indexOf'
replacement: 'indexOf',
});
const lastIndexOfOverFindLastIndexRule = simpleArraySearchRule({
method: 'findLastIndex',
replacement: 'lastIndexOf',
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create: context => createListeners(context),
create(context) {
indexOfOverFindIndexRule.listen(context);
lastIndexOfOverFindLastIndexRule.listen(context);
},
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `Array#indexOf()` over `Array#findIndex()` when looking for the index of an item.'
description: 'Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item.',
},
fixable: 'code',
messages,
hasSuggestions: true
}
hasSuggestions: true,
messages: {
...indexOfOverFindIndexRule.messages,
...lastIndexOfOverFindLastIndexRule.messages,
},
},
};
'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, isMethodCall, isMemberExpression} = require('./ast/index.js');

@@ -12,83 +15,133 @@ const ERROR_ID_ARRAY_SOME = 'some';

const messages = {
[ERROR_ID_ARRAY_SOME]: 'Prefer `.some(…)` over `.find(…)`.',
[SUGGESTION_ID_ARRAY_SOME]: 'Replace `.find(…)` with `.some(…)`.',
[ERROR_ID_ARRAY_FILTER]: 'Prefer `.some(…)` over non-zero length check from `.filter(…)`.'
[ERROR_ID_ARRAY_SOME]: 'Prefer `.some(…)` over `.{{method}}(…)`.',
[SUGGESTION_ID_ARRAY_SOME]: 'Replace `.{{method}}(…)` with `.some(…)`.',
[ERROR_ID_ARRAY_FILTER]: 'Prefer `.some(…)` over non-zero length check from `.filter(…)`.',
};
const arrayFindCallSelector = methodCallSelector({
name: 'find',
min: 1,
max: 2
});
const isCheckingUndefined = node =>
node.parent.type === 'BinaryExpression'
// Not checking yoda expression `null != foo.find()` and `undefined !== foo.find()
&& node.parent.left === node
&& (
(
(
node.parent.operator === '!='
|| node.parent.operator === '=='
|| node.parent.operator === '==='
|| node.parent.operator === '!=='
)
&& isUndefined(node.parent.right)
)
|| (
(
node.parent.operator === '!='
|| node.parent.operator === '=='
)
// eslint-disable-next-line unicorn/no-null
&& isLiteral(node.parent.right, null)
)
);
const arrayFilterCallSelector = [
'BinaryExpression',
'[right.type="Literal"]',
// We assume the user already follows `unicorn/explicit-length-check`, these are allowed in that rule
matches([
'[operator=">"][right.raw="0"]',
'[operator="!=="][right.raw="0"]',
'[operator=">="][right.raw="1"]'
]),
' > ',
`${memberExpressionSelector('length')}.left`,
' > ',
`${methodCallSelector('filter')}.object`
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
CallExpression(callExpression) {
if (!isMethodCall(callExpression, {
methods: ['find', 'findLast'],
minimumArguments: 1,
maximumArguments: 2,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const create = context => {
return {
[arrayFindCallSelector](findCall) {
if (!isBooleanNode(findCall)) {
return;
}
const isCompare = isCheckingUndefined(callExpression);
if (!isCompare && !isBooleanNode(callExpression)) {
return;
}
const findProperty = findCall.callee.property;
return {
node: findProperty,
messageId: ERROR_ID_ARRAY_SOME,
suggest: [
{
messageId: SUGGESTION_ID_ARRAY_SOME,
fix: fixer => fixer.replaceText(findProperty, 'some')
}
]
};
},
[arrayFilterCallSelector](filterCall) {
const filterProperty = filterCall.callee.property;
return {
node: filterProperty,
messageId: ERROR_ID_ARRAY_FILTER,
* fix(fixer) {
// `.filter` to `.some`
yield fixer.replaceText(filterProperty, 'some');
const methodNode = callExpression.callee.property;
return {
node: methodNode,
messageId: ERROR_ID_ARRAY_SOME,
data: {method: methodNode.name},
suggest: [
{
messageId: SUGGESTION_ID_ARRAY_SOME,
* fix(fixer) {
yield fixer.replaceText(methodNode, 'some');
const sourceCode = context.getSourceCode();
const lengthNode = filterCall.parent;
/*
Remove `.length`
`(( (( array.filter() )).length )) > (( 0 ))`
------------------------^^^^^^^
*/
yield removeMemberExpressionProperty(fixer, lengthNode, sourceCode);
if (!isCompare) {
return;
}
const compareNode = lengthNode.parent;
/*
Remove `> 0`
`(( (( array.filter() )).length )) > (( 0 ))`
----------------------------------^^^^^^^^^^
*/
yield fixer.removeRange([
getParenthesizedRange(lengthNode, sourceCode)[1],
compareNode.range[1]
]);
const parenthesizedRange = getParenthesizedRange(callExpression, context.sourceCode);
yield fixer.replaceTextRange([parenthesizedRange[1], callExpression.parent.range[1]], '');
// The `BinaryExpression` always ends with a number or `)`, no need check for ASI
}
};
if (callExpression.parent.operator === '!=' || callExpression.parent.operator === '!==') {
return;
}
yield fixer.insertTextBeforeRange(parenthesizedRange, '!');
},
},
],
};
},
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;
return {
node: filterProperty,
messageId: ERROR_ID_ARRAY_FILTER,
* fix(fixer) {
// `.filter` to `.some`
yield fixer.replaceText(filterProperty, 'some');
const {sourceCode} = context;
const lengthNode = binaryExpression.left;
/*
Remove `.length`
`(( (( array.filter() )).length )) > (( 0 ))`
------------------------^^^^^^^
*/
yield removeMemberExpressionProperty(fixer, lengthNode, sourceCode);
/*
Remove `> 0`
`(( (( array.filter() )).length )) > (( 0 ))`
----------------------------------^^^^^^^^^^
*/
yield fixer.removeRange([
getParenthesizedRange(lengthNode, sourceCode)[1],
binaryExpression.range[1],
]);
// The `BinaryExpression` always ends with a number or `)`, no need check for ASI
},
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -99,8 +152,8 @@ create: checkVueTemplate(create),

docs: {
description: 'Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`.'
description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`.',
},
fixable: 'code',
messages,
hasSuggestions: true
}
hasSuggestions: true,
},
};
'use strict';
const {isOpeningBracketToken, isClosingBracketToken, getStaticValue} = require('eslint-utils');
const isLiteralValue = require('./utils/is-literal-value.js');
const {isOpeningBracketToken, isClosingBracketToken, getStaticValue} = require('@eslint-community/eslint-utils');
const {
isParenthesized,
getParenthesizedRange,
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');
getParenthesizedText,
isNodeMatchesNameOrPath,
needsSemicolon,
shouldAddParenthesesToMemberExpressionObject,
isLeftHandSide,
} = require('./utils/index.js');
const {
getNegativeIndexLengthNode,
removeLengthNode
removeLengthNode,
} = require('./shared/negative-index.js');
const {methodCallSelector, callExpressionSelector, notLeftHandSideSelector} = require('./selectors/index.js');
const {removeMemberExpressionProperty, removeMethodCall} = require('./fix/index.js');
const {isLiteral, isCallExpression, isMethodCall} = require('./ast/index.js');

@@ -34,28 +33,21 @@ const MESSAGE_ID_NEGATIVE_INDEX = 'negative-index';

[MESSAGE_ID_GET_LAST_FUNCTION]: 'Prefer `.at(-1)` over `{{description}}(…)` to get the last element.',
[SUGGESTION_ID]: 'Use `.at(…)`.'
[SUGGESTION_ID]: 'Use `.at(…)`.',
};
const indexAccess = [
'MemberExpression',
'[optional!=true]',
'[computed!=false]',
notLeftHandSideSelector()
].join('');
const sliceCall = methodCallSelector({name: 'slice', min: 1, max: 2});
const stringCharAt = methodCallSelector({name: 'charAt', length: 1});
const isArguments = node => node.type === 'Identifier' && node.name === 'arguments';
const isLiteralNegativeInteger = node =>
node.type === 'UnaryExpression' &&
node.prefix &&
node.operator === '-' &&
node.argument.type === 'Literal' &&
Number.isInteger(node.argument.value) &&
node.argument.value > 0;
node.type === 'UnaryExpression'
&& node.prefix
&& node.operator === '-'
&& node.argument.type === 'Literal'
&& Number.isInteger(node.argument.value)
&& node.argument.value > 0;
const isZeroIndexAccess = node => {
const {parent} = node;
return parent.type === 'MemberExpression' &&
!parent.optional &&
parent.computed &&
parent.object === node &&
isLiteralValue(parent.property, 0);
return parent.type === 'MemberExpression'
&& !parent.optional
&& parent.computed
&& parent.object === node
&& isLiteral(parent.property, 0);
};

@@ -65,12 +57,12 @@

const {parent} = node;
return parent.type === 'MemberExpression' &&
!parent.optional &&
!parent.computed &&
parent.object === node &&
parent.property.type === 'Identifier' &&
parent.property.name === method &&
parent.parent.type === 'CallExpression' &&
parent.parent.callee === parent &&
!parent.parent.optional &&
parent.parent.arguments.length === 0;
return parent.type === 'MemberExpression'
&& !parent.optional
&& !parent.computed
&& parent.object === node
&& parent.property.type === 'Identifier'
&& parent.property.name === method
&& parent.parent.type === 'CallExpression'
&& parent.parent.callee === parent
&& !parent.parent.optional
&& parent.parent.arguments.length === 0;
};

@@ -109,5 +101,5 @@

if (
firstElementGetMethod === 'zero-index' ||
firstElementGetMethod === 'shift' ||
(startIndex === -1 && firstElementGetMethod === 'pop')
firstElementGetMethod === 'zero-index'
|| firstElementGetMethod === 'shift'
|| (startIndex === -1 && firstElementGetMethod === 'pop')
) {

@@ -121,4 +113,4 @@ return {safeToFix: true, firstElementGetMethod};

if (
isLiteralNegativeInteger(endIndexNode) &&
-endIndexNode.argument.value === startIndex + 1
isLiteralNegativeInteger(endIndexNode)
&& -endIndexNode.argument.value === startIndex + 1
) {

@@ -138,3 +130,3 @@ return {safeToFix: true, firstElementGetMethod};

'lodash.last',
'underscore.last'
'underscore.last',
];

@@ -146,31 +138,103 @@

getLastElementFunctions,
checkAllIndexAccess
checkAllIndexAccess,
} = {
getLastElementFunctions: [],
checkAllIndexAccess: false,
...context.options[0]
...context.options[0],
};
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;
}
// 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;
}
}
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);
}
// 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 (
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]]);
}
}
return {
node: indexNode,
messageId: lengthNode ? MESSAGE_ID_NEGATIVE_INDEX : MESSAGE_ID_INDEX,
const openingBracketToken = sourceCode.getTokenBefore(indexNode, isOpeningBracketToken);
yield fixer.replaceText(openingBracketToken, '.at(');
const closingBracketToken = sourceCode.getTokenAfter(indexNode, isClosingBracketToken);
yield fixer.replaceText(closingBracketToken, ')');
};
return problem;
});
// `string.charAt`
context.on('CallExpression', node => {
if (!isMethodCall(node, {
method: 'charAt',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const [indexNode] = node.arguments;
const lengthNode = getNegativeIndexLengthNode(indexNode, node.callee.object);
// `String#charAt` don't care about index value, we assume it's always number
if (!lengthNode && !checkAllIndexAccess) {
return;
}
return {
node: indexNode,
messageId: lengthNode ? MESSAGE_ID_STRING_CHAR_AT_NEGATIVE : MESSAGE_ID_STRING_CHAR_AT,
suggest: [{
messageId: SUGGESTION_ID,
* fix(fixer) {

@@ -181,109 +245,105 @@ if (lengthNode) {

const openingBracketToken = sourceCode.getTokenBefore(indexNode, isOpeningBracketToken);
yield fixer.replaceText(openingBracketToken, '.at(');
yield fixer.replaceText(node.callee.property, 'at');
},
}],
};
});
const isClosingBraceToken = sourceCode.getTokenAfter(indexNode, isClosingBracketToken);
yield fixer.replaceText(isClosingBraceToken, ')');
}
};
},
[stringCharAt](node) {
const [indexNode] = node.arguments;
const lengthNode = getNegativeIndexLengthNode(indexNode, node.callee.object);
// `.slice()`
context.on('CallExpression', sliceCall => {
if (!isMethodCall(sliceCall, {
method: 'slice',
minimumArguments: 1,
maximumArguments: 2,
optionalCall: false,
optionalMember: false,
})) {
return;
}
// `String#charAt` don't care about index value, we assume it's always number
if (!lengthNode && !checkAllIndexAccess) {
return;
}
const result = checkSliceCall(sliceCall);
if (!result) {
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 {safeToFix, firstElementGetMethod} = result;
yield fixer.replaceText(node.callee.property, 'at');
}
}]
};
},
[sliceCall](sliceCall) {
const result = checkSliceCall(sliceCall);
if (!result) {
return;
/** @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]);
}
const {safeToFix, firstElementGetMethod} = result;
// Remove `[0]`, `.shift()`, or `.pop()`
if (firstElementGetMethod === 'zero-index') {
yield removeMemberExpressionProperty(fixer, sliceCall.parent, sourceCode);
} else {
yield * removeMethodCall(fixer, sliceCall.parent.parent, sourceCode);
}
}
/** @param {import('eslint').Rule.RuleFixer} fixer */
function * fix(fixer) {
// `.slice` to `.at`
yield fixer.replaceText(sliceCall.callee.property, 'at');
const problem = {
node: sliceCall.callee.property,
messageId: MESSAGE_ID_SLICE,
};
// 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]);
}
if (safeToFix) {
problem.fix = fix;
} else {
problem.suggest = [{messageId: SUGGESTION_ID, fix}];
}
// Remove `[0]`, `.shift()`, or `.pop()`
if (firstElementGetMethod === 'zero-index') {
yield removeMemberExpressionProperty(fixer, sliceCall.parent, sourceCode);
} else {
yield * removeMethodCall(fixer, sliceCall.parent.parent, sourceCode);
}
}
return problem;
});
const problem = {
node: sliceCall.callee.property,
messageId: MESSAGE_ID_SLICE
};
context.on('CallExpression', node => {
if (!isCallExpression(node, {argumentsLength: 1, optional: false})) {
return;
}
if (safeToFix) {
problem.fix = fix;
} else {
problem.suggest = [{messageId: SUGGESTION_ID, fix}];
}
const matchedFunction = getLastFunctions.find(nameOrPath => isNodeMatchesNameOrPath(node.callee, nameOrPath));
if (!matchedFunction) {
return;
}
const problem = {
node: node.callee,
messageId: MESSAGE_ID_GET_LAST_FUNCTION,
data: {description: matchedFunction.trim()},
};
const [array] = node.arguments;
if (isArguments(array)) {
return problem;
},
[callExpressionSelector({length: 1})](node) {
const matchedFunction = getLastFunctions.find(nameOrPath => isNodeMatchesNameOrPath(node.callee, nameOrPath));
if (!matchedFunction) {
return;
}
}
return {
node: node.callee,
messageId: MESSAGE_ID_GET_LAST_FUNCTION,
data: {description: matchedFunction.trim()},
fix(fixer) {
const [array] = node.arguments;
problem.fix = function (fixer) {
let fixed = getParenthesizedText(array, sourceCode);
let fixed = getParenthesizedText(array, sourceCode);
if (
!isParenthesized(array, sourceCode)
&& shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
) {
fixed = `(${fixed})`;
}
if (
!isParenthesized(array, sourceCode) &&
shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
) {
fixed = `(${fixed})`;
}
fixed = `${fixed}.at(-1)`;
fixed = `${fixed}.at(-1)`;
const tokenBefore = sourceCode.getTokenBefore(node);
if (needsSemicolon(tokenBefore, sourceCode, fixed)) {
fixed = `;${fixed}`;
}
const tokenBefore = sourceCode.getTokenBefore(node);
if (needsSemicolon(tokenBefore, sourceCode, fixed)) {
fixed = `;${fixed}`;
}
return fixer.replaceText(node, fixed);
};
return fixer.replaceText(node, fixed);
}
};
}
};
return problem;
});
}

@@ -294,16 +354,17 @@

type: 'object',
additionalProperties: false,
properties: {
getLastElementFunctions: {
type: 'array',
uniqueItems: true
uniqueItems: true,
},
checkAllIndexAccess: {
type: 'boolean',
default: false
}
default: false,
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -314,9 +375,9 @@ create,

docs: {
description: 'Prefer `.at()` method for index access and `String#charAt()`.'
description: 'Prefer `.at()` method for index access and `String#charAt()`.',
},
fixable: 'code',
hasSuggestions: true,
schema,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {
matches,
methodCallSelector,
newExpressionSelector,
callExpressionSelector
} = require('./selectors/index.js');
isMethodCall,
isCallExpression,
isNewExpression,
} = require('./ast/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');

@@ -15,81 +15,112 @@ const MESSAGE_ID_DEFAULT = 'prefer-date';

[MESSAGE_ID_METHOD]: 'Prefer `Date.now()` over `Date#{{method}}()`.',
[MESSAGE_ID_NUMBER]: 'Prefer `Date.now()` over `Number(new Date())`.'
[MESSAGE_ID_NUMBER]: 'Prefer `Date.now()` over `Number(new Date())`.',
};
const createNewDateSelector = path => newExpressionSelector({path, name: 'Date', length: 0});
const operatorsSelector = (...operators) => matches(operators.map(operator => `[operator="${operator}"]`));
// `new Date()`
const newDateSelector = createNewDateSelector();
// `new Date().{getTime,valueOf}()`
const methodsSelector = [
methodCallSelector({
names: ['getTime', 'valueOf'],
length: 0
}),
createNewDateSelector('callee.object')
].join('');
// `{Number,BigInt}(new Date())`
const builtinObjectSelector = [
callExpressionSelector({names: ['Number', 'BigInt'], length: 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});
const getProblem = (node, problem) => ({
const getProblem = (node, problem, sourceCode) => ({
node,
messageId: MESSAGE_ID_DEFAULT,
fix: fixer => fixer.replaceText(node, 'Date.now()'),
...problem
* fix(fixer) {
yield fixer.replaceText(node, 'Date.now()');
if (node.type === 'UnaryExpression') {
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
}
},
...problem,
});
const create = () => {
return {
[methodsSelector](node) {
const method = node.callee.property;
return getProblem(node, {
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
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}
data: {method: method.name},
});
},
[builtinObjectSelector](node) {
const {name} = node.callee;
}
// `{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(node, {
messageId: MESSAGE_ID_NUMBER
return getProblem(callExpression, {
messageId: MESSAGE_ID_NUMBER,
});
}
return getProblem(node.arguments[0]);
},
[unaryExpressionsSelector](node) {
return getProblem(node.operator === '-' ? node.argument : node);
},
[assignmentExpressionSelector](node) {
return getProblem(node);
},
[binaryExpressionSelector](node) {
return getProblem(node);
return getProblem(callExpression.arguments[0]);
}
};
};
},
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,
);
}
},
AssignmentExpression(assignmentExpression) {
if (
assignmentExpression.operator !== '-='
&& assignmentExpression.operator !== '*='
&& assignmentExpression.operator !== '/='
&& assignmentExpression.operator !== '%='
&& assignmentExpression.operator !== '**='
) {
return;
}
if (isNewDate(assignmentExpression.right)) {
return getProblem(assignmentExpression.right);
}
},
* 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);
}
}
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -100,7 +131,7 @@ create,

docs: {
description: 'Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch.'
description: 'Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {findVariable} = require('eslint-utils');
const {findVariable} = require('@eslint-community/eslint-utils');
const {functionTypes} = require('./ast/index.js');

@@ -7,20 +8,10 @@ const MESSAGE_ID = 'preferDefaultParameters';

const assignmentSelector = [
'ExpressionStatement',
'[expression.type="AssignmentExpression"]'
].join('');
const declarationSelector = [
'VariableDeclaration',
'[declarations.0.type="VariableDeclarator"]'
].join('');
const isDefaultExpression = (left, right) =>
left &&
right &&
left.type === 'Identifier' &&
right.type === 'LogicalExpression' &&
(right.operator === '||' || right.operator === '??') &&
right.left.type === 'Identifier' &&
right.right.type === 'Literal';
left
&& right
&& left.type === 'Identifier'
&& right.type === 'LogicalExpression'
&& (right.operator === '||' || right.operator === '??')
&& right.left.type === 'Identifier'
&& right.right.type === 'Literal';

@@ -115,3 +106,3 @@ const containsCallExpression = (sourceCode, node) => {

sourceCode.getIndexFromLoc({line, column: 0}),
sourceCode.getIndexFromLoc({line: line + 1, column: 0})
sourceCode.getIndexFromLoc({line: line + 1, column: 0}),
]);

@@ -123,3 +114,3 @@ }

node.range[0],
node.range[1] + 1
node.range[1] + 1,
]);

@@ -131,4 +122,5 @@ }

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const functionStack = [];

@@ -146,3 +138,3 @@

left: {name: secondId},
right: {raw: literal}
right: {raw: literal},
} = right;

@@ -155,7 +147,7 @@

const variable = findVariable(context.getScope(), secondId);
const variable = findVariable(sourceCode.getScope(node), secondId);
// This was reported https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1122
// But can't reproduce, just ignore this case
/* istanbul ignore next */
/* c8 ignore next 3 */
if (!variable) {

@@ -168,10 +160,10 @@ return;

const parameter = params.find(parameter =>
parameter.type === 'Identifier' &&
parameter.name === secondId
parameter.type === 'Identifier'
&& parameter.name === secondId,
);
if (
hasSideEffects(sourceCode, currentFunction, node) ||
hasExtraReferences(assignment, references, left) ||
!isLastParameter(params, parameter)
hasSideEffects(sourceCode, currentFunction, node)
|| hasExtraReferences(assignment, references, left)
|| !isLastParameter(params, parameter)
) {

@@ -181,5 +173,5 @@ return;

const replacement = needsParentheses(sourceCode, currentFunction) ?
`(${firstId} = ${literal})` :
`${firstId} = ${literal}`;
const replacement = needsParentheses(sourceCode, currentFunction)
? `(${firstId} = ${literal})`
: `${firstId} = ${literal}`;

@@ -193,28 +185,30 @@ return {

fixer.replaceText(parameter, replacement),
fixDefaultExpression(fixer, sourceCode, node)
]
}]
fixDefaultExpression(fixer, sourceCode, node),
],
}],
};
};
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);
}
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -225,11 +219,11 @@ create,

docs: {
description: 'Prefer default parameters over reassignment.'
description: 'Prefer default parameters over reassignment.',
},
fixable: 'code',
hasSuggestions: true,
messages: {
[MESSAGE_ID]: 'Prefer default parameters over reassignment.',
[MESSAGE_ID_SUGGEST]: 'Replace reassignment with default parameter.'
[MESSAGE_ID_SUGGEST]: 'Replace reassignment with default parameter.',
},
hasSuggestions: true
}
},
};
'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');
const MESSAGE_ID = 'prefer-dom-node-append';
const messages = {
[MESSAGE_ID]: 'Prefer `Node#append()` over `Node#appendChild()`.'
[MESSAGE_ID]: 'Prefer `Node#append()` over `Node#appendChild()`.',
};
const selector = [
methodCallSelector({
name: 'appendChild',
length: 1
}),
notDomNodeSelector('callee.object'),
notDomNodeSelector('arguments.0')
].join('');
const create = () => {
return {
[selector](node) {
const fix = isValueNotUsable(node) ?
fixer => fixer.replaceText(node.callee.property, 'append') :
undefined;
return {
node,
messageId: MESSAGE_ID,
fix
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
CallExpression(node) {
if (
!isMethodCall(node, {
method: 'appendChild',
argumentsLength: 1,
optionalCall: false,
})
|| isNodeValueNotDomNode(node.callee.object)
|| isNodeValueNotDomNode(node.arguments[0])
) {
return;
}
};
};
const fix = isValueNotUsable(node)
? fixer => fixer.replaceText(node.callee.property, 'append')
: undefined;
return {
node,
messageId: MESSAGE_ID,
fix,
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -39,7 +43,7 @@ create,

docs: {
description: 'Prefer `Node#append()` over `Node#appendChild()`.'
description: 'Prefer `Node#append()` over `Node#appendChild()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const isValidVariableName = require('./utils/is-valid-variable-name.js');
const quoteString = require('./utils/quote-string.js');
const {methodCallSelector} = require('./selectors/index.js');
const {isIdentifierName} = require('@babel/helper-validator-identifier');
const {
escapeString,
hasOptionalChainElement,
isValueNotUsable,
} = require('./utils/index.js');
const {isMethodCall, isStringLiteral, isExpressionStatement} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-dom-node-dataset';
const messages = {
[MESSAGE_ID]: 'Prefer `.dataset` over `setAttribute(…)`.'
[MESSAGE_ID]: 'Prefer `.dataset` over `{{method}}(…)`.',
};
const selector = [
methodCallSelector({
name: 'setAttribute',
length: 2
}),
'[arguments.0.type="Literal"]'
].join('');
const parseNodeText = (context, argument) => context.getSourceCode().getText(argument);
const dashToCamelCase = string => string.replace(/-[a-z]/g, s => s[1].toUpperCase());
const fix = (context, node, fixer) => {
let [name, value] = node.arguments;
const calleeObject = parseNodeText(context, node.callee.object);
function getFix(callExpression, context) {
const method = callExpression.callee.property.name;
name = dashToCamelCase(name.value.slice(5));
value = parseNodeText(context, value);
// `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;
}
const replacement = `${calleeObject}.dataset${
isValidVariableName(name) ?
`.${name}` :
`[${quoteString(name)}]`
} = ${value}`;
// `element.setAttribute(…)` returns `undefined`, but `AssignmentExpression` returns value of RHS
if (method === 'setAttribute' && !isValueNotUsable(callExpression)) {
return;
}
return fixer.replaceText(node, replacement);
};
if (method === 'removeAttribute' && !isExpressionStatement(callExpression.parent)) {
return;
}
const create = context => {
return {
[selector](node) {
const name = node.arguments[0].value;
return fixer => {
const [nameNode] = callExpression.arguments;
const name = dashToCamelCase(nameNode.value.toLowerCase().slice(5));
const {sourceCode} = context;
let text = '';
const datasetText = `${sourceCode.getText(callExpression.callee.object)}.dataset`;
switch (method) {
case 'setAttribute':
case 'getAttribute':
case 'removeAttribute': {
text = isIdentifierName(name) ? `.${name}` : `[${escapeString(name, nameNode.raw.charAt(0))}]`;
text = `${datasetText}${text}`;
if (method === 'setAttribute') {
text += ` = ${sourceCode.getText(callExpression.arguments[1])}`;
} else if (method === 'removeAttribute') {
text = `delete ${text}`;
}
if (typeof name !== 'string' || !name.startsWith('data-') || name === 'data-') {
return;
/*
For non-exists attribute, `element.getAttribute('data-foo')` returns `null`,
but `element.dataset.foo` returns `undefined`, switch to suggestions if necessary
*/
break;
}
return {
node,
messageId: MESSAGE_ID,
fix: fixer => fix(context, node, fixer)
};
case 'hasAttribute': {
text = `Object.hasOwn(${datasetText}, ${escapeString(name, nameNode.raw.charAt(0))})`;
break;
}
// No default
}
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: callExpression,
messageId: MESSAGE_ID,
data: {method: callExpression.callee.property.name},
fix: getFix(callExpression, context),
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -62,7 +115,7 @@ create,

docs: {
description: 'Prefer using `.dataset` on DOM elements over `.setAttribute(…)`.'
description: 'Prefer using `.dataset` on DOM elements over calling attribute methods.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isParenthesized, hasSideEffect} = require('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 {isParenthesized, hasSideEffect} = require('@eslint-community/eslint-utils');
const {isMethodCall} = require('./ast/index.js');
const {
getParenthesizedText,
isNodeValueNotDomNode,
isValueNotUsable,
needsSemicolon,
shouldAddParenthesesToMemberExpressionObject,
} = require('./utils/index.js');

@@ -13,19 +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({
name: 'removeChild',
length: 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;

@@ -36,10 +52,12 @@ const childNode = node.arguments[0];

node,
messageId: ERROR_MESSAGE_ID
messageId: ERROR_MESSAGE_ID,
};
const fix = fixer => {
const isOptionalParentNode = isMemberExpressionOptionalObject(parentNode);
const createFix = (optional = false) => fixer => {
let childNodeText = getParenthesizedText(childNode, sourceCode);
if (
!isParenthesized(childNode, sourceCode) &&
shouldAddParenthesesToMemberExpressionObject(childNode, sourceCode)
!isParenthesized(childNode, sourceCode)
&& shouldAddParenthesesToMemberExpressionObject(childNode, sourceCode)
) {

@@ -53,21 +71,44 @@ childNodeText = `(${childNodeText})`;

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;
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -78,8 +119,8 @@ create,

docs: {
description: 'Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`.'
description: 'Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {memberExpressionSelector} = require('./selectors/index.js');
const {isMemberExpression} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-dom-node-text-content';
const ERROR = 'error';
const SUGGESTION = 'suggestion';
const messages = {
[MESSAGE_ID]: 'Prefer `.textContent` over `.innerText`.'
[ERROR]: 'Prefer `.textContent` over `.innerText`.',
[SUGGESTION]: 'Switch to `.textContent`.',
};
const selector = `${memberExpressionSelector('innerText')} > .property`;
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
MemberExpression(memberExpression) {
if (
!isMemberExpression(memberExpression, {
property: 'innerText',
})
) {
return;
}
const create = () => {
return {
[selector](node) {
return {
node,
messageId: MESSAGE_ID,
fix: fixer => fixer.replaceText(node, 'textContent')
};
const node = memberExpression.property;
return {
node,
messageId: ERROR,
suggest: [
{
messageId: SUGGESTION,
fix: fixer => fixer.replaceText(node, 'textContent'),
},
],
};
},
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 {
node,
messageId: ERROR,
suggest: [
{
messageId: SUGGESTION,
fix: fixer => fixer.replaceText(
node,
node.parent.shorthand ? 'textContent: innerText' : 'textContent',
),
},
],
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -28,7 +70,7 @@ create,

docs: {
description: 'Prefer `.textContent` over `.innerText`.'
description: 'Prefer `.textContent` over `.innerText`.',
},
fixable: 'code',
messages
}
hasSuggestions: true,
messages,
},
};
'use strict';
const isMethodNamed = require('./utils/is-method-named.js');
const isLiteralValue = require('./utils/is-literal-value.js');
const simpleArraySearchRule = require('./shared/simple-array-search-rule.js');
const {isLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-includes';
const messages = {
[MESSAGE_ID]: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.'
[MESSAGE_ID]: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.',
};

@@ -14,7 +14,7 @@ // Ignore {_,lodash,underscore}.indexOf

const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1;
const isLiteralZero = node => isLiteralValue(node, 0);
const isLiteralZero = node => isLiteral(node, 0);
const isNegativeResult = node => ['===', '==', '<'].includes(node.operator);
const getProblem = (context, node, target, argumentsNodes) => {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const memberExpressionNode = target.parent;

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

messageId: MESSAGE_ID,
fix: fixer => {
fix(fixer) {
const replacement = `${isNegativeResult(node) ? '!' : ''}${targetSource}.includes(${argumentsSource.join(', ')})`;
return fixer.replaceText(node, replacement);
}
},
};

@@ -44,7 +44,10 @@ };

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

@@ -70,4 +73,4 @@

if (
(['!==', '!=', '>', '===', '=='].includes(operator) && isNegativeOne(right)) ||
(['>=', '<'].includes(operator) && isLiteralZero(right))
(['!==', '!=', '>', '===', '=='].includes(operator) && isNegativeOne(right))
|| (['>=', '<'].includes(operator) && isLiteralZero(right))
) {

@@ -78,9 +81,9 @@ return getProblem(

target,
argumentsNodes
argumentsNodes,
);
}
},
...includesOverSomeRule.createListeners(context)
});
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -91,11 +94,11 @@ create,

docs: {
description: 'Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence.'
description: 'Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence.',
},
fixable: 'code',
hasSuggestions: true,
messages: {
...messages,
...includesOverSomeRule.messages
...includesOverSomeRule.messages,
},
hasSuggestions: true
}
},
};
'use strict';
const quoteString = require('./utils/quote-string.js');
const escapeString = require('./utils/escape-string.js');
const translateToKey = require('./shared/event-keys.js');
const {isNumberLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-keyboard-event-key';
const messages = {
[MESSAGE_ID]: 'Use `.key` instead of `.{{name}}`.'
[MESSAGE_ID]: 'Use `.key` instead of `.{{name}}`.',
};

@@ -13,29 +14,27 @@

'charCode',
'which'
'which',
]);
const isPropertyNamedAddEventListener = node =>
node &&
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'MemberExpression' &&
node.callee.property &&
node.callee.property.name === 'addEventListener';
node?.type === 'CallExpression'
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'addEventListener';
const getEventNodeAndReferences = (context, node) => {
const eventListener = getMatchingAncestorOfType(node, 'CallExpression', isPropertyNamedAddEventListener);
const callback = eventListener && eventListener.arguments && eventListener.arguments[1];
switch (callback && callback.type) {
const callback = eventListener?.arguments[1];
switch (callback?.type) {
case 'ArrowFunctionExpression':
case 'FunctionExpression': {
const eventVariable = context.getDeclaredVariables(callback)[0];
const references = eventVariable && eventVariable.references;
const eventVariable = context.sourceCode.getDeclaredVariables(callback)[0];
const references = eventVariable?.references;
return {
event: callback.params && callback.params[0],
references
event: callback.params[0],
references,
};
}
default:
default: {
return {};
}
}

@@ -45,7 +44,4 @@ };

const isPropertyOf = (node, eventNode) =>
node &&
node.parent &&
node.parent.type === 'MemberExpression' &&
node.parent.object &&
node.parent.object === eventNode;
node?.parent?.type === 'MemberExpression'
&& node.parent.object === eventNode;

@@ -72,3 +68,3 @@ // The third argument is a condition function, as one passed to `Array#filter()`

/* istanbul ignore else */
/* c8 ignore next 3 */
if (level === 0) {

@@ -86,9 +82,17 @@ return current;

const {right = {}, operator} = nearestIf.test;
const isTestingEquality = operator === '==' || operator === '===';
const isRightValid = isTestingEquality && right.type === 'Literal' && typeof right.value === 'number';
const {type, operator, right} = nearestIf.test;
if (
!(
type === 'BinaryExpression'
&& (operator === '==' || operator === '===')
&& isNumberLiteral(right)
)
) {
return;
}
// Either a meta key or a printable character
const keyCode = translateToKey[right.value] || String.fromCharCode(right.value);
const key = translateToKey[right.value] || String.fromCodePoint(right.value);
// And if we recognize the `.keyCode`
if (!isRightValid || !keyCode) {
if (!key) {
return;

@@ -100,3 +104,3 @@ }

fixer.replaceText(node, 'key'),
fixer.replaceText(right, quoteString(keyCode))
fixer.replaceText(right, escapeString(key)),
];

@@ -109,65 +113,70 @@ };

node,
fix: fix(node)
fix: fix(node),
});
const create = context => {
return {
'Identifier:matches([name="keyCode"], [name="charCode"], [name="which"])'(node) {
// Normal case when usage is direct -> `event.keyCode`
const {event, references} = getEventNodeAndReferences(context, node);
if (!event) {
return;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
Identifier(node) {
if (
node.name !== 'keyCode'
&& node.name !== 'charCode'
&& node.name !== 'which'
) {
return;
}
if (
references &&
references.some(reference => isPropertyOf(node, reference.identifier))
) {
return getProblem(node);
}
},
// Normal case when usage is direct -> `event.keyCode`
const {event, references} = getEventNodeAndReferences(context, node);
if (!event) {
return;
}
Property(node) {
// Destructured case
const propertyName = node.value && node.value.name;
if (!keys.has(propertyName)) {
return;
}
if (
references
&& references.some(reference => isPropertyOf(node, reference.identifier))
) {
return getProblem(node);
}
},
const {event, references} = getEventNodeAndReferences(context, node);
if (!event) {
return;
}
Property(node) {
// Destructured case
const propertyName = node.value.name;
if (!keys.has(propertyName)) {
return;
}
const nearestVariableDeclarator = getMatchingAncestorOfType(
node,
'VariableDeclarator'
);
const initObject =
nearestVariableDeclarator &&
nearestVariableDeclarator.init &&
nearestVariableDeclarator.init;
const {event, references} = getEventNodeAndReferences(context, node);
if (!event) {
return;
}
// Make sure initObject is a reference of eventVariable
if (
references &&
references.some(reference => reference.identifier === initObject)
) {
return getProblem(node.value);
}
const nearestVariableDeclarator = getMatchingAncestorOfType(
node,
'VariableDeclarator',
);
const initObject = nearestVariableDeclarator?.init;
// When the event parameter itself is destructured directly
const isEventParameterDestructured = event.type === 'ObjectPattern';
if (isEventParameterDestructured) {
// Check for properties
for (const property of event.properties) {
if (property === node) {
return getProblem(node.value);
}
// Make sure initObject is a reference of eventVariable
if (
references
&& references.some(reference => reference.identifier === initObject)
) {
return getProblem(node.value);
}
// When the event parameter itself is destructured directly
const isEventParameterDestructured = event.type === 'ObjectPattern';
if (isEventParameterDestructured) {
// Check for properties
for (const property of event.properties) {
if (property === node) {
return getProblem(node.value);
}
}
}
};
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -178,7 +187,7 @@ create,

docs: {
description: 'Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`.'
description: 'Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {hasSideEffect} = require('eslint-utils');
const {hasSideEffect} = require('@eslint-community/eslint-utils');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isLiteral} = require('./ast/index.js');

@@ -10,25 +12,14 @@ const ERROR_BITWISE = 'error-bitwise';

[ERROR_BITWISE_NOT]: 'Use `Math.trunc` instead of `~~`.',
[SUGGESTION_BITWISE]: 'Replace `{{operator}} {{value}}` with `Math.trunc`.'
[SUGGESTION_BITWISE]: 'Replace `{{operator}} {{value}}` with `Math.trunc`.',
};
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;

@@ -41,60 +32,69 @@ 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 (!isAssignment || !hasSideEffect(left, sourceCode)) {
const fix = fixer => {
let fixed = mathTruncFunctionCall(left);
if (isAssignment) {
fixed = `${sourceCode.getText(left)} = ${fixed}`;
}
if (operator === '|') {
problem.suggest = [
{
messageId: SUGGESTION_BITWISE,
fix,
},
];
} else {
problem.fix = fix;
}
}
return fixer.replaceText(node, fixed);
};
return problem;
});
if (operator === '|') {
problem.suggest = [
{
messageId: SUGGESTION_BITWISE,
data: {
operator,
value: right.raw
},
fix
}
];
} else {
problem.fix = fix;
}
}
return problem;
},
[bitwiseNotUnaryExpressionSelector]: node => {
// 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 => fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument))
* fix(fixer) {
yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
},
};
}
};
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -105,8 +105,8 @@ create,

docs: {
description: 'Enforce the use of `Math.trunc` instead of bitwise operators.'
description: 'Enforce the use of `Math.trunc` instead of bitwise operators.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'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');

@@ -9,22 +9,8 @@ const messages = {

insertAdjacentTextOrInsertAdjacentElement:
'Prefer `{{reference}}.{{preferredMethod}}({{content}})` over `{{reference}}.{{method}}({{position}}, {{content}})`.'
'Prefer `{{reference}}.{{preferredMethod}}({{content}})` over `{{reference}}.{{method}}({{position}}, {{content}})`.',
};
const replaceChildOrInsertBeforeSelector = [
methodCallSelector({
names: ['replaceChild', 'insertBefore'],
length: 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 forbiddenMethods = new Map([
const disallowedMethods = new Map([
['replaceChild', 'replaceWith'],
['insertBefore', 'before']
['insertBefore', 'before'],
]);

@@ -36,10 +22,10 @@

const [newChildNode, oldChildNode] = node.arguments.map(({name}) => name);
const preferredMethod = forbiddenMethods.get(method);
const preferredMethod = disallowedMethods.get(method);
const fix = isValueNotUsable(node) ?
fixer => fixer.replaceText(
const fix = isValueNotUsable(node)
? fixer => fixer.replaceText(
node,
`${oldChildNode}.${preferredMethod}(${newChildNode})`
) :
undefined;
`${oldChildNode}.${preferredMethod}(${newChildNode})`,
)
: undefined;

@@ -54,21 +40,8 @@ return {

newChildNode,
oldChildNode
oldChildNode,
},
fix
fix,
};
};
const insertAdjacentTextOrInsertAdjacentElementSelector = [
methodCallSelector({
names: ['insertAdjacentText', 'insertAdjacentElement'],
length: 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([

@@ -78,3 +51,3 @@ ['beforebegin', 'before'],

['beforeend', 'append'],
['afterend', 'after']
['afterend', 'after'],
]);

@@ -93,11 +66,12 @@

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);
const fix = method === 'insertAdjacentElement' && !isValueNotUsable(node) ?
undefined :
const fix = method === 'insertAdjacentElement' && !isValueNotUsable(node)
? undefined
// TODO: make a better fix, don't touch reference
fixer => fixer.replaceText(
: fixer => fixer.replaceText(
node,
`${reference}.${preferredMethod}(${content})`
`${reference}.${preferredMethod}(${content})`,
);

@@ -112,20 +86,52 @@

preferredMethod,
position: context.getSource(positionNode),
content
position: sourceCode.getText(positionNode),
content,
},
fix
fix,
};
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
return {
[replaceChildOrInsertBeforeSelector](node) {
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);
},
[insertAdjacentTextOrInsertAdjacentElementSelector](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} */
module.exports = {

@@ -136,7 +142,7 @@ create,

docs: {
description: 'Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`.'
description: 'Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isOpeningParenToken} = require('eslint-utils');
const {isOpeningParenToken} = require('@eslint-community/eslint-utils');
const isShadowed = require('./utils/is-shadowed.js');
const removeSpacesAfter = require('./utils/remove-spaces-after.js');
const isStaticRequire = require('./utils/is-static-require.js');
const replaceReferenceIdentifier = require('./utils/replace-reference-identifier.js');
const {getParentheses} = require('./utils/parentheses.js');
const assertToken = require('./utils/assert-token.js');
const {referenceIdentifierSelector} = require('./selectors/index.js');
const {isStaticRequire, isReferenceIdentifier, isFunction} = require('./ast/index.js');
const {
removeParentheses,
replaceReferenceIdentifier,
removeSpacesAfter,
} = require('./fix/index.js');

@@ -14,2 +15,3 @@ const ERROR_USE_STRICT_DIRECTIVE = 'error/use-strict-directive';

const ERROR_IDENTIFIER = 'error/identifier';
const SUGGESTION_USE_STRICT_DIRECTIVE = 'suggestion/use-strict-directive';
const SUGGESTION_DIRNAME = 'suggestion/dirname';

@@ -23,25 +25,9 @@ const SUGGESTION_FILENAME = 'suggestion/filename';

[ERROR_IDENTIFIER]: 'Do not use "{{name}}".',
[SUGGESTION_USE_STRICT_DIRECTIVE]: 'Remove "use strict" directive.',
[SUGGESTION_DIRNAME]: 'Replace "__dirname" with `"…(import.meta.url)"`.',
[SUGGESTION_FILENAME]: 'Replace "__filename" with `"…(import.meta.url)"`.',
[SUGGESTION_IMPORT]: 'Switch to `import`.',
[SUGGESTION_EXPORT]: 'Switch to `export`.'
[SUGGESTION_EXPORT]: 'Switch to `export`.',
};
const identifierSelector = referenceIdentifierSelector([
'exports',
'require',
'module',
'__filename',
'__dirname'
]);
function * removeParentheses(nodeOrNodes, fixer, sourceCode) {
for (const node of Array.isArray(nodeOrNodes) ? nodeOrNodes : [nodeOrNodes]) {
const parentheses = getParentheses(node, sourceCode);
for (const token of parentheses) {
yield fixer.remove(token);
}
}
}
function fixRequireCall(node, sourceCode) {

@@ -56,3 +42,3 @@ if (!isStaticRequire(node.parent) || node.parent.callee !== node) {

callee,
arguments: [source]
arguments: [source],
} = requireCall;

@@ -66,3 +52,3 @@

callee,
isOpeningParenToken
isOpeningParenToken,
);

@@ -72,3 +58,6 @@ yield fixer.replaceText(openingParenthesisToken, ' ');

yield fixer.remove(closingParenthesisToken);
yield * removeParentheses([callee, requireCall, source], fixer, sourceCode);
for (const node of [callee, requireCall, source]) {
yield * removeParentheses(node, fixer, sourceCode);
}
};

@@ -80,22 +69,22 @@ }

if (
parent.type === 'VariableDeclarator' &&
parent.init === requireCall &&
(
parent.id.type === 'Identifier' ||
(
parent.id.type === 'ObjectPattern' &&
parent.id.properties.every(
parent.type === 'VariableDeclarator'
&& parent.init === requireCall
&& (
parent.id.type === 'Identifier'
|| (
parent.id.type === 'ObjectPattern'
&& parent.id.properties.every(
({type, key, value, computed}) =>
type === 'Property' &&
!computed &&
value.type === 'Identifier' &&
key.type === 'Identifier'
type === 'Property'
&& !computed
&& value.type === 'Identifier'
&& key.type === 'Identifier',
)
)
) &&
parent.parent.type === 'VariableDeclaration' &&
parent.parent.kind === 'const' &&
parent.parent.declarations.length === 1 &&
parent.parent.declarations[0] === parent &&
parent.parent.parent.type === 'Program'
)
&& parent.parent.type === 'VariableDeclaration'
&& parent.parent.kind === 'const'
&& parent.parent.declarations.length === 1
&& parent.parent.declarations[0] === parent
&& parent.parent.parent.type === 'Program'
) {

@@ -110,3 +99,3 @@ const declarator = parent;

expected: {type: 'Keyword', value: 'const'},
ruleId: 'prefer-module'
ruleId: 'prefer-module',
});

@@ -118,3 +107,3 @@ yield fixer.replaceText(constToken, 'import');

expected: {type: 'Punctuator', value: '='},
ruleId: 'prefer-module'
ruleId: 'prefer-module',
});

@@ -128,3 +117,3 @@ yield removeSpacesAfter(id, sourceCode, fixer);

callee,
isOpeningParenToken
isOpeningParenToken,
);

@@ -135,3 +124,5 @@ yield fixer.remove(openingParenthesisToken);

yield * removeParentheses([callee, requireCall, source], fixer, sourceCode);
for (const node of [callee, requireCall, source]) {
yield * removeParentheses(node, fixer, sourceCode);
}

@@ -150,3 +141,3 @@ if (id.type === 'Identifier') {

expected: {type: 'Punctuator', value: ':'},
ruleId: 'prefer-module'
ruleId: 'prefer-module',
});

@@ -163,23 +154,32 @@ yield removeSpacesAfter(key, sourceCode, fixer);

const isTopLevelAssignment = node =>
node.parent.type === 'AssignmentExpression' &&
node.parent.operator === '=' &&
node.parent.left === node &&
node.parent.parent.type === 'ExpressionStatement' &&
node.parent.parent.parent.type === 'Program';
node.parent.type === 'AssignmentExpression'
&& node.parent.operator === '='
&& node.parent.left === node
&& node.parent.parent.type === 'ExpressionStatement'
&& node.parent.parent.parent.type === 'Program';
const isNamedExport = node =>
node.parent.type === 'MemberExpression' &&
!node.parent.optional &&
!node.parent.computed &&
node.parent.object === node &&
node.parent.property.type === 'Identifier' &&
isTopLevelAssignment(node.parent) &&
node.parent.parent.right.type === 'Identifier';
node.parent.type === 'MemberExpression'
&& !node.parent.optional
&& !node.parent.computed
&& node.parent.object === node
&& node.parent.property.type === 'Identifier'
&& isTopLevelAssignment(node.parent)
&& node.parent.parent.right.type === 'Identifier';
const isModuleExports = node =>
node.parent.type === 'MemberExpression' &&
!node.parent.optional &&
!node.parent.computed &&
node.parent.object === node &&
node.parent.property.type === 'Identifier' &&
node.parent.property.name === 'exports';
node.parent.type === 'MemberExpression'
&& !node.parent.optional
&& !node.parent.computed
&& node.parent.object === node
&& node.parent.property.type === 'Identifier'
&& 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) {

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

yield * removeParentheses([node.parent, node], fixer, sourceCode);
for (const currentNode of [node.parent, node]) {
yield * removeParentheses(currentNode, fixer, sourceCode);
}
};

@@ -229,50 +231,81 @@ }

function create(context) {
const filename = context.getPhysicalFilename();
const filename = context.filename.toLowerCase();
if (filename.toLowerCase().endsWith('.cjs')) {
return {};
if (filename.endsWith('.cjs')) {
return;
}
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
'ExpressionStatement[directive="use strict"]'(node) {
context.on('ExpressionStatement', node => {
if (node.directive !== 'use strict') {
return;
}
const problem = {node, messageId: ERROR_USE_STRICT_DIRECTIVE};
const fix = function * (fixer) {
yield fixer.remove(node);
yield removeSpacesAfter(node, sourceCode, fixer);
};
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 {
node,
messageId: ERROR_USE_STRICT_DIRECTIVE,
* fix(fixer) {
yield fixer.remove(node);
yield removeSpacesAfter(node, sourceCode, fixer);
}
};
},
'ReturnStatement:not(:function ReturnStatement)'(node) {
return {
node: sourceCode.getFirstToken(node),
messageId: ERROR_GLOBAL_RETURN
messageId: ERROR_GLOBAL_RETURN,
};
},
[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,
}];

@@ -282,49 +315,39 @@ 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;
}
default:
break;
}
return problem;
default:
}
};
return problem;
});
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -335,8 +358,8 @@ create,

docs: {
description: 'Prefer JavaScript modules (ESM) over CommonJS.'
description: 'Prefer JavaScript modules (ESM) over CommonJS.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const isLiteralValue = require('./utils/is-literal-value.js');
const {
getNegativeIndexLengthNode,
removeLengthNode
removeLengthNode,
} = require('./shared/negative-index.js');
const typedArray = require('./shared/typed-array.js');
const {isLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-negative-index';
const messages = {
[MESSAGE_ID]: 'Prefer negative index over length minus index for `{{method}}`.'
[MESSAGE_ID]: 'Prefer negative index over length minus index for `{{method}}`.',
};

@@ -22,18 +23,8 @@

'ArrayBuffer',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'BigInt64Array',
'BigUint64Array'
...typedArray,
// `{Blob,File}#slice()` are not generally used
// 'Blob'
// 'File'
])
}
]),
},
],

@@ -45,7 +36,16 @@ [

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

@@ -55,6 +55,18 @@ {

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

@@ -66,5 +78,4 @@

if (
type === 'MemberExpression' &&
property &&
property.type === 'Identifier'
type === 'MemberExpression'
&& property.type === 'Identifier'
) {

@@ -86,3 +97,3 @@ return property.name;

target,
argumentsNodes
argumentsNodes,
};

@@ -108,18 +119,18 @@ }

if (
// [].{slice,splice}
// `[].{slice,splice,toSpliced,at,with}`
(
parentCallee.type === 'ArrayExpression' &&
parentCallee.elements.length === 0
) ||
// ''.slice
(
method === 'slice' &&
isLiteralValue(parentCallee, '')
) ||
parentCallee.type === 'ArrayExpression'
&& parentCallee.elements.length === 0
)
// `''.slice`
|| (
method === 'slice'
&& isLiteral(parentCallee, '')
)
// {Array,String...}.prototype.slice
// Array.prototype.splice
(
getMemberName(parentCallee) === 'prototype' &&
parentCallee.object.type === 'Identifier' &&
supportObjects.has(parentCallee.object.name)
|| (
getMemberName(parentCallee) === 'prototype'
&& parentCallee.object.type === 'Identifier'
&& supportObjects.has(parentCallee.object.name)
)

@@ -143,3 +154,3 @@ ) {

target,
argumentsNodes
argumentsNodes,
};

@@ -149,4 +160,9 @@ }

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
'CallExpression[callee.type="MemberExpression"]': node => {
CallExpression(node) {
if (node.callee.type !== 'MemberExpression') {
return;
}
const parsed = parse(node);

@@ -161,3 +177,3 @@

target,
argumentsNodes
argumentsNodes,
} = parsed;

@@ -179,11 +195,12 @@

* fix(fixer) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
for (const node of removableNodes) {
yield removeLengthNode(node, fixer, sourceCode);
}
}
},
};
}
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -194,7 +211,7 @@ create,

docs: {
description: 'Prefer negative index over `.length - index` for `{String,Array,TypedArray}#slice()`, `Array#splice()` and `Array#at()`.'
description: 'Prefer negative index over `.length - index` when possible.',
},
fixable: 'code',
messages
}
messages,
},
};
'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');
const MESSAGE_ID = 'prefer-node-protocol';
const messages = {
[MESSAGE_ID]: 'Prefer `node:{{moduleName}}` over `{{moduleName}}`.'
[MESSAGE_ID]: 'Prefer `node:{{moduleName}}` over `{{moduleName}}`.',
};
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;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {checkRequire} = {
checkRequire: false,
...context.options[0]
};
const selectors = [importExportSourceSelector];
if (checkRequire) {
selectors.push(STATIC_REQUIRE_SOURCE_SELECTOR);
}
const {value} = node;
return {
[matches(selectors)](node) {
const {value} = node;
if (
typeof value !== 'string' ||
value.startsWith('node:') ||
!isBuiltinModule(value)
) {
return;
}
const firstCharacterIndex = node.range[0] + 1;
return {
node,
messageId: MESSAGE_ID,
data: {moduleName: value},
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => fixer.insertTextBeforeRange([firstCharacterIndex, firstCharacterIndex], 'node:')
};
if (
typeof value !== 'string'
|| value.startsWith('node:')
|| !isBuiltinModule(value)
) {
return;
}
};
};
const schema = [
{
type: 'object',
properties: {
checkRequire: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}
];
return {
node,
messageId: MESSAGE_ID,
data: {moduleName: value},
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => replaceStringLiteral(fixer, node, 'node:', 0, 0),
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -68,8 +56,7 @@ create,

docs: {
description: 'Prefer using the `node:` protocol when importing Node.js builtin modules.'
description: 'Prefer using the `node:` protocol when importing Node.js builtin modules.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'use strict';
const isShadowed = require('./utils/is-shadowed.js');
const replaceReferenceIdentifier = require('./utils/replace-reference-identifier.js');
const {
referenceIdentifierSelector,
callExpressionSelector
} = require('./selectors/index.js');
const {GlobalReferenceTracker} = require('./utils/global-reference-tracker.js');
const {replaceReferenceIdentifier} = require('./fix/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const isLeftHandSide = require('./utils/is-left-hand-side.js');
const METHOD_ERROR_MESSAGE_ID = 'method-error';
const METHOD_SUGGESTION_MESSAGE_ID = 'method-suggestion';
const PROPERTY_ERROR_MESSAGE_ID = 'property-error';
const MESSAGE_ID_ERROR = 'error';
const MESSAGE_ID_SUGGESTION = 'suggestion';
const messages = {
[METHOD_ERROR_MESSAGE_ID]: 'Prefer `Number.{{name}}()` over `{{name}}()`.',
[METHOD_SUGGESTION_MESSAGE_ID]: 'Replace `{{name}}()` with `Number.{{name}}()`.',
[PROPERTY_ERROR_MESSAGE_ID]: 'Prefer `Number.{{property}}` over `{{identifier}}`.'
[MESSAGE_ID_ERROR]: 'Prefer `Number.{{property}}` over `{{description}}`.',
[MESSAGE_ID_SUGGESTION]: 'Replace `{{description}}` with `Number.{{property}}`.',
};
const methods = {
// Safe
const globalObjects = {
// Safe to replace with `Number` properties
parseInt: true,
parseFloat: true,
// Unsafe
NaN: true,
Infinity: true,
// Unsafe to replace with `Number` properties
isNaN: false,
isFinite: false
isFinite: false,
};
const methodsSelector = [
callExpressionSelector(Object.keys(methods)),
' > ',
'.callee'
].join('');
const propertiesSelector = referenceIdentifierSelector(['NaN', 'Infinity']);
const isNegative = node => {
const {parent} = node;
return parent && parent.type === 'UnaryExpression' && parent.operator === '-' && parent.argument === node;
return parent.type === 'UnaryExpression' && parent.operator === '-' && parent.argument === node;
};
const create = context => {
const sourceCode = context.getSourceCode();
const options = {
checkInfinity: true,
...context.options[0]
};
function checkProperty({node, path: [name]}, sourceCode) {
const {parent} = node;
// Cache `NaN` and `Infinity` in `foo = {NaN, Infinity}`
const reported = new WeakSet();
let property = name;
if (name === 'Infinity') {
property = isNegative(node) ? 'NEGATIVE_INFINITY' : 'POSITIVE_INFINITY';
}
return {
[methodsSelector]: node => {
if (isShadowed(context.getScope(), node)) {
return;
}
const problem = {
node,
messageId: MESSAGE_ID_ERROR,
data: {
description: name,
property,
},
};
const {name} = node;
const isSafe = methods[name];
if (property === 'NEGATIVE_INFINITY') {
problem.node = parent;
problem.data.description = '-Infinity';
problem.fix = function * (fixer) {
yield fixer.replaceText(parent, 'Number.NEGATIVE_INFINITY');
yield * fixSpaceAroundKeyword(fixer, parent, sourceCode);
};
const problem = {
node,
messageId: METHOD_ERROR_MESSAGE_ID,
data: {
name
}
};
return problem;
}
const fix = fixer => replaceReferenceIdentifier(node, `Number.${name}`, fixer, sourceCode);
const fix = fixer => replaceReferenceIdentifier(node, `Number.${property}`, fixer, sourceCode);
const isSafeToFix = globalObjects[name];
if (isSafe) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: METHOD_SUGGESTION_MESSAGE_ID,
data: {
name
},
fix
}
];
}
if (isSafeToFix) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: MESSAGE_ID_SUGGESTION,
fix,
},
];
}
return problem;
},
[propertiesSelector]: node => {
if (reported.has(node) || isShadowed(context.getScope(), node)) {
return;
}
return problem;
}
const {name, parent} = node;
if (name === 'Infinity' && !options.checkInfinity) {
return;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {
checkInfinity,
} = {
checkInfinity: true,
...context.options[0],
};
const {sourceCode} = context;
let property = name;
if (name === 'Infinity') {
property = isNegative(node) ? 'NEGATIVE_INFINITY' : 'POSITIVE_INFINITY';
}
let objects = Object.keys(globalObjects);
if (!checkInfinity) {
objects = objects.filter(name => name !== 'Infinity');
}
const problem = {
node,
messageId: PROPERTY_ERROR_MESSAGE_ID,
data: {
identifier: name,
property
}
};
const tracker = new GlobalReferenceTracker({
objects,
handle: reference => checkProperty(reference, sourceCode),
filter: ({node}) => !isLeftHandSide(node),
});
if (property === 'NEGATIVE_INFINITY') {
problem.node = parent;
problem.data.identifier = '-Infinity';
problem.fix = fixer => fixer.replaceText(parent, 'Number.NEGATIVE_INFINITY');
} else {
problem.fix = fixer => replaceReferenceIdentifier(node, `Number.${property}`, fixer, sourceCode);
}
reported.add(node);
return problem;
}
};
return tracker.createListeners(context);
};

@@ -126,12 +103,13 @@

type: 'object',
additionalProperties: false,
properties: {
checkInfinity: {
type: 'boolean',
default: true
}
default: true,
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -142,9 +120,9 @@ create,

docs: {
description: 'Prefer `Number` static properties over global ones.'
description: 'Prefer `Number` static properties over global ones.',
},
fixable: 'code',
hasSuggestions: true,
schema,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
const {isOpeningParenToken, isClosingParenToken} = require('@eslint-community/eslint-utils');
const assertToken = require('./utils/assert-token.js');

@@ -9,58 +9,58 @@

[MESSAGE_ID_WITH_NAME]: 'Remove unused catch binding `{{name}}`.',
[MESSAGE_ID_WITHOUT_NAME]: 'Remove unused catch binding.'
[MESSAGE_ID_WITHOUT_NAME]: 'Remove unused catch binding.',
};
const selector = [
'CatchClause',
' > ',
'.param'
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
CatchClause(catchClause) {
const node = catchClause.param;
if (!node) {
return;
}
const create = context => {
return {
[selector]: node => {
const variables = context.getDeclaredVariables(node.parent);
const {sourceCode} = context;
const variables = sourceCode.getDeclaredVariables(node.parent);
if (variables.some(variable => variable.references.length > 0)) {
return;
}
if (variables.some(variable => variable.references.length > 0)) {
return;
}
const {type, name, parent} = node;
const {type, name, parent} = node;
return {
node,
messageId: type === 'Identifier' ? MESSAGE_ID_WITH_NAME : MESSAGE_ID_WITHOUT_NAME,
data: {name},
* fix(fixer) {
const tokenBefore = context.getTokenBefore(node);
assertToken(tokenBefore, {
test: isOpeningParenToken,
expected: '(',
ruleId: 'prefer-optional-catch-binding'
});
return {
node,
messageId: type === 'Identifier' ? MESSAGE_ID_WITH_NAME : MESSAGE_ID_WITHOUT_NAME,
data: {name},
* fix(fixer) {
const tokenBefore = sourceCode.getTokenBefore(node);
assertToken(tokenBefore, {
test: isOpeningParenToken,
expected: '(',
ruleId: 'prefer-optional-catch-binding',
});
const tokenAfter = context.getTokenAfter(node);
assertToken(tokenAfter, {
test: isClosingParenToken,
expected: ')',
ruleId: 'prefer-optional-catch-binding'
});
const tokenAfter = sourceCode.getTokenAfter(node);
assertToken(tokenAfter, {
test: isClosingParenToken,
expected: ')',
ruleId: 'prefer-optional-catch-binding',
});
yield fixer.remove(tokenBefore);
yield fixer.remove(node);
yield fixer.remove(tokenAfter);
yield fixer.remove(tokenBefore);
yield fixer.remove(node);
yield fixer.remove(tokenAfter);
const [, endOfClosingParenthesis] = tokenAfter.range;
const [startOfCatchClauseBody] = parent.body.range;
const text = context.getSourceCode().text.slice(endOfClosingParenthesis, startOfCatchClauseBody);
const leadingSpacesLength = text.length - text.trimStart().length;
if (leadingSpacesLength !== 0) {
yield fixer.removeRange([endOfClosingParenthesis, endOfClosingParenthesis + leadingSpacesLength]);
}
const [, endOfClosingParenthesis] = tokenAfter.range;
const [startOfCatchClauseBody] = parent.body.range;
const text = sourceCode.text.slice(endOfClosingParenthesis, startOfCatchClauseBody);
const leadingSpacesLength = text.length - text.trimStart().length;
if (leadingSpacesLength !== 0) {
yield fixer.removeRange([endOfClosingParenthesis, endOfClosingParenthesis + leadingSpacesLength]);
}
};
}
};
};
},
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -71,7 +71,7 @@ create,

docs: {
description: 'Prefer omitting the `catch` binding parameter.'
description: 'Prefer omitting the `catch` binding parameter.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {
methodCallSelector,
emptyObjectSelector,
emptyArraySelector,
matches
} = require('./selectors/index.js');
const getPropertyName = require('./utils/get-property-name.js');
const {getPropertyName} = require('@eslint-community/eslint-utils');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isMemberExpression, isMethodCall} = require('./ast/index.js');
const messages = {
'known-method': 'Prefer using `{{constructorName}}.prototype.{{methodName}}`.',
'unknown-method': 'Prefer using method from `{{constructorName}}.prototype`.'
'unknown-method': 'Prefer using method from `{{constructorName}}.prototype`.',
};
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', name: 'apply', min: 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: String(methodName)},
fix: fixer => fixer.replaceText(node.object, `${constructorName}.prototype`)
data: {constructorName, methodName},
* fix(fixer) {
yield fixer.replaceText(objectNode, `${constructorName}.prototype`);
if (
objectNode.type === 'ArrayExpression'
|| objectNode.type === 'ObjectExpression'
) {
yield * fixSpaceAroundKeyword(fixer, callExpression, sourceCode);
}
},
};
}
},
};
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -60,7 +83,7 @@ create,

docs: {
description: 'Prefer borrowing methods from the prototype instead of the instance.'
description: 'Prefer borrowing methods from the prototype instead of the instance.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {methodCallSelector, notDomNodeSelector} = require('./selectors/index.js');
const {isNodeValueNotDomNode} = require('./utils/index.js');
const {isMethodCall, isStringLiteral, isNullLiteral} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-query-selector';
const messages = {
[MESSAGE_ID]: 'Prefer `.{{replacement}}()` over `.{{method}}()`.'
[MESSAGE_ID]: 'Prefer `.{{replacement}}()` over `.{{method}}()`.',
};
const selector = [
methodCallSelector({
names: ['getElementById', 'getElementsByClassName', 'getElementsByTagName'],
length: 1
}),
notDomNodeSelector('callee.object')
].join('');
const forbiddenIdentifierNames = new Map([
const disallowedIdentifierNames = new Map([
['getElementById', 'querySelector'],
['getElementsByClassName', 'querySelectorAll'],
['getElementsByTagName', 'querySelectorAll']
['getElementsByTagName', 'querySelectorAll'],
]);

@@ -53,3 +46,3 @@

templateElement,
getReplacementForId(templateElement.value.cooked)
getReplacementForId(templateElement.value.cooked),
);

@@ -61,3 +54,3 @@ }

templateElement,
getReplacementForClass(templateElement.value.cooked)
getReplacementForClass(templateElement.value.cooked),
);

@@ -68,17 +61,11 @@ }

const canBeFixed = node => {
if (node.type === 'Literal') {
return node.raw === 'null' || (typeof node.value === 'string' && Boolean(node.value.trim()));
}
const canBeFixed = node =>
isNullLiteral(node)
|| (isStringLiteral(node) && Boolean(node.value.trim()))
|| (
node.type === 'TemplateLiteral'
&& node.expressions.length === 0
&& node.quasis.some(templateElement => templateElement.value.cooked.trim())
);
if (node.type === 'TemplateLiteral') {
return (
node.expressions.length === 0 &&
node.quasis.some(templateElement => templateElement.value.cooked.trim())
);
}
return false;
};
const hasValue = node => {

@@ -105,26 +92,38 @@ if (node.type === 'Literal') {

const create = () => {
return {
[selector](node) {
const method = node.callee.property.name;
const preferredSelector = forbiddenIdentifierNames.get(method);
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
CallExpression(node) {
if (
!isMethodCall(node, {
methods: ['getElementById', 'getElementsByClassName', 'getElementsByTagName'],
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})
|| isNodeValueNotDomNode(node.callee.object)
) {
return;
}
const problem = {
node: node.callee.property,
messageId: MESSAGE_ID,
data: {
replacement: preferredSelector,
method
}
};
const method = node.callee.property.name;
const preferredSelector = disallowedIdentifierNames.get(method);
if (canBeFixed(node.arguments[0])) {
problem.fix = fix(node, method, preferredSelector);
}
const problem = {
node: node.callee.property,
messageId: MESSAGE_ID,
data: {
replacement: preferredSelector,
method,
},
};
return problem;
if (canBeFixed(node.arguments[0])) {
problem.fix = fix(node, method, preferredSelector);
}
};
};
return problem;
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -135,7 +134,7 @@ create,

docs: {
description: 'Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`.'
description: 'Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const isLiteralValue = require('./utils/is-literal-value.js');
const getPropertyName = require('./utils/get-property-name.js');
const {not, methodCallSelector} = require('./selectors/index.js');
const {getPropertyName} = require('@eslint-community/eslint-utils');
const {isNullLiteral, isMethodCall} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-reflect-apply';
const messages = {
[MESSAGE_ID]: 'Prefer `Reflect.apply()` over `Function#apply()`.'
[MESSAGE_ID]: 'Prefer `Reflect.apply()` over `Function#apply()`.',
};
const selector = [
methodCallSelector({allowComputed: true}),
not(['Literal', 'ArrayExpression', 'ObjectExpression'].map(type => `[callee.object.type=${type}]`))
].join('');
const isApplySignature = (argument1, argument2) => (
(
// eslint-disable-next-line unicorn/no-null
isLiteralValue(argument1, null) ||
argument1.type === 'ThisExpression'
) &&
(
argument2.type === 'ArrayExpression' ||
(argument2.type === 'Identifier' && argument2.name === 'arguments')
isNullLiteral(argument1)
|| argument1.type === 'ThisExpression'
)
&& (
argument2.type === 'ArrayExpression'
|| (argument2.type === 'Identifier' && argument2.name === 'arguments')
)
);

@@ -34,5 +27,5 @@

if (
getPropertyName(node.callee) === 'apply' &&
node.arguments.length === 2 &&
isApplySignature(node.arguments[0], node.arguments[1])
getPropertyName(node.callee) === 'apply'
&& node.arguments.length === 2
&& isApplySignature(node.arguments[0], node.arguments[1])
) {

@@ -42,3 +35,3 @@ return fixer => (

node,
getReflectApplyCall(sourceCode, node.callee.object, node.arguments[0], node.arguments[1])
getReflectApplyCall(sourceCode, node.callee.object, node.arguments[0], node.arguments[1]),
)

@@ -51,10 +44,9 @@ );

if (
getPropertyName(node.callee) === 'call' &&
getPropertyName(node.callee.object) === 'apply' &&
getPropertyName(node.callee.object.object) === 'prototype' &&
node.callee.object.object.object &&
node.callee.object.object.object.type === 'Identifier' &&
node.callee.object.object.object.name === 'Function' &&
node.arguments.length === 3 &&
isApplySignature(node.arguments[1], node.arguments[2])
getPropertyName(node.callee) === 'call'
&& getPropertyName(node.callee.object) === 'apply'
&& getPropertyName(node.callee.object.object) === 'prototype'
&& node.callee.object.object.object?.type === 'Identifier'
&& node.callee.object.object.object.name === 'Function'
&& node.arguments.length === 3
&& isApplySignature(node.arguments[1], node.arguments[2])
) {

@@ -64,3 +56,3 @@ return fixer => (

node,
getReflectApplyCall(sourceCode, node.arguments[0], node.arguments[1], node.arguments[2])
getReflectApplyCall(sourceCode, node.arguments[0], node.arguments[1], node.arguments[2]),
)

@@ -71,18 +63,30 @@ );

const create = context => {
return {
[selector]: node => {
const sourceCode = context.getSourceCode();
const fix = fixDirectApplyCall(node, sourceCode) || fixFunctionPrototypeCall(node, sourceCode);
if (fix) {
return {
node,
messageId: MESSAGE_ID,
fix
};
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
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);
if (fix) {
return {
node,
messageId: MESSAGE_ID,
fix,
};
}
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -93,7 +97,7 @@ create,

docs: {
description: 'Prefer `Reflect.apply()` over `Function#apply()`.'
description: 'Prefer `Reflect.apply()` over `Function#apply()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isParenthesized, getStaticValue} = require('eslint-utils');
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
const {checkVueTemplate} = require('./utils/rule.js');
const {methodCallSelector} = require('./selectors/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');
const REGEXP_EXEC = 'regexp-exec';
const STRING_MATCH = 'string-match';
const SUGGESTION = 'suggestion';
const messages = {
[REGEXP_EXEC]: 'Prefer `.test(…)` over `.exec(…)`.',
[STRING_MATCH]: 'Prefer `RegExp#test(…)` over `String#match(…)`.'
[STRING_MATCH]: 'Prefer `RegExp#test(…)` over `String#match(…)`.',
[SUGGESTION]: 'Switch to `RegExp#test(…)`.',
};

@@ -18,5 +22,7 @@

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

@@ -26,11 +32,13 @@ getNodes: node => ({

methodNode: node.callee.property,
regexpNode: node.callee.object
regexpNode: node.callee.object,
}),
fix: (fixer, {methodNode}) => fixer.replaceText(methodNode, 'test')
fix: (fixer, {methodNode}) => fixer.replaceText(methodNode, 'test'),
},
{
type: STRING_MATCH,
selector: methodCallSelector({
name: 'match',
length: 1
test: node => isMethodCall(node, {
method: 'match',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
}),

@@ -40,3 +48,3 @@ getNodes: node => ({

methodNode: node.callee.property,
regexpNode: node.arguments[0]
regexpNode: node.arguments[0],
}),

@@ -48,5 +56,5 @@ * fix(fixer, {stringNode, methodNode, regexpNode}, sourceCode) {

if (
!isParenthesized(regexpNode, sourceCode) &&
!isParenthesized(regexpNode, sourceCode)
// Only `SequenceExpression` need add parentheses
stringNode.type === 'SequenceExpression'
&& stringNode.type === 'SequenceExpression'
) {

@@ -60,4 +68,4 @@ stringText = `(${stringText})`;

if (
!isParenthesized(stringNode, sourceCode) &&
shouldAddParenthesesToMemberExpressionObject(regexpNode, sourceCode)
!isParenthesized(stringNode, sourceCode)
&& shouldAddParenthesesToMemberExpressionObject(regexpNode, sourceCode)
) {

@@ -70,31 +78,35 @@ regexpText = `(${regexpText})`;

yield fixer.replaceText(stringNode, regexpText);
}
}
},
},
];
const isRegExpNode = node => {
if (node.type === 'Literal' && node.regex) {
return true;
}
const isRegExpNode = node => isRegexLiteral(node) || isNewExpression(node, {name: 'RegExp'});
if (
node.type === 'NewExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'RegExp'
) {
return true;
const isRegExpWithoutGlobalFlag = (node, scope) => {
const staticResult = getStaticValue(node, scope);
// Don't know if there is `g` flag
if (!staticResult) {
return false;
}
return false;
const {value} = staticResult;
return (
Object.prototype.toString.call(value) === '[object RegExp]'
&& !value.global
);
};
const create = context => Object.fromEntries(
cases.map(checkCase => [
checkCase.selector,
node => {
if (!isBooleanNode(node)) {
return;
/** @param {import('eslint').Rule.RuleContext} context */
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);

@@ -104,3 +116,3 @@ const {methodNode, regexpNode} = nodes;

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

@@ -110,24 +122,28 @@

node: type === REGEXP_EXEC ? methodNode : node,
messageId: type
messageId: type,
};
if (!isRegExpNode(regexpNode)) {
const staticResult = getStaticValue(regexpNode, context.getScope());
if (staticResult) {
const {value} = staticResult;
if (
Object.prototype.toString.call(value) !== '[object RegExp]' ||
value.flags.includes('g')
) {
return problem;
}
}
const {sourceCode} = context;
const fixFunction = fixer => fix(fixer, nodes, sourceCode);
if (
isRegExpNode(regexpNode)
|| isRegExpWithoutGlobalFlag(regexpNode, sourceCode.getScope(regexpNode))
) {
problem.fix = fixFunction;
} else {
problem.suggest = [
{
messageId: SUGGESTION,
fix: fixFunction,
},
];
}
problem.fix = fixer => fix(fixer, nodes, context.getSourceCode());
return problem;
yield problem;
}
])
);
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -138,7 +154,8 @@ create: checkVueTemplate(create),

docs: {
description: 'Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`.'
description: 'Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`.',
},
fixable: 'code',
messages
}
hasSuggestions: true,
messages,
},
};
'use strict';
const {findVariable} = require('eslint-utils');
const getVariableIdentifiers = require('./utils/get-variable-identifiers.js');
const {
matches,
not,
methodCallSelector,
callOrNewExpressionSelector
} = require('./selectors/index.js');
const {findVariable} = require('@eslint-community/eslint-utils');
const {getVariableIdentifiers} = require('./utils/index.js');
const {isCallOrNewExpression, isMethodCall} = require('./ast/index.js');

@@ -15,83 +10,36 @@ const MESSAGE_ID_ERROR = 'error';

[MESSAGE_ID_ERROR]: '`{{name}}` should be a `Set`, and use `{{name}}.has()` to check existence or non-existence.',
[MESSAGE_ID_SUGGESTION]: 'Switch `{{name}}` to `Set`.'
[MESSAGE_ID_SUGGESTION]: 'Switch `{{name}}` to `Set`.',
};
// `[]`
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',
names: ['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({
names: [
'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 => {
/* istanbul ignore next */
if (!node.parent || !node.parent.parent) {
return false;
}
const {type, optional, callee, arguments: includesArguments} = node.parent.parent;
const {type, optional, callee, arguments: includesArguments} = node.parent.parent ?? {};
return (
type === 'CallExpression' &&
!optional &&
callee &&
callee.type === 'MemberExpression' &&
!callee.computed &&
!callee.optional &&
callee.object === node &&
callee.property.type === 'Identifier' &&
callee.property.name === 'includes' &&
includesArguments.length === 1 &&
includesArguments[0].type !== 'SpreadElement'
type === 'CallExpression'
&& !optional
&& callee.type === 'MemberExpression'
&& !callee.computed
&& !callee.optional
&& callee.object === node
&& callee.property.type === 'Identifier'
&& callee.property.name === 'includes'
&& includesArguments.length === 1
&& includesArguments[0].type !== 'SpreadElement'
);

@@ -108,3 +56,3 @@ };

'FunctionExpression',
'ArrowFunctionExpression'
'ArrowFunctionExpression',
]);

@@ -116,4 +64,4 @@

while (
parent &&
parent !== root
parent
&& parent !== root
) {

@@ -130,66 +78,102 @@ if (multipleCallNodeTypes.has(parent.type)) {

const create = context => {
return {
[selector]: node => {
const variable = findVariable(context.getScope(), node);
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
Identifier(node) {
const {parent} = node;
// This was reported https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1075#issuecomment-768073342
// But can't reproduce, just ignore this case
/* istanbul ignore next */
if (!variable) {
return;
}
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 identifiers = getVariableIdentifiers(variable).filter(identifier => identifier !== node);
const variable = findVariable(context.sourceCode.getScope(node), node);
if (
identifiers.length === 0 ||
identifiers.some(identifier => !isIncludesCall(identifier))
) {
return;
}
// This was reported https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1075#issuecomment-768073342
// But can't reproduce, just ignore this case
/* c8 ignore next 3 */
if (!variable) {
return;
}
if (
identifiers.length === 1 &&
identifiers.every(identifier => !isMultipleCall(identifier, node))
) {
return;
}
const identifiers = getVariableIdentifiers(variable).filter(identifier => identifier !== node);
const problem = {
node,
messageId: MESSAGE_ID_ERROR,
data: {
name: node.name
}
};
if (
identifiers.length === 0
|| identifiers.some(identifier => !isIncludesCall(identifier))
) {
return;
}
const fix = function * (fixer) {
yield fixer.insertTextBefore(node.parent.init, 'new Set(');
yield fixer.insertTextAfter(node.parent.init, ')');
if (
identifiers.length === 1
&& identifiers.every(identifier => !isMultipleCall(identifier, node))
) {
return;
}
for (const identifier of identifiers) {
yield fixer.replaceText(identifier.parent.property, 'has');
}
};
const problem = {
node,
messageId: MESSAGE_ID_ERROR,
data: {
name: node.name,
},
};
if (node.typeAnnotation) {
problem.suggest = [
{
messageId: MESSAGE_ID_SUGGESTION,
data: {
name: node.name
},
fix
}
];
} else {
problem.fix = fix;
const fix = function * (fixer) {
yield fixer.insertTextBefore(node.parent.init, 'new Set(');
yield fixer.insertTextAfter(node.parent.init, ')');
for (const identifier of identifiers) {
yield fixer.replaceText(identifier.parent.property, 'has');
}
};
return problem;
if (node.typeAnnotation) {
problem.suggest = [
{
messageId: MESSAGE_ID_SUGGESTION,
fix,
},
];
} else {
problem.fix = fix;
}
};
};
return problem;
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -200,8 +184,8 @@ create,

docs: {
description: 'Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence.'
description: 'Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {isParenthesized, getStaticValue, isCommaToken, hasSideEffect} = require('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 replaceNodeOrTokenAndSpacesBefore = require('./utils/replace-node-or-token-and-spaces-before.js');
const removeSpacesAfter = require('./utils/remove-spaces-after.js');
const isLiteralValue = require('./utils/is-literal-value.js');
const {isNodeMatches} = require('./utils/is-node-matches.js');
const {isParenthesized, getStaticValue, isCommaToken, hasSideEffect} = require('@eslint-community/eslint-utils');
const {
getParenthesizedRange,
getParenthesizedText,
needsSemicolon,
shouldAddParenthesesToSpreadElementArgument,
isNodeMatches,
isMethodNamed,
} = require('./utils/index.js');
const {removeMethodCall} = require('./fix/index.js');
const {isLiteral, isMethodCall} = require('./ast/index.js');

@@ -15,2 +17,4 @@ 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';
const SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE = 'argument-is-spreadable';

@@ -20,2 +24,3 @@ const SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE = 'argument-is-not-spreadable';

const SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS = 'spread-all-arguments';
const SUGGESTION_USE_SPREAD = 'use-spread';
const messages = {

@@ -25,42 +30,11 @@ [ERROR_ARRAY_FROM]: 'Prefer the spread operator over `Array.from(…)`.',

[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(\'\')`.',
[SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE]: 'First argument is an `array`.',
[SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE]: 'First argument is not an `array`.',
[SUGGESTION_CONCAT_TEST_ARGUMENT]: 'Test first argument with `Array.isArray(…)`.',
[SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS]: 'Spread all unknown arguments`.'
[SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS]: 'Spread all unknown arguments`.',
[SUGGESTION_USE_SPREAD]: 'Use `...` operator.',
};
const arrayFromCallSelector = [
methodCallSelector({
object: 'Array',
name: 'from',
min: 1,
max: 3
}),
// Allow `Array.from({length})`
'[arguments.0.type!="ObjectExpression"]'
].join('');
const arrayConcatCallSelector = [
methodCallSelector('concat'),
`:not(${
[
...[
'Literal',
'TemplateLiteral'
].map(type => `[callee.object.type="${type}"]`),
// Most likely it's a static method of a class
'[callee.object.name=/^[A-Z]/]'
].join(', ')
})`
].join('');
const arraySliceCallSelector = [
methodCallSelector({
name: 'slice',
min: 0,
max: 1
}),
'[callee.object.type!="ArrayExpression"]'
].join('');
const ignoredSliceCallee = [

@@ -71,3 +45,3 @@ 'arrayBuffer',

'file',
'this'
'this',
];

@@ -84,11 +58,2 @@

const getRangeAfterCalleeObject = (node, sourceCode) => {
const {object} = node.callee;
const parenthesizedRange = getParenthesizedRange(object, sourceCode);
const [, start] = parenthesizedRange;
const [, end] = node.range;
return [start, end];
};
function fixConcat(node, sourceCode, fixableArguments) {

@@ -103,4 +68,4 @@ const array = node.callee.object;

if (
!keepTrailingComma &&
isArrayLiteralHasTrailingComma(node, sourceCode)
!keepTrailingComma
&& isArrayLiteralHasTrailingComma(node, sourceCode)
) {

@@ -134,4 +99,4 @@ const start = node.range[0] + 1;

if (
!isParenthesized(node, sourceCode) &&
shouldAddParenthesesToSpreadElementArgument(node)
!isParenthesized(node, sourceCode)
&& shouldAddParenthesesToSpreadElementArgument(node)
) {

@@ -161,4 +126,4 @@ text = `(${text})`;

if (
arrayHasTrailingComma &&
(!lastArgument.isArrayLiteral || !isArrayLiteralHasTrailingComma(lastArgument.node, sourceCode))
arrayHasTrailingComma
&& (!lastArgument.isArrayLiteral || !isArrayLiteralHasTrailingComma(lastArgument.node, sourceCode))
) {

@@ -192,4 +157,4 @@ text = `${text},`;

if (
!arrayIsArrayLiteral &&
needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[')
!arrayIsArrayLiteral
&& needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[')
) {

@@ -199,7 +164,7 @@ yield fixer.insertTextBefore(node, ';');

yield (
concatCallArguments.length - fixableArguments.length === 0 ?
fixer.replaceTextRange(getRangeAfterCalleeObject(node, sourceCode), '') :
removeArguments(fixer)
);
if (concatCallArguments.length - fixableArguments.length === 0) {
yield * removeMethodCall(fixer, node, sourceCode);
} else {
yield removeArguments(fixer);
}

@@ -268,4 +233,4 @@ const text = getFixedText();

if (
!isParenthesized(object, sourceCode) &&
shouldAddParenthesesToSpreadElementArgument(object)
!isParenthesized(object, sourceCode)
&& shouldAddParenthesesToSpreadElementArgument(object)
) {

@@ -278,9 +243,2 @@ text = `(${text})`;

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) {

@@ -294,15 +252,7 @@ // 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);
};
}
function fixSlice(node, sourceCode) {
function methodCallToSpread(node, sourceCode) {
return function * (fixer) {

@@ -317,115 +267,266 @@ // Fixed code always starts with `[`

// The array is already accessing `.slice`, there should not any case need add extra `()`
// The array is already accessing `.slice` or `.split`, there should not any case need add extra `()`
yield fixer.replaceTextRange(getRangeAfterCalleeObject(node, sourceCode), '');
yield * removeMethodCall(fixer, node, sourceCode);
};
}
function isClassName(node) {
if (node.type === 'MemberExpression') {
node = node.property;
}
if (node.type !== 'Identifier') {
return false;
}
const {name} = node;
return /^[A-Z]./.test(name) && name.toUpperCase() !== name;
}
function isNotArray(node, scope) {
if (
node.type === 'TemplateLiteral'
|| node.type === 'Literal'
|| node.type === 'BinaryExpression'
|| isClassName(node)
// `foo.join()`
|| (isMethodNamed(node, 'join') && node.arguments.length <= 1)
) {
return true;
}
const staticValue = getStaticValue(node, scope);
if (staticValue && !Array.isArray(staticValue.value)) {
return true;
}
return false;
}
/** @param {import('eslint').Rule.RuleContext} context */
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 {
node,
messageId: ERROR_ARRAY_FROM,
fix: fixArrayFrom(node, sourceCode)
fix: fixArrayFrom(node, sourceCode),
};
},
[arrayConcatCallSelector](node) {
const scope = context.getScope();
const staticResult = getStaticValue(node.callee.object, scope);
}
});
if (staticResult && !Array.isArray(staticResult.value)) {
return;
}
// `array.concat()`
context.on('CallExpression', node => {
if (!isMethodCall(node, {
method: 'concat',
optionalCall: false,
optionalMember: false,
})) {
return;
}
const problem = {
node: node.callee.property,
messageId: ERROR_ARRAY_CONCAT
};
const {object} = node.callee;
const scope = sourceCode.getScope(object);
const fixableArguments = getConcatFixableArguments(node.arguments, scope);
if (isNotArray(object, scope)) {
return;
}
if (fixableArguments.length > 0 || node.arguments.length === 0) {
problem.fix = fixConcat(node, sourceCode, fixableArguments);
return problem;
}
const staticResult = getStaticValue(object, scope);
if (staticResult && !Array.isArray(staticResult.value)) {
return;
}
const [firstArgument, ...restArguments] = node.arguments;
if (firstArgument.type === 'SpreadElement') {
return problem;
}
const problem = {
node: node.callee.property,
messageId: ERROR_ARRAY_CONCAT,
};
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 fixableArguments = getConcatFixableArguments(node.arguments, scope);
if (!hasSideEffect(firstArgument, sourceCode)) {
suggestions.push({
messageId: SUGGESTION_CONCAT_TEST_ARGUMENT,
testArgument: true
});
}
if (fixableArguments.length > 0 || node.arguments.length === 0) {
problem.fix = fixConcat(node, sourceCode, fixableArguments);
return problem;
}
problem.suggest = suggestions.map(({messageId, isSpreadable, testArgument}) => ({
messageId,
const [firstArgument, ...restArguments] = node.arguments;
if (firstArgument.type === 'SpreadElement') {
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,
},
];
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 && !isLiteralValue(firstArgument, 0)) {
if (isNodeMatches(node.callee.object, ignoredSliceCallee)) {
return;
}
const [firstArgument] = node.arguments;
if (firstArgument && !isLiteral(firstArgument, 0)) {
return;
}
return {
node: node.callee.property,
messageId: ERROR_ARRAY_SLICE,
fix: methodCallToSpread(node, sourceCode),
};
});
// `array.toSpliced()`
context.on('CallExpression', node => {
if (!(
isMethodCall(node, {
method: 'toSpliced',
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
&& node.callee.object.type !== 'ArrayExpression'
)) {
return;
}
return {
node: node.callee.property,
messageId: ERROR_ARRAY_TO_SPLICED,
fix: methodCallToSpread(node, sourceCode),
};
});
// `string.split()`
context.on('CallExpression', node => {
if (!isMethodCall(node, {
method: 'split',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const [separator] = node.arguments;
if (!isLiteral(separator, '')) {
return;
}
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 {
node: node.callee.property,
messageId: ERROR_ARRAY_SLICE,
fix: fixSlice(node, sourceCode)
};
// 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;
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -436,8 +537,8 @@ create,

docs: {
description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)` and `Array#slice()`.'
description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split(\'\')`.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const quoteString = require('./utils/quote-string.js');
const {methodCallSelector} = require('./selectors/index.js');
const {getStaticValue} = require('@eslint-community/eslint-utils');
const {parse: parseRegExp} = require('regjsparser');
const escapeString = require('./utils/escape-string.js');
const {isRegexLiteral, isNewExpression, isMethodCall} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-string-replace-all';
const MESSAGE_ID_USE_REPLACE_ALL = 'method';
const MESSAGE_ID_USE_STRING = 'pattern';
const messages = {
[MESSAGE_ID]: 'Prefer `String#replaceAll()` over `String#replace()`.'
[MESSAGE_ID_USE_REPLACE_ALL]: 'Prefer `String#replaceAll()` over `String#replace()`.',
[MESSAGE_ID_USE_STRING]: 'This pattern can be replaced with {{replacement}}.',
};
const selector = methodCallSelector({
name: 'replace',
length: 2
});
function getPatternReplacement(node) {
if (!isRegexLiteral(node)) {
return;
}
function isRegexWithGlobalFlag(node) {
const {type, regex} = node;
if (type !== 'Literal' || !regex) {
return false;
const {pattern, flags} = node.regex;
if (flags.replace('u', '') !== 'g') {
return;
}
const {flags} = regex;
return flags.replace('u', '') === 'g';
}
let tree;
function isLiteralCharactersOnly(node) {
const searchPattern = node.regex.pattern;
return !/[$()*+.?[\\\]^{}]/.test(searchPattern.replace(/\\[$()*+.?[\\\]^{}]/g, ''));
try {
tree = parseRegExp(pattern, flags, {
unicodePropertyEscape: true,
namedGroups: true,
lookbehind: true,
});
} catch {
return;
}
const parts = tree.type === 'alternative' ? tree.body : [tree];
if (parts.some(part => part.type !== 'value')) {
return;
}
// TODO: Preserve escape
const string = String.fromCodePoint(...parts.map(part => part.codePoint));
return escapeString(string);
}
function removeEscapeCharacters(regexString) {
let fixedString = regexString;
let index = 0;
do {
index = fixedString.indexOf('\\', index);
const isRegExpWithGlobalFlag = (node, scope) => {
if (isRegexLiteral(node)) {
return node.regex.flags.includes('g');
}
if (index >= 0) {
fixedString = fixedString.slice(0, index) + fixedString.slice(index + 1);
index++;
if (
isNewExpression(node, {name: 'RegExp'})
&& node.arguments[0]?.type !== 'SpreadElement'
&& node.arguments[1]?.type === 'Literal'
&& typeof node.arguments[1].value === 'string'
) {
return node.arguments[1].value.includes('g');
}
const staticResult = getStaticValue(node, scope);
// Don't know if there is `g` flag
if (!staticResult) {
return false;
}
const {value} = staticResult;
return (
Object.prototype.toString.call(value) === '[object RegExp]'
&& value.global
);
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
CallExpression(node) {
if (!isMethodCall(node, {
methods: ['replace', 'replaceAll'],
argumentsLength: 2,
optionalCall: false,
optionalMember: false,
})) {
return;
}
} while (index >= 0);
return fixedString;
}
const {
arguments: [pattern],
callee: {property},
} = node;
const create = () => {
return {
[selector]: node => {
const {arguments: arguments_, callee} = node;
const [search] = arguments_;
if (!isRegExpWithGlobalFlag(pattern, context.sourceCode.getScope(pattern))) {
return;
}
if (!isRegexWithGlobalFlag(search) || !isLiteralCharactersOnly(search)) {
const methodName = property.name;
const patternReplacement = getPatternReplacement(pattern);
if (methodName === 'replaceAll') {
if (!patternReplacement) {
return;

@@ -56,13 +105,31 @@ }

return {
node,
messageId: MESSAGE_ID,
fix: fixer => [
fixer.insertTextAfter(callee, 'All'),
fixer.replaceText(search, quoteString(removeEscapeCharacters(search.regex.pattern)))
]
node: pattern,
messageId: MESSAGE_ID_USE_STRING,
data: {
// Show `This pattern can be replaced with a string literal.` for long strings
replacement: patternReplacement.length < 20 ? patternReplacement : 'a string literal',
},
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => fixer.replaceText(pattern, patternReplacement),
};
}
};
};
return {
node: property,
messageId: MESSAGE_ID_USE_REPLACE_ALL,
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
yield fixer.insertTextAfter(property, 'All');
if (!patternReplacement) {
return;
}
yield fixer.replaceText(pattern, patternReplacement);
},
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -73,7 +140,7 @@ create,

docs: {
description: 'Prefer `String#replaceAll()` over regex searches with the global flag.'
description: 'Prefer `String#replaceAll()` over regex searches with the global flag.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const eslintTemplateVisitor = require('eslint-template-visitor');
const {getStaticValue} = require('@eslint-community/eslint-utils');
const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
const isNumber = require('./utils/is-number.js');
const {replaceArgument} = require('./fix/index.js');
const {isNumberLiteral, isMethodCall} = require('./ast/index.js');

@@ -8,17 +12,7 @@ const MESSAGE_ID_SUBSTR = 'substr';

[MESSAGE_ID_SUBSTR]: 'Prefer `String#slice()` over `String#substr()`.',
[MESSAGE_ID_SUBSTRING]: 'Prefer `String#slice()` over `String#substring()`.'
[MESSAGE_ID_SUBSTRING]: 'Prefer `String#slice()` over `String#substring()`.',
};
const templates = eslintTemplateVisitor();
const objectVariable = templates.variable();
const argumentsVariable = templates.spreadVariable();
const substrCallTemplate = templates.template`${objectVariable}.substr(${argumentsVariable})`;
const substringCallTemplate = templates.template`${objectVariable}.substring(${argumentsVariable})`;
const isLiteralNumber = node => node && node.type === 'Literal' && typeof node.value === 'number';
const getNumericValue = node => {
if (isLiteralNumber(node)) {
if (isNumberLiteral(node)) {
return node.value;

@@ -34,165 +28,147 @@ }

const isLengthProperty = node => (
node &&
node.type === 'MemberExpression' &&
node.computed === false &&
node.property.type === 'Identifier' &&
node.property.name === 'length'
node?.type === 'MemberExpression'
&& node.computed === false
&& node.property.type === 'Identifier'
&& node.property.name === 'length'
);
const isLikelyNumeric = node => isLiteralNumber(node) || isLengthProperty(node);
function * fixSubstrArguments({node, fixer, context, abort}) {
const argumentNodes = node.arguments;
const [firstArgument, secondArgument] = argumentNodes;
const create = context => {
const sourceCode = context.getSourceCode();
if (!secondArgument) {
return;
}
const getNodeText = node => {
const text = sourceCode.getText(node);
const before = sourceCode.getTokenBefore(node);
const after = sourceCode.getTokenAfter(node);
if (
(before && before.type === 'Punctuator' && before.value === '(') &&
(after && after.type === 'Punctuator' && after.value === ')')
) {
return `(${text})`;
const {sourceCode} = context;
const scope = sourceCode.getScope(node);
const firstArgumentStaticResult = getStaticValue(firstArgument, scope);
const secondArgumentRange = getParenthesizedRange(secondArgument, sourceCode);
const replaceSecondArgument = text => replaceArgument(fixer, secondArgument, text, sourceCode);
if (firstArgumentStaticResult?.value === 0) {
if (isNumberLiteral(secondArgument) || isLengthProperty(secondArgument)) {
return;
}
return text;
};
if (typeof getNumericValue(secondArgument) === 'number') {
yield replaceSecondArgument(Math.max(0, getNumericValue(secondArgument)));
return;
}
return templates.visitor({
[substrCallTemplate](node) {
const objectNode = substrCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substrCallTemplate.context.getMatch(argumentsVariable);
yield fixer.insertTextBeforeRange(secondArgumentRange, 'Math.max(0, ');
yield fixer.insertTextAfterRange(secondArgumentRange, ')');
return;
}
const problem = {
node,
messageId: MESSAGE_ID_SUBSTR
};
if (argumentNodes.every(node => isNumberLiteral(node))) {
yield replaceSecondArgument(firstArgument.value + secondArgument.value);
return;
}
const firstArgument = argumentNodes[0] ? sourceCode.getText(argumentNodes[0]) : undefined;
const secondArgument = argumentNodes[1] ? sourceCode.getText(argumentNodes[1]) : undefined;
if (argumentNodes.every(node => isNumber(node, scope))) {
const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);
let sliceArguments;
yield fixer.insertTextBeforeRange(secondArgumentRange, `${firstArgumentText} + `);
return;
}
switch (argumentNodes.length) {
case 0: {
sliceArguments = [];
break;
}
return abort();
}
case 1: {
sliceArguments = [firstArgument];
break;
}
function * fixSubstringArguments({node, fixer, context, abort}) {
const {sourceCode} = context;
const [firstArgument, secondArgument] = node.arguments;
case 2: {
if (firstArgument === '0') {
sliceArguments = [firstArgument];
if (isLiteralNumber(secondArgument) || isLengthProperty(argumentNodes[1])) {
sliceArguments.push(secondArgument);
} else if (typeof getNumericValue(argumentNodes[1]) === 'number') {
sliceArguments.push(Math.max(0, getNumericValue(argumentNodes[1])));
} else {
sliceArguments.push(`Math.max(0, ${secondArgument})`);
}
} else if (
isLiteralNumber(argumentNodes[0]) &&
isLiteralNumber(argumentNodes[1])
) {
sliceArguments = [
firstArgument,
argumentNodes[0].value + argumentNodes[1].value
];
} else if (
isLikelyNumeric(argumentNodes[0]) &&
isLikelyNumeric(argumentNodes[1])
) {
sliceArguments = [firstArgument, firstArgument + ' + ' + secondArgument];
}
const firstNumber = firstArgument ? getNumericValue(firstArgument) : undefined;
const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);
const replaceFirstArgument = text => replaceArgument(fixer, firstArgument, text, sourceCode);
break;
}
// No default
}
if (!secondArgument) {
if (isLengthProperty(firstArgument)) {
return;
}
if (sliceArguments) {
const objectText = getNodeText(objectNode);
const optionalMemberSuffix = node.callee.optional ? '?' : '';
const optionalCallSuffix = node.optional ? '?.' : '';
if (firstNumber !== undefined) {
yield replaceFirstArgument(Math.max(0, firstNumber));
return;
}
problem.fix = fixer => fixer.replaceText(node, `${objectText}${optionalMemberSuffix}.slice${optionalCallSuffix}(${sliceArguments.join(', ')})`);
}
const firstArgumentRange = getParenthesizedRange(firstArgument, sourceCode);
yield fixer.insertTextBeforeRange(firstArgumentRange, 'Math.max(0, ');
yield fixer.insertTextAfterRange(firstArgumentRange, ')');
return;
}
context.report(problem);
},
const secondNumber = getNumericValue(secondArgument);
const secondArgumentText = getParenthesizedText(secondArgument, sourceCode);
const replaceSecondArgument = text => replaceArgument(fixer, secondArgument, text, sourceCode);
[substringCallTemplate](node) {
const objectNode = substringCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substringCallTemplate.context.getMatch(argumentsVariable);
if (firstNumber !== undefined && secondNumber !== undefined) {
const argumentsValue = [Math.max(0, firstNumber), Math.max(0, secondNumber)];
if (firstNumber > secondNumber) {
argumentsValue.reverse();
}
const problem = {
node,
messageId: MESSAGE_ID_SUBSTRING
};
if (argumentsValue[0] !== firstNumber) {
yield replaceFirstArgument(argumentsValue[0]);
}
const firstArgument = argumentNodes[0] ? sourceCode.getText(argumentNodes[0]) : undefined;
const secondArgument = argumentNodes[1] ? sourceCode.getText(argumentNodes[1]) : undefined;
if (argumentsValue[1] !== secondNumber) {
yield replaceSecondArgument(argumentsValue[1]);
}
const firstNumber = argumentNodes[0] ? getNumericValue(argumentNodes[0]) : undefined;
return;
}
let sliceArguments;
if (firstNumber === 0 || secondNumber === 0) {
yield replaceFirstArgument(0);
yield replaceSecondArgument(`Math.max(0, ${firstNumber === 0 ? secondArgumentText : firstArgumentText})`);
return;
}
switch (argumentNodes.length) {
case 0: {
sliceArguments = [];
break;
}
// As values aren't Literal, we can not know whether secondArgument will become smaller than the first or not, causing an issue:
// .substring(0, 2) and .substring(2, 0) returns the same result
// .slice(0, 2) and .slice(2, 0) doesn't return the same result
// There's also an issue with us now knowing whether the value will be negative or not, due to:
// .substring() treats a negative number the same as it treats a zero.
// The latter issue could be solved by wrapping all dynamic numbers in Math.max(0, <value>), but the resulting code would not be nice
case 1: {
if (firstNumber !== undefined) {
sliceArguments = [Math.max(0, firstNumber)];
} else if (isLengthProperty(argumentNodes[0])) {
sliceArguments = [firstArgument];
} else {
sliceArguments = [`Math.max(0, ${firstArgument})`];
}
return abort();
}
break;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
CallExpression(node) {
if (!isMethodCall(node, {methods: ['substr', 'substring']})) {
return;
}
case 2: {
const secondNumber = getNumericValue(argumentNodes[1]);
const method = node.callee.property.name;
if (firstNumber !== undefined && secondNumber !== undefined) {
sliceArguments = firstNumber > secondNumber ?
[Math.max(0, secondNumber), Math.max(0, firstNumber)] :
[Math.max(0, firstNumber), Math.max(0, secondNumber)];
} else if (firstNumber === 0 || secondNumber === 0) {
sliceArguments = [0, `Math.max(0, ${firstNumber === 0 ? secondArgument : firstArgument})`];
} else {
// As values aren't Literal, we can not know whether secondArgument will become smaller than the first or not, causing an issue:
// .substring(0, 2) and .substring(2, 0) returns the same result
// .slice(0, 2) and .slice(2, 0) doesn't return the same result
// There's also an issue with us now knowing whether the value will be negative or not, due to:
// .substring() treats a negative number the same as it treats a zero.
// The latter issue could be solved by wrapping all dynamic numbers in Math.max(0, <value>), but the resulting code would not be nice
}
return {
node,
messageId: method,
* fix(fixer, {abort}) {
yield fixer.replaceText(node.callee.property, 'slice');
break;
if (node.arguments.length === 0) {
return;
}
// No default
}
if (sliceArguments) {
const objectText = getNodeText(objectNode);
const optionalMemberSuffix = node.callee.optional ? '?' : '';
const optionalCallSuffix = node.optional ? '?.' : '';
if (
node.arguments.length > 2
|| node.arguments.some(node => node.type === 'SpreadElement')
) {
return abort();
}
problem.fix = fixer => fixer.replaceText(node, `${objectText}${optionalMemberSuffix}.slice${optionalCallSuffix}(${sliceArguments.join(', ')})`);
}
const fixArguments = method === 'substr' ? fixSubstrArguments : fixSubstringArguments;
yield * fixArguments({node, fixer, context, abort});
},
};
},
});
context.report(problem);
}
});
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -203,7 +179,7 @@ create,

docs: {
description: 'Prefer `String#slice()` over `String#substr()` and `String#substring()`.'
description: 'Prefer `String#slice()` over `String#substr()` and `String#substring()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {isParenthesized, getStaticValue} = require('eslint-utils');
const {methodCallSelector} = require('./selectors/index.js');
const quoteString = require('./utils/quote-string.js');
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
const escapeString = require('./utils/escape-string.js');
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
const shouldAddParenthesesToLogicalExpressionChild = require('./utils/should-add-parentheses-to-logical-expression-child.js');
const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
const {isMethodCall, isRegexLiteral} = require('./ast/index.js');

@@ -19,3 +19,3 @@ const MESSAGE_STARTS_WITH = 'prefer-starts-with';

[FIX_TYPE_OPTIONAL_CHAINING]: 'Use optional chaining `…?.{{method}}()`.',
[FIX_TYPE_NULLISH_COALESCING]: 'Use nullish coalescing `(… ?? \'\').{{method}}()`.'
[FIX_TYPE_NULLISH_COALESCING]: 'Use nullish coalescing `(… ?? \'\').{{method}}()`.',
};

@@ -26,11 +26,6 @@

string,
['^', '$', '+', '[', '{', '(', '\\', '.', '?', '*', '|']
['^', '$', '+', '[', '{', '(', '\\', '.', '?', '*', '|'],
);
const addParentheses = text => `(${text})`;
const regexTestSelector = [
methodCallSelector({name: 'test', length: 1}),
'[callee.object.regex]'
].join('');
const checkRegex = ({pattern, flags}) => {

@@ -47,3 +42,3 @@ if (flags.includes('i') || flags.includes('m')) {

messageId: MESSAGE_STARTS_WITH,
string
string,
};

@@ -59,3 +54,3 @@ }

messageId: MESSAGE_ENDS_WITH,
string
string,
};

@@ -66,7 +61,20 @@ }

/** @param {import('eslint').Rule.RuleContext} context */
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;

@@ -82,11 +90,11 @@ const {regex} = regexNode;

let isString = target.type === 'TemplateLiteral' ||
(
target.type === 'CallExpression' &&
target.callee.type === 'Identifier' &&
target.callee.name === 'String'
let isString = target.type === 'TemplateLiteral'
|| (
target.type === 'CallExpression'
&& target.callee.type === 'Identifier'
&& target.callee.name === 'String'
);
let isNonString = false;
if (!isString) {
const staticValue = getStaticValue(target, context.getScope());
const staticValue = getStaticValue(target, sourceCode.getScope(target));

@@ -101,3 +109,3 @@ if (staticValue) {

node,
messageId: result.messageId
messageId: result.messageId,
};

@@ -112,6 +120,6 @@

// Goal: `(target ?? '').startsWith(pattern)`
case FIX_TYPE_NULLISH_COALESCING:
case FIX_TYPE_NULLISH_COALESCING: {
if (
!isTargetParenthesized &&
shouldAddParenthesesToLogicalExpressionChild(target, {operator: '??', property: 'left'})
!isTargetParenthesized
&& shouldAddParenthesesToLogicalExpressionChild(target, {operator: '??', property: 'left'})
) {

@@ -130,5 +138,6 @@ targetText = addParentheses(targetText);

break;
}
// Goal: `String(target).startsWith(pattern)`
case FIX_TYPE_STRING_CASTING:
case FIX_TYPE_STRING_CASTING: {
// `target` was a call argument, don't need check parentheses

@@ -138,16 +147,20 @@ targetText = `String(${targetText})`;

break;
}
// Goal: `target.startsWith(pattern)` or `target?.startsWith(pattern)`
case FIX_TYPE_OPTIONAL_CHAINING:
case FIX_TYPE_OPTIONAL_CHAINING: {
// Optional chaining: `target.startsWith` => `target?.startsWith`
yield fixer.replaceText(sourceCode.getTokenBefore(node.callee.property), '?.');
// Fallthrough
default:
}
// Fallthrough
default: {
if (
!isRegexParenthesized &&
!isTargetParenthesized &&
shouldAddParenthesesToMemberExpressionObject(target, sourceCode)
!isRegexParenthesized
&& !isTargetParenthesized
&& shouldAddParenthesesToMemberExpressionObject(target, sourceCode)
) {
targetText = addParentheses(targetText);
}
}
}

@@ -164,3 +177,3 @@

// Replace argument with result.string
yield fixer.replaceTextRange(getParenthesizedRange(target, sourceCode), quoteString(result.string));
yield fixer.replaceTextRange(getParenthesizedRange(target, sourceCode), escapeString(result.string));
}

@@ -176,3 +189,3 @@

FIX_TYPE_OPTIONAL_CHAINING,
FIX_TYPE_NULLISH_COALESCING
FIX_TYPE_NULLISH_COALESCING,
].map(type => ({messageId: type, data: {method}, fix: fixer => fix(fixer, type)}));

@@ -182,6 +195,7 @@ }

return problem;
}
},
};
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -192,8 +206,8 @@ create,

docs: {
description: 'Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`.'
description: 'Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`.',
},
fixable: 'code',
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-string-trim-start-end';
const messages = {
[MESSAGE_ID]: 'Prefer `String#{{replacement}}()` over `String#{{method}}()`.'
[MESSAGE_ID]: 'Prefer `String#{{replacement}}()` over `String#{{method}}()`.',
};
const selector = [
methodCallSelector({
names: ['trimLeft', 'trimRight'],
length: 0
}),
' > .callee',
' > .property'
].join(' ');
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
CallExpression(callExpression) {
if (!isMethodCall(callExpression, {
methods: ['trimLeft', 'trimRight'],
argumentsLength: 0,
optionalCall: false,
})) {
return;
}
const create = () => {
return {
[selector](node) {
const method = node.name;
const replacement = method === 'trimLeft' ? 'trimStart' : 'trimEnd';
const node = callExpression.callee.property;
const method = node.name;
const replacement = method === 'trimLeft' ? 'trimStart' : 'trimEnd';
return {
node,
messageId: MESSAGE_ID,
data: {method, replacement},
fix: fixer => fixer.replaceText(node, replacement)
};
}
};
};
return {
node,
messageId: MESSAGE_ID,
data: {method, replacement},
fix: fixer => fixer.replaceText(node, replacement),
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -39,7 +39,7 @@ create,

docs: {
description: 'Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`.'
description: 'Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {hasSideEffect} = require('eslint-utils');
const {hasSideEffect} = require('@eslint-community/eslint-utils');
const isSameReference = require('./utils/is-same-reference.js');

@@ -8,3 +8,3 @@ const getIndentString = require('./utils/get-indent-string.js');

const messages = {
[MESSAGE_ID]: 'Use `switch` instead of multiple `else-if`.'
[MESSAGE_ID]: 'Use `switch` instead of multiple `else-if`.',
};

@@ -65,3 +65,3 @@

compareExpressions,
discriminantCandidates
discriminantCandidates,
);

@@ -77,3 +77,3 @@

statement,
compareExpressions
compareExpressions,
});

@@ -84,3 +84,3 @@ }

ifStatements,
discriminant: discriminantCandidates && discriminantCandidates[0]
discriminant: discriminantCandidates && discriminantCandidates[0],
};

@@ -95,3 +95,3 @@ }

'ForInStatement',
'SwitchStatement'
'SwitchStatement',
]);

@@ -170,9 +170,11 @@ const getBreakTarget = node => {

case 'ReturnStatement':
case 'ThrowStatement':
case 'ThrowStatement': {
return false;
}
case 'IfStatement':
return !node.alternate ||
shouldInsertBreakStatement(node.consequent) ||
shouldInsertBreakStatement(node.alternate);
case 'IfStatement': {
return !node.alternate
|| shouldInsertBreakStatement(node.consequent)
|| shouldInsertBreakStatement(node.alternate);
}

@@ -184,4 +186,5 @@ case 'BlockStatement': {

default:
default: {
return true;
}
}

@@ -215,5 +218,7 @@ }

switch (options.emptyDefaultCase) {
case 'no-default-comment':
case 'no-default-comment': {
yield fixer.insertTextAfter(firstStatement, `\n${indent}// No default`);
break;
}
case 'do-nothing-comment': {

@@ -254,2 +259,3 @@ yield fixer.insertTextAfter(firstStatement, `\n${indent}default:\n${indent}// Do nothing`);

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {

@@ -260,5 +266,5 @@ const options = {

insertBreakInDefaultCase: false,
...context.options[0]
...context.options[0],
};
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const ifStatements = new Set();

@@ -269,7 +275,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);
}
},

@@ -295,10 +303,10 @@ * 'Program:exit'() {

start: node.loc.start,
end: node.consequent.loc.start
end: node.consequent.loc.start,
},
messageId: MESSAGE_ID
messageId: MESSAGE_ID,
};
if (
!hasSideEffect(discriminant, sourceCode) &&
!ifStatements.some(({statement}) => hasBreakInside(breakStatements, statement))
!hasSideEffect(discriminant, sourceCode)
&& !ifStatements.some(({statement}) => hasBreakInside(breakStatements, statement))
) {

@@ -310,3 +318,3 @@ problem.fix = fix({discriminant, ifStatements}, sourceCode, options);

}
}
},
};

@@ -318,2 +326,3 @@ };

type: 'object',
additionalProperties: false,
properties: {

@@ -323,3 +332,3 @@ minimumCases: {

minimum: 2,
default: 3
default: 3,
},

@@ -330,11 +339,11 @@ emptyDefaultCase: {

'do-nothing-comment',
'no-default-case'
'no-default-case',
],
default: 'no-default-comment'
}
default: 'no-default-comment',
},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -345,8 +354,8 @@ create,

docs: {
description: 'Prefer `switch` over multiple `else-if`.'
description: 'Prefer `switch` over multiple `else-if`.',
},
fixable: 'code',
schema,
messages
}
messages,
},
};
'use strict';
const {isParenthesized} = require('eslint-utils');
const {isParenthesized} = require('@eslint-community/eslint-utils');
const avoidCapture = require('./utils/avoid-capture.js');
const extendFixRange = require('./utils/extend-fix-range.js');
const needsSemicolon = require('./utils/needs-semicolon.js');

@@ -10,17 +9,11 @@ const isSameReference = require('./utils/is-same-reference.js');

const shouldAddParenthesesToConditionalExpressionChild = require('./utils/should-add-parentheses-to-conditional-expression-child.js');
const {extendFixRange} = require('./fix/index.js');
const getScopes = require('./utils/get-scopes.js');
const messageId = 'prefer-ternary';
const selector = [
'IfStatement',
':not(IfStatement > .alternate)',
'[test.type!="ConditionalExpression"]',
'[consequent]',
'[alternate]'
].join('');
const isTernary = node => node?.type === 'ConditionalExpression';
const isTernary = node => node && node.type === 'ConditionalExpression';
function getNodeBody(node) {
/* istanbul ignore next */
/* c8 ignore next 3 */
if (!node) {

@@ -44,12 +37,8 @@ return;

const getScopes = scope => [
scope,
...scope.childScopes.flatMap(scope => getScopes(scope))
];
const isSingleLineNode = node => node.loc.start.line === node.loc.end.line;
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const onlySingleLine = context.options[0] === 'only-single-line';
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
const scopeToNamesGeneratedByFixer = new WeakMap();

@@ -64,4 +53,4 @@ const isSafeName = (name, scopes) => scopes.every(scope => {

if (
!isParenthesized(node, sourceCode) &&
shouldAddParenthesesToConditionalExpressionChild(node)
!isParenthesized(node, sourceCode)
&& shouldAddParenthesesToConditionalExpressionChild(node)
) {

@@ -80,3 +69,3 @@ text = `(${text})`;

alternate,
node
node,
} = options;

@@ -86,7 +75,7 @@

checkThrowStatement,
returnFalseIfNotMergeable
returnFalseIfNotMergeable,
} = {
checkThrowStatement: false,
returnFalseIfNotMergeable: false,
...mergeOptions
...mergeOptions,
};

@@ -101,5 +90,5 @@

if (
type === 'ReturnStatement' &&
!isTernary(argument) &&
!isTernary(alternate.argument)
type === 'ReturnStatement'
&& !isTernary(argument)
&& !isTernary(alternate.argument)
) {

@@ -111,3 +100,3 @@ return merge({

alternate: alternate.argument === null ? 'undefined' : alternate.argument,
node
node,
});

@@ -117,6 +106,6 @@ }

if (
type === 'YieldExpression' &&
delegate === alternate.delegate &&
!isTernary(argument) &&
!isTernary(alternate.argument)
type === 'YieldExpression'
&& delegate === alternate.delegate
&& !isTernary(argument)
&& !isTernary(alternate.argument)
) {

@@ -128,3 +117,3 @@ return merge({

alternate: alternate.argument === null ? 'undefined' : alternate.argument,
node
node,
});

@@ -134,5 +123,5 @@ }

if (
type === 'AwaitExpression' &&
!isTernary(argument) &&
!isTernary(alternate.argument)
type === 'AwaitExpression'
&& !isTernary(argument)
&& !isTernary(alternate.argument)
) {

@@ -144,3 +133,3 @@ return merge({

alternate: alternate.argument,
node
node,
});

@@ -150,6 +139,6 @@ }

if (
checkThrowStatement &&
type === 'ThrowStatement' &&
!isTernary(argument) &&
!isTernary(alternate.argument)
checkThrowStatement
&& type === 'ThrowStatement'
&& !isTernary(argument)
&& !isTernary(alternate.argument)
) {

@@ -166,3 +155,3 @@ // `ThrowStatement` don't check nested

consequent: argument,
alternate: alternate.argument
alternate: alternate.argument,
};

@@ -172,9 +161,9 @@ }

if (
type === 'AssignmentExpression' &&
operator === alternate.operator &&
!isTernary(left) &&
!isTernary(alternate.left) &&
!isTernary(right) &&
!isTernary(alternate.right) &&
isSameReference(left, alternate.left)
type === 'AssignmentExpression'
&& operator === alternate.operator
&& !isTernary(left)
&& !isTernary(alternate.left)
&& !isTernary(right)
&& !isTernary(alternate.right)
&& isSameReference(left, alternate.left)
) {

@@ -186,3 +175,3 @@ return merge({

alternate: alternate.right,
node
node,
});

@@ -195,3 +184,12 @@ }

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);

@@ -201,4 +199,4 @@ const alternate = getNodeBody(node.alternate);

if (
onlySingleLine &&
[consequent, alternate, node.test].some(node => !isSingleLineNode(node))
onlySingleLine
&& [consequent, alternate, node.test].some(node => !isSingleLineNode(node))
) {

@@ -210,3 +208,3 @@ return;

checkThrowStatement: true,
returnFalseIfNotMergeable: true
returnFalseIfNotMergeable: true,
});

@@ -218,58 +216,62 @@

const scope = context.getScope();
const problem = {node, messageId};
return {
node,
messageId,
* fix(fixer) {
const testText = getText(node.test);
const consequentText = typeof result.consequent === 'string' ?
result.consequent :
getText(result.consequent);
const alternateText = typeof result.alternate === 'string' ?
result.alternate :
getText(result.alternate);
// Don't fix if there are comments
if (sourceCode.getCommentsInside(node).length > 0) {
return problem;
}
let {type, before, after} = result;
const scope = sourceCode.getScope(node);
problem.fix = function * (fixer) {
const testText = getText(node.test);
const consequentText = typeof result.consequent === 'string'
? result.consequent
: getText(result.consequent);
const alternateText = typeof result.alternate === 'string'
? result.alternate
: getText(result.alternate);
let generateNewVariables = false;
if (type === 'ThrowStatement') {
const scopes = getScopes(scope);
const errorName = avoidCapture('error', scopes, context.parserOptions.ecmaVersion, isSafeName);
let {type, before, after} = result;
for (const scope of scopes) {
if (!scopeToNamesGeneratedByFixer.has(scope)) {
scopeToNamesGeneratedByFixer.set(scope, new Set());
}
let generateNewVariables = false;
if (type === 'ThrowStatement') {
const scopes = getScopes(scope);
const errorName = avoidCapture('error', scopes, isSafeName);
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
generatedNames.add(errorName);
for (const scope of scopes) {
if (!scopeToNamesGeneratedByFixer.has(scope)) {
scopeToNamesGeneratedByFixer.set(scope, new Set());
}
const indentString = getIndentString(node, sourceCode);
after = after
.replace('{{INDENT_STRING}}', indentString)
.replace('{{ERROR_NAME}}', errorName);
before = before
.replace('{{INDENT_STRING}}', indentString)
.replace('{{ERROR_NAME}}', errorName);
generateNewVariables = true;
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
generatedNames.add(errorName);
}
let fixed = `${before}${testText} ? ${consequentText} : ${alternateText}${after}`;
const tokenBefore = sourceCode.getTokenBefore(node);
const shouldAddSemicolonBefore = needsSemicolon(tokenBefore, sourceCode, fixed);
if (shouldAddSemicolonBefore) {
fixed = `;${fixed}`;
}
const indentString = getIndentString(node, sourceCode);
yield fixer.replaceText(node, fixed);
after = after
.replace('{{INDENT_STRING}}', indentString)
.replace('{{ERROR_NAME}}', errorName);
before = before
.replace('{{INDENT_STRING}}', indentString)
.replace('{{ERROR_NAME}}', errorName);
generateNewVariables = true;
}
if (generateNewVariables) {
yield * extendFixRange(fixer, sourceCode.ast.range);
}
let fixed = `${before}${testText} ? ${consequentText} : ${alternateText}${after}`;
const tokenBefore = sourceCode.getTokenBefore(node);
const shouldAddSemicolonBefore = needsSemicolon(tokenBefore, sourceCode, fixed);
if (shouldAddSemicolonBefore) {
fixed = `;${fixed}`;
}
yield fixer.replaceText(node, fixed);
if (generateNewVariables) {
yield * extendFixRange(fixer, sourceCode.ast.range);
}
};
}
return problem;
},
};

@@ -281,6 +283,7 @@ };

enum: ['always', 'only-single-line'],
default: 'always'
}
default: 'always',
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -291,3 +294,3 @@ create,

docs: {
description: 'Prefer ternary expressions over simple `if-else` statements.'
description: 'Prefer ternary expressions over simple `if-else` statements.',
},

@@ -297,5 +300,5 @@ fixable: 'code',

messages: {
[messageId]: 'This `if` statement can be replaced by a ternary expression.'
}
}
[messageId]: 'This `if` statement can be replaced by a ternary expression.',
},
},
};
'use strict';
const {findVariable, getFunctionHeadLocation} = require('eslint-utils');
const {matches, memberExpressionSelector} = require('./selectors/index.js');
const {findVariable, getFunctionHeadLocation} = require('@eslint-community/eslint-utils');
const {isFunction, isMemberExpression, isMethodCall} = require('./ast/index.js');

@@ -13,45 +13,100 @@ const ERROR_PROMISE = 'promise';

[ERROR_IDENTIFIER]: 'Prefer top-level await over an async function `{{name}}` call.',
[SUGGESTION_ADD_AWAIT]: 'Insert `await`.'
[SUGGESTION_ADD_AWAIT]: 'Insert `await`.',
};
const topLevelCallExpression = 'Program > ExpressionStatement > CallExpression[optional!=true].expression';
const iife = [
topLevelCallExpression,
matches([
'[callee.type="FunctionExpression"]',
'[callee.type="ArrowFunctionExpression"]'
]),
'[callee.async!=false]',
'[callee.generator!=true]'
].join('');
const promise = [
topLevelCallExpression,
memberExpressionSelector({
path: 'callee',
names: ['then', 'catch', 'finally']
const promisePrototypeMethods = ['then', 'catch', 'finally'];
const isTopLevelCallExpression = node => {
if (node.type !== 'CallExpression') {
return false;
}
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 =>
node.parent.type === 'MemberExpression'
&& node.parent.object === node
&& !node.parent.computed
&& node.parent.property.type === 'Identifier'
&& promisePrototypeMethods.includes(node.parent.property.name)
&& node.parent.parent.type === 'CallExpression'
&& node.parent.parent.callee === node.parent;
const isAwaitExpressionArgument = node => {
if (node.parent.type === 'ChainExpression') {
node = node.parent;
}
return node.parent.type === 'AwaitExpression' && node.parent.argument === node;
};
// `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,
})
].join('');
const identifier = [
topLevelCallExpression,
'[callee.type="Identifier"]'
].join('');
&& 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) {
return {
node: node.callee.property,
messageId: ERROR_PROMISE
};
},
[iife](node) {
return {
node,
loc: getFunctionHeadLocation(node.callee, context.getSourceCode()),
messageId: ERROR_IIFE
};
},
[identifier](node) {
const variable = findVariable(context.getScope(), node.callee);
CallExpression(node) {
if (
!isTopLevelCallExpression(node)
|| isPromiseMethodCalleeObject(node)
|| isAwaitExpressionArgument(node)
|| isInPromiseMethods(node)
) {
return;
}
// Promises
if (isMemberExpression(node.callee, {
properties: promisePrototypeMethods,
computed: false,
})) {
return {
node: node.callee.property,
messageId: ERROR_PROMISE,
};
}
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(sourceCode.getScope(node), node.callee);
if (!variable || variable.defs.length !== 1) {

@@ -62,13 +117,8 @@ return;

const [definition] = variable.defs;
const value = definition.type === 'Variable' && definition.kind === 'const' ?
definition.node.init :
definition.node;
const value = definition.type === 'Variable' && definition.kind === 'const'
? definition.node.init
: definition.node;
if (
!(
(
value.type === 'ArrowFunctionExpression' ||
value.type === 'FunctionExpression' ||
value.type === 'FunctionDeclaration'
) && !value.generator && value.async
)
!value
|| !(isFunction(value) && !value.generator && value.async)
) {

@@ -85,10 +135,11 @@ return;

messageId: SUGGESTION_ADD_AWAIT,
fix: fixer => fixer.insertTextBefore(node, 'await ')
}
]
fix: fixer => fixer.insertTextBefore(node, 'await '),
},
],
};
}
},
};
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -99,7 +150,7 @@ create,

docs: {
description: 'Prefer top-level await over top-level promises and async function calls.'
description: 'Prefer top-level await over top-level promises and async function calls.',
},
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {newExpressionSelector} = require('./selectors/index.js');
const {isNewExpression} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-type-error';
const messages = {
[MESSAGE_ID]: '`new Error()` is too unspecific for a type check. Use `new TypeError()` instead.'
[MESSAGE_ID]: '`new Error()` is too unspecific for a type check. Use `new TypeError()` instead.',
};
const tcIdentifiers = new Set([
const typeCheckIdentifiers = new Set([
'isArguments',

@@ -46,23 +46,18 @@ 'isArray',

'isWindow',
'isXMLDoc'
'isXMLDoc',
]);
const tcGlobalIdentifiers = new Set([
const typeCheckGlobalIdentifiers = new Set([
'isNaN',
'isFinite'
'isFinite',
]);
const selector = [
'ThrowStatement',
newExpressionSelector({name: 'Error', path: 'argument'})
].join('');
const isTypecheckingIdentifier = (node, callExpression, isMemberExpression) =>
callExpression !== undefined &&
callExpression.arguments.length > 0 &&
node.type === 'Identifier' &&
((isMemberExpression === true &&
tcIdentifiers.has(node.name)) ||
(isMemberExpression === false &&
tcGlobalIdentifiers.has(node.name)));
callExpression !== undefined
&& callExpression.arguments.length > 0
&& node.type === 'Identifier'
&& (
(isMemberExpression === true && typeCheckIdentifiers.has(node.name))
|| (isMemberExpression === false && typeCheckGlobalIdentifiers.has(node.name))
);

@@ -85,26 +80,39 @@ const isLone = node => node.parent && node.parent.body && node.parent.body.length === 1;

switch (node.type) {
case 'Identifier':
case 'Identifier': {
return isTypecheckingIdentifier(node, callExpression, false);
case 'MemberExpression':
}
case 'MemberExpression': {
return isTypecheckingMemberExpression(node, callExpression);
case 'CallExpression':
}
case 'CallExpression': {
return isTypecheckingExpression(node.callee, node);
case 'UnaryExpression':
}
case 'UnaryExpression': {
return (
node.operator === 'typeof' ||
(node.operator === '!' && isTypecheckingExpression(node.argument))
node.operator === 'typeof'
|| (node.operator === '!' && isTypecheckingExpression(node.argument))
);
case 'BinaryExpression':
}
case 'BinaryExpression': {
return (
node.operator === 'instanceof' ||
isTypecheckingExpression(node.left, callExpression) ||
isTypecheckingExpression(node.right, callExpression)
node.operator === 'instanceof'
|| isTypecheckingExpression(node.left, callExpression)
|| isTypecheckingExpression(node.right, callExpression)
);
case 'LogicalExpression':
}
case 'LogicalExpression': {
return (
isTypecheckingExpression(node.left, callExpression) &&
isTypecheckingExpression(node.right, callExpression)
isTypecheckingExpression(node.left, callExpression)
&& isTypecheckingExpression(node.right, callExpression)
);
default:
}
default: {
return false;
}
}

@@ -115,21 +123,22 @@ };

const create = () => {
return {
[selector]: node => {
if (
isLone(node) &&
node.parent.parent &&
isTypechecking(node.parent.parent)
) {
const errorConstructor = node.argument.callee;
return {
node: errorConstructor,
messageId: MESSAGE_ID,
fix: fixer => fixer.insertTextBefore(errorConstructor, 'Type')
};
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
ThrowStatement(node) {
if (
isNewExpression(node.argument, {name: 'Error'})
&& isLone(node)
&& node.parent.parent
&& isTypechecking(node.parent.parent)
) {
const errorConstructor = node.argument.callee;
return {
node: errorConstructor,
messageId: MESSAGE_ID,
fix: fixer => fixer.insertTextBefore(errorConstructor, 'Type'),
};
}
};
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -140,7 +149,7 @@ create,

docs: {
description: 'Enforce throwing `TypeError` in type checking conditions.'
description: 'Enforce throwing `TypeError` in type checking conditions.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const path = require('path');
const path = require('node:path');
const {defaultsDeep, upperFirst, lowerFirst} = require('lodash');
const avoidCapture = require('./utils/avoid-capture.js');

@@ -10,6 +9,15 @@ const cartesianProductSamples = require('./utils/cartesian-product-samples.js');

const getVariableIdentifiers = require('./utils/get-variable-identifiers.js');
const renameVariable = require('./utils/rename-variable.js');
const isStaticRequire = require('./utils/is-static-require.js');
const {defaultReplacements, defaultAllowList} = require('./shared/abbreviations.js');
const {defaultReplacements, defaultAllowList, defaultIgnore} = require('./shared/abbreviations.js');
const {renameVariable} = require('./fix/index.js');
const getScopes = require('./utils/get-scopes.js');
const {isStaticRequire} = require('./ast/index.js');
const MESSAGE_ID_REPLACE = 'replace';
const MESSAGE_ID_SUGGESTION = 'suggestion';
const anotherNameMessage = 'A more descriptive name will do too.';
const messages = {
[MESSAGE_ID_REPLACE]: `The {{nameTypeText}} \`{{discouragedName}}\` should be named \`{{replacement}}\`. ${anotherNameMessage}`,
[MESSAGE_ID_SUGGESTION]: `Please rename the {{nameTypeText}} \`{{discouragedName}}\`. Suggested names are: {{replacementsText}}. ${anotherNameMessage}`,
};
const isUpperCase = string => string === string.toUpperCase();

@@ -34,14 +42,16 @@ const isUpperFirst = string => isUpperCase(string[0]);

ignore = []
ignore = [],
} = {}) => {
const mergedReplacements = extendDefaultReplacements ?
defaultsDeep({}, replacements, defaultReplacements) :
replacements;
const mergedReplacements = extendDefaultReplacements
? defaultsDeep({}, replacements, defaultReplacements)
: replacements;
const mergedAllowList = extendDefaultAllowList ?
defaultsDeep({}, allowList, defaultAllowList) :
allowList;
const mergedAllowList = extendDefaultAllowList
? defaultsDeep({}, allowList, defaultAllowList)
: allowList;
ignore = [...defaultIgnore, ...ignore];
ignore = ignore.map(
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
);

@@ -62,8 +72,8 @@

([discouragedName, replacements]) =>
[discouragedName, new Map(Object.entries(replacements))]
)
[discouragedName, new Map(Object.entries(replacements))],
),
),
allowList: new Map(Object.entries(mergedAllowList)),
ignore
ignore,
};

@@ -78,5 +88,5 @@ };

const replacement = replacements.get(lowerFirst(word)) ||
replacements.get(word) ||
replacements.get(upperFirst(word));
const replacement = replacements.get(lowerFirst(word))
|| replacements.get(word)
|| replacements.get(upperFirst(word));

@@ -108,3 +118,3 @@ let wordReplacement = [];

total: exactReplacements.length,
samples: exactReplacements.slice(0, limit)
samples: exactReplacements.slice(0, limit),
};

@@ -135,38 +145,52 @@ }

total,
samples
samples,
} = cartesianProductSamples(combinations, limit);
// `retVal` -> `['returnValue', 'Value']` -> `['returnValue']`
for (const parts of samples) {
for (let index = parts.length - 1; index > 0; index--) {
const word = parts[index];
if (/^[A-Za-z]+$/.test(word) && parts[index - 1].endsWith(parts[index])) {
parts.splice(index, 1);
}
}
}
return {
total,
samples: samples.map(words => words.join(''))
samples: samples.map(words => words.join('')),
};
};
const anotherNameMessage = 'A more descriptive name will do too.';
const formatMessage = (discouragedName, replacements, nameTypeText) => {
const message = [];
const getMessage = (discouragedName, replacements, nameTypeText) => {
const {total, samples = []} = replacements;
if (total === 1) {
message.push(`The ${nameTypeText} \`${discouragedName}\` should be named \`${samples[0]}\`.`);
} else {
let replacementsText = samples
.map(replacement => `\`${replacement}\``)
.join(', ');
return {
messageId: MESSAGE_ID_REPLACE,
data: {
nameTypeText,
discouragedName,
replacement: samples[0],
},
};
}
const omittedReplacementsCount = total - samples.length;
if (omittedReplacementsCount > 0) {
replacementsText += `, ... (${omittedReplacementsCount > 99 ? '99+' : omittedReplacementsCount} more omitted)`;
}
let replacementsText = samples
.map(replacement => `\`${replacement}\``)
.join(', ');
message.push(
`Please rename the ${nameTypeText} \`${discouragedName}\`.`,
`Suggested names are: ${replacementsText}.`
);
const omittedReplacementsCount = total - samples.length;
if (omittedReplacementsCount > 0) {
replacementsText += `, ... (${omittedReplacementsCount > 99 ? '99+' : omittedReplacementsCount} more omitted)`;
}
message.push(anotherNameMessage);
return message.join(' ');
return {
messageId: MESSAGE_ID_SUGGESTION,
data: {
nameTypeText,
discouragedName,
replacementsText,
},
};
};

@@ -176,8 +200,8 @@

if (
identifier.parent.type === 'VariableDeclarator' &&
identifier.parent.id === identifier
identifier.parent.type === 'VariableDeclarator'
&& identifier.parent.id === identifier
) {
return (
identifier.parent.parent.type === 'VariableDeclaration' &&
identifier.parent.parent.parent.type === 'ExportNamedDeclaration'
identifier.parent.parent.type === 'VariableDeclaration'
&& identifier.parent.parent.parent.type === 'ExportNamedDeclaration'
);

@@ -187,4 +211,4 @@ }

if (
identifier.parent.type === 'FunctionDeclaration' &&
identifier.parent.id === identifier
identifier.parent.type === 'FunctionDeclaration'
&& identifier.parent.id === identifier
) {

@@ -195,4 +219,4 @@ return identifier.parent.parent.type === 'ExportNamedDeclaration';

if (
identifier.parent.type === 'ClassDeclaration' &&
identifier.parent.id === identifier
identifier.parent.type === 'ClassDeclaration'
&& identifier.parent.id === identifier
) {

@@ -203,4 +227,4 @@ return identifier.parent.parent.type === 'ExportNamedDeclaration';

if (
identifier.parent.type === 'TSTypeAliasDeclaration' &&
identifier.parent.id === identifier
identifier.parent.type === 'TSTypeAliasDeclaration'
&& identifier.parent.id === identifier
) {

@@ -213,10 +237,14 @@ return identifier.parent.parent.type === 'ExportNamedDeclaration';

const shouldFix = variable => {
return !getVariableIdentifiers(variable).some(identifier => isExportedIdentifier(identifier));
};
const shouldFix = variable => getVariableIdentifiers(variable)
.every(identifier =>
!isExportedIdentifier(identifier)
// In typescript parser, only `JSXOpeningElement` is added to variable
// `<foo></foo>` -> `<bar></foo>` will cause parse error
&& identifier.type !== 'JSXIdentifier',
);
const isDefaultOrNamespaceImportName = identifier => {
if (
identifier.parent.type === 'ImportDefaultSpecifier' &&
identifier.parent.local === identifier
identifier.parent.type === 'ImportDefaultSpecifier'
&& identifier.parent.local === identifier
) {

@@ -227,4 +255,4 @@ return true;

if (
identifier.parent.type === 'ImportNamespaceSpecifier' &&
identifier.parent.local === identifier
identifier.parent.type === 'ImportNamespaceSpecifier'
&& identifier.parent.local === identifier
) {

@@ -235,6 +263,6 @@ return true;

if (
identifier.parent.type === 'ImportSpecifier' &&
identifier.parent.local === identifier &&
identifier.parent.imported.type === 'Identifier' &&
identifier.parent.imported.name === 'default'
identifier.parent.type === 'ImportSpecifier'
&& identifier.parent.local === identifier
&& identifier.parent.imported.type === 'Identifier'
&& identifier.parent.imported.name === 'default'
) {

@@ -245,5 +273,5 @@ return true;

if (
identifier.parent.type === 'VariableDeclarator' &&
identifier.parent.id === identifier &&
isStaticRequire(identifier.parent.init)
identifier.parent.type === 'VariableDeclarator'
&& identifier.parent.id === identifier
&& isStaticRequire(identifier.parent.init)
) {

@@ -268,7 +296,7 @@ return true;

if (
identifier.parent.type === 'MemberExpression' &&
identifier.parent.property === identifier &&
!identifier.parent.computed &&
identifier.parent.parent.type === 'AssignmentExpression' &&
identifier.parent.parent.left === identifier.parent
identifier.parent.type === 'MemberExpression'
&& identifier.parent.property === identifier
&& !identifier.parent.computed
&& identifier.parent.parent.type === 'AssignmentExpression'
&& identifier.parent.parent.left === identifier.parent
) {

@@ -279,7 +307,7 @@ return true;

if (
identifier.parent.type === 'Property' &&
identifier.parent.key === identifier &&
!identifier.parent.computed &&
!identifier.parent.shorthand && // Shorthand properties are reported and fixed as variables
identifier.parent.parent.type === 'ObjectExpression'
identifier.parent.type === 'Property'
&& identifier.parent.key === identifier
&& !identifier.parent.computed
&& !identifier.parent.shorthand // Shorthand properties are reported and fixed as variables
&& identifier.parent.parent.type === 'ObjectExpression'
) {

@@ -290,5 +318,5 @@ return true;

if (
identifier.parent.type === 'ExportSpecifier' &&
identifier.parent.exported === identifier &&
identifier.parent.local !== identifier // Same as shorthand properties above
identifier.parent.type === 'ExportSpecifier'
&& identifier.parent.exported === identifier
&& identifier.parent.local !== identifier // Same as shorthand properties above
) {

@@ -299,5 +327,8 @@ return true;

if (
identifier.parent.type === 'MethodDefinition' &&
identifier.parent.key === identifier &&
!identifier.parent.computed
(
identifier.parent.type === 'MethodDefinition'
|| identifier.parent.type === 'PropertyDefinition'
)
&& identifier.parent.key === identifier
&& !identifier.parent.computed
) {

@@ -307,10 +338,2 @@ return true;

if (
(identifier.parent.type === 'ClassProperty' || identifier.parent.type === 'PropertyDefinition') &&
identifier.parent.key === identifier &&
!identifier.parent.computed
) {
return true;
}
return false;

@@ -329,14 +352,14 @@ };

return (
!source.includes('node_modules') &&
(source.startsWith('.') || source.startsWith('/'))
!source.includes('node_modules')
&& (source.startsWith('.') || source.startsWith('/'))
);
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {ecmaVersion} = context.parserOptions;
const options = prepareOptions(context.options[0]);
const filenameWithExtension = context.getPhysicalFilename();
const filenameWithExtension = context.physicalFilename;
// A `class` declaration produces two variables in two scopes:
// the inner class scope, and the outer one (whereever the class is declared).
// the inner class scope, and the outer one (wherever the class is declared).
// This map holds the outer ones to be later processed when the inner one is encountered.

@@ -363,3 +386,3 @@ // For why this is not a eslint issue see https://github.com/eslint/eslint-scope/issues/48#issuecomment-464358754

identifiers: variable.identifiers,
references: [...variable.references, ...outerClassVariable.references]
references: [...variable.references, ...outerClassVariable.references],
};

@@ -402,4 +425,4 @@

if (
options.checkDefaultAndNamespaceImports === 'internal' &&
!isInternalImport(definition)
options.checkDefaultAndNamespaceImports === 'internal'
&& !isInternalImport(definition)
) {

@@ -416,4 +439,4 @@ return;

if (
options.checkShorthandImports === 'internal' &&
!isInternalImport(definition)
options.checkShorthandImports === 'internal'
&& !isInternalImport(definition)
) {

@@ -425,4 +448,4 @@ return;

if (
!options.checkShorthandProperties &&
isShorthandPropertyValue(definition.name)
!options.checkShorthandProperties
&& isShorthandPropertyValue(definition.name)
) {

@@ -440,14 +463,19 @@ return;

...variable.references.map(reference => reference.from),
variable.scope
variable.scope,
];
variableReplacements.samples = variableReplacements.samples.map(
name => avoidCapture(name, scopes, ecmaVersion, isSafeName)
name => avoidCapture(name, scopes, isSafeName),
);
const problem = {
...getMessage(definition.name.name, variableReplacements, 'variable'),
node: definition.name,
message: formatMessage(definition.name.name, variableReplacements, 'variable')
};
if (variableReplacements.total === 1 && shouldFix(variable)) {
if (
variableReplacements.total === 1
&& shouldFix(variable)
&& variableReplacements.samples[0]
&& !variable.references.some(reference => reference.vueUsedInTemplate)
) {
const [replacement] = variableReplacements.samples;

@@ -476,14 +504,9 @@

const checkChildScopes = scope => {
for (const childScope of scope.childScopes) {
checkScope(childScope);
const checkScope = scope => {
const scopes = getScopes(scope);
for (const scope of scopes) {
checkVariables(scope);
}
};
const checkScope = scope => {
checkVariables(scope);
return checkChildScopes(scope);
};
return {

@@ -510,4 +533,4 @@ Identifier(node) {

const problem = {
...getMessage(node.name, identifierReplacements, 'property'),
node,
message: formatMessage(node.name, identifierReplacements, 'property')
};

@@ -524,4 +547,4 @@

if (
filenameWithExtension === '<input>' ||
filenameWithExtension === '<text>'
filenameWithExtension === '<input>'
|| filenameWithExtension === '<text>'
) {

@@ -531,5 +554,5 @@ return;

const extension = path.extname(filenameWithExtension);
const filename = path.basename(filenameWithExtension, extension);
const filenameReplacements = getNameReplacements(filename, options);
const filename = path.basename(filenameWithExtension);
const extension = path.extname(filename);
const filenameReplacements = getNameReplacements(path.basename(filename, extension), options);

@@ -543,8 +566,8 @@ if (filenameReplacements.total === 0) {

context.report({
...getMessage(filename, filenameReplacements, 'filename'),
node,
message: formatMessage(filenameWithExtension, filenameReplacements, 'filename')
});
},
'Program:exit'() {
'Program:exit'(program) {
if (!options.checkVariables) {

@@ -554,84 +577,89 @@ return;

checkScope(context.getScope());
}
checkScope(context.sourceCode.getScope(program));
},
};
};
const schema = [
{
type: 'object',
properties: {
checkProperties: {
type: 'boolean'
const schema = {
type: 'array',
additionalItems: false,
items: [
{
type: 'object',
additionalProperties: false,
properties: {
checkProperties: {
type: 'boolean',
},
checkVariables: {
type: 'boolean',
},
checkDefaultAndNamespaceImports: {
type: [
'boolean',
'string',
],
pattern: 'internal',
},
checkShorthandImports: {
type: [
'boolean',
'string',
],
pattern: 'internal',
},
checkShorthandProperties: {
type: 'boolean',
},
checkFilenames: {
type: 'boolean',
},
extendDefaultReplacements: {
type: 'boolean',
},
replacements: {
$ref: '#/definitions/abbreviations',
},
extendDefaultAllowList: {
type: 'boolean',
},
allowList: {
$ref: '#/definitions/booleanObject',
},
ignore: {
type: 'array',
uniqueItems: true,
},
},
checkVariables: {
type: 'boolean'
},
],
definitions: {
abbreviations: {
type: 'object',
additionalProperties: {
$ref: '#/definitions/replacements',
},
checkDefaultAndNamespaceImports: {
type: [
'boolean',
'string'
],
pattern: 'internal'
},
replacements: {
anyOf: [
{
enum: [
false,
],
},
{
$ref: '#/definitions/booleanObject',
},
],
},
booleanObject: {
type: 'object',
additionalProperties: {
type: 'boolean',
},
checkShorthandImports: {
type: [
'boolean',
'string'
],
pattern: 'internal'
},
checkShorthandProperties: {
type: 'boolean'
},
checkFilenames: {
type: 'boolean'
},
extendDefaultReplacements: {
type: 'boolean'
},
replacements: {
$ref: '#/items/0/definitions/abbreviations'
},
extendDefaultAllowList: {
type: 'boolean'
},
allowList: {
$ref: '#/items/0/definitions/booleanObject'
},
ignore: {
type: 'array',
uniqueItems: true
}
},
additionalProperties: false,
definitions: {
abbreviations: {
type: 'object',
additionalProperties: {
$ref: '#/items/0/definitions/replacements'
}
},
replacements: {
anyOf: [
{
enum: [
false
]
},
{
$ref: '#/items/0/definitions/booleanObject'
}
]
},
booleanObject: {
type: 'object',
additionalProperties: {
type: 'boolean'
}
}
}
}
];
},
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -642,7 +670,8 @@ create,

docs: {
description: 'Prevent abbreviations.'
description: 'Prevent abbreviations.',
},
fixable: 'code',
schema
}
schema,
messages,
},
};
'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');
const MESSAGE_ID = 'require-array-join-separator';
const messages = {
[MESSAGE_ID]: 'Missing the separator argument.'
[MESSAGE_ID]: 'Missing the separator argument.',
};
const selector = matches([
// `foo.join()`
methodCallSelector({name: 'join', length: 0}),
// `[].join.call(foo)` and `Array.prototype.join.call(foo)`
[
methodCallSelector({name: 'call', length: 1}),
arrayPrototypeMethodSelector({path: 'callee.object', name: '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} */
module.exports = {

@@ -45,7 +58,7 @@ create,

docs: {
description: 'Enforce using the separator argument with `Array#join()`.'
description: 'Enforce using the separator argument with `Array#join()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {appendArgument} = require('./fix/index.js');
const {isMethodCall} = require('./ast/index.js');
const MESSAGE_ID = 'require-number-to-fixed-digits-argument';
const messages = {
[MESSAGE_ID]: 'Missing the digits argument.'
[MESSAGE_ID]: 'Missing the digits argument.',
};
const mathToFixed = methodCallSelector({
name: 'toFixed',
length: 0
});
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
return {
[mathToFixed](node) {
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)
};
const create = context => ({
CallExpression(node) {
if (
!isMethodCall(node, {
method: 'toFixed',
argumentsLength: 0,
optionalCall: false,
optionalMember: false,
})
|| node.callee.object.type === 'NewExpression'
) {
return;
}
};
};
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} */
module.exports = {

@@ -43,7 +49,7 @@ create,

docs: {
description: 'Enforce using the digits argument with `Number#toFixed()`.'
description: 'Enforce using the digits argument with `Number#toFixed()`.',
},
fixable: 'code',
messages
}
messages,
},
};
'use strict';
const {methodCallSelector} = require('./selectors/index.js');
const {isMethodCall} = require('./ast/index.js');
const {appendArgument} = require('./fix/index.js');
const ERROR = 'error';
const SUGGESTION_TARGET_LOCATION_ORIGIN = 'target-location-origin';
const SUGGESTION_SELF_LOCATION_ORIGIN = 'self-location-origin';
const SUGGESTION_STAR = 'star';
const SUGGESTION = 'suggestion';
const messages = {
[ERROR]: 'Missing the `targetOrigin` argument.',
[SUGGESTION_TARGET_LOCATION_ORIGIN]: 'Use `{{target}}.location.origin`.',
[SUGGESTION_SELF_LOCATION_ORIGIN]: 'Use `self.location.origin`.',
[SUGGESTION_STAR]: 'Use `"*"`.'
[SUGGESTION]: 'Use `{{code}}`.',
};

@@ -18,7 +14,16 @@

function create(context) {
const sourceCode = context.getSourceCode();
const {sourceCode} = context;
return {
[methodCallSelector({name: 'postMessage', length: 1})](node) {
CallExpression(node) {
if (!isMethodCall(node, {
method: 'postMessage',
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
})) {
return;
}
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
const suggestions = [];
const replacements = [];
const target = node.callee.object;

@@ -28,16 +33,12 @@ if (target.type === 'Identifier') {

suggestions.push({
messageId: SUGGESTION_TARGET_LOCATION_ORIGIN,
data: {target: name},
code: `${target.name}.location.origin`
});
replacements.push(`${name}.location.origin`);
if (name !== 'self' && name !== 'window' && name !== 'globalThis') {
suggestions.push({messageId: SUGGESTION_SELF_LOCATION_ORIGIN, code: 'self.location.origin'});
replacements.push('self.location.origin');
}
} else {
suggestions.push({messageId: SUGGESTION_SELF_LOCATION_ORIGIN, code: 'self.location.origin'});
replacements.push('self.location.origin');
}
suggestions.push({messageId: SUGGESTION_STAR, code: '\'*\''});
replacements.push('\'*\'');

@@ -47,16 +48,17 @@ return {

start: penultimateToken.loc.end,
end: lastToken.loc.end
end: lastToken.loc.end,
},
messageId: ERROR,
suggest: suggestions.map(({messageId, data, code}) => ({
messageId,
data,
suggest: replacements.map(code => ({
messageId: SUGGESTION,
data: {code},
/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => appendArgument(fixer, node, code, sourceCode)
}))
fix: fixer => appendArgument(fixer, node, code, sourceCode),
})),
};
}
},
};
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -67,7 +69,7 @@ create,

docs: {
description: 'Enforce using the `targetOrigin` argument with `window.postMessage()`.'
description: 'Enforce using the `targetOrigin` argument with `window.postMessage()`.',
},
hasSuggestions: true,
messages,
hasSuggestions: true
}
},
};

@@ -5,153 +5,170 @@ /* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */

acc: {
accumulator: true
accumulator: true,
},
arg: {
argument: true
argument: true,
},
args: {
arguments: true
arguments: true,
},
arr: {
array: true
array: true,
},
attr: {
attribute: true
attribute: true,
},
attrs: {
attributes: true
attributes: true,
},
btn: {
button: true
button: true,
},
cb: {
callback: true
callback: true,
},
conf: {
config: true
config: true,
},
ctx: {
context: true
context: true,
},
cur: {
current: true
current: true,
},
curr: {
current: true
current: true,
},
db: {
database: true
database: true,
},
def: {
defer: true,
deferred: true,
define: true,
definition: true,
},
dest: {
destination: true
destination: true,
},
dev: {
development: true
development: true,
},
dir: {
direction: true,
directory: true
directory: true,
},
dirs: {
directories: true
directories: true,
},
dist: {
distribution: true,
},
doc: {
document: true
document: true,
},
docs: {
documentation: true,
documents: true
documents: true,
},
dst: {
daylightSavingTime: true,
destination: true,
distribution: true,
},
e: {
error: true,
event: true
event: true,
},
el: {
element: true
element: true,
},
elem: {
element: true
element: true,
},
elems: {
elements: true,
},
env: {
environment: true
environment: true,
},
envs: {
environments: true
environments: true,
},
err: {
error: true
error: true,
},
ev: {
event: true
event: true,
},
evt: {
event: true
event: true,
},
ext: {
extension: true
extension: true,
},
exts: {
extensions: true
extensions: true,
},
fn: {
function: true
function: true,
},
func: {
function: true
function: true,
},
i: {
index: true
index: true,
},
idx: {
index: true
index: true,
},
j: {
index: true
index: true,
},
len: {
length: true
length: true,
},
lib: {
library: true
library: true,
},
mod: {
module: true
module: true,
},
msg: {
message: true
message: true,
},
num: {
number: true
number: true,
},
obj: {
object: true
object: true,
},
opts: {
options: true
options: true,
},
param: {
parameter: true
parameter: true,
},
params: {
parameters: true
parameters: true,
},
pkg: {
package: true
package: true,
},
prev: {
previous: true
previous: true,
},
prod: {
production: true
production: true,
},
prop: {
property: true
property: true,
},
props: {
properties: true
properties: true,
},
ref: {
reference: true
reference: true,
},
refs: {
references: true
references: true,
},

@@ -161,53 +178,53 @@ rel: {

relationship: true,
relative: true
relative: true,
},
req: {
request: true
request: true,
},
res: {
response: true,
result: true
result: true,
},
ret: {
returnValue: true
returnValue: true,
},
retval: {
returnValue: true
returnValue: true,
},
sep: {
separator: true
separator: true,
},
src: {
source: true
source: true,
},
stdDev: {
standardDeviation: true
standardDeviation: true,
},
str: {
string: true
string: true,
},
tbl: {
table: true
table: true,
},
temp: {
temporary: true
temporary: true,
},
tit: {
title: true
title: true,
},
tmp: {
temporary: true
temporary: true,
},
val: {
value: true
value: true,
},
var: {
variable: true
variable: true,
},
vars: {
variables: true
variables: true,
},
ver: {
version: true
}
version: true,
},
};

@@ -231,2 +248,4 @@

getInitialProps: true,
getServerSideProps: true,
getStaticProps: true,
// React PropTypes

@@ -237,3 +256,10 @@ // https://reactjs.org/docs/typechecking-with-proptypes.html

// https://jestjs.io/docs/en/configuration#setupfilesafterenv-array
setupFilesAfterEnv: true
setupFilesAfterEnv: true,
};
module.exports.defaultIgnore = [
// Internationalization and localization
// https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1188
'i18n',
'l10n',
];

@@ -51,3 +51,3 @@ /* eslint sort-keys: ["error", "asc", {natural: true}] */

222: '\'',
224: 'Meta'
224: 'Meta',
};
'use strict';
const isSameReference = require('../utils/is-same-reference.js');
const {getParenthesizedRange} = require('../utils/parentheses.js');
const {isNumberLiteral} = require('../ast/index.js');
const isLengthMemberExpression = node =>
node.type === 'MemberExpression' &&
!node.computed &&
!node.optional &&
node.property.type === 'Identifier' &&
node.property.name === 'length';
node.type === 'MemberExpression'
&& !node.computed
&& !node.optional
&& node.property.type === 'Identifier'
&& node.property.name === 'length';
const isLiteralPositiveNumber = node =>
node.type === 'Literal' &&
typeof node.value === 'number' &&
node.value > 0;
isNumberLiteral(node)
&& node.value > 0;

@@ -39,3 +39,3 @@ function getNegativeIndexLengthNode(node, objectNode) {

start,
end + sourceCode.text.slice(end).match(/\S|$/).index
end + sourceCode.text.slice(end).match(/\S|$/).index,
]);

@@ -46,3 +46,3 @@ }

getNegativeIndexLengthNode,
removeLengthNode
removeLengthNode,
};
'use strict';
const {hasSideEffect, isParenthesized, findVariable} = require('eslint-utils');
const {matches, methodCallSelector} = require('../selectors/index.js');
const isFunctionSelfUsedInside = require('../utils/is-function-self-used-inside.js');
const {hasSideEffect, isParenthesized, findVariable} = require('@eslint-community/eslint-utils');
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;

@@ -41,7 +39,8 @@

const MESSAGE_ID_PREFIX = `prefer-${replacement}-over-${method}/`;
const ERROR = `${MESSAGE_ID_PREFIX}/error`;
const SUGGESTION = `${MESSAGE_ID_PREFIX}/suggestion`;
const ERROR = `${MESSAGE_ID_PREFIX}error`;
const SUGGESTION = `${MESSAGE_ID_PREFIX}suggestion`;
const ERROR_MESSAGES = {
findIndex: 'Use `.indexOf()` instead of `.findIndex()` when looking for the index of an item.',
some: `Use \`.${replacement}()\` instead of \`.${method}()\` when checking value existence.`
findLastIndex: 'Use `.lastIndexOf()` instead of `findLastIndex() when looking for the index of an item.`',
some: `Use \`.${replacement}()\` instead of \`.${method}()\` when checking value existence.`,
};

@@ -51,79 +50,81 @@

[ERROR]: ERROR_MESSAGES[method],
[SUGGESTION]: `Replace \`.${method}()\` with \`.${replacement}()\`.`
[SUGGESTION]: `Replace \`.${method}()\` with \`.${replacement}()\`.`,
};
const selector = [
methodCallSelector({
name: method,
length: 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;
'use strict';
const quoteString = require('./utils/quote-string.js');
const replaceTemplateElement = require('./utils/replace-template-element.js');
const escapeString = require('./utils/escape-string.js');
const escapeTemplateElementRaw = require('./utils/escape-template-element-raw.js');
const {replaceTemplateElement} = require('./fix/index.js');

@@ -9,3 +9,3 @@ const defaultMessage = 'Prefer `{{suggest}}` over `{{match}}`.';

const messages = {
[SUGGESTION_MESSAGE_ID]: 'Replace `{{match}}` with `{{suggest}}`.'
[SUGGESTION_MESSAGE_ID]: 'Replace `{{match}}` with `{{suggest}}`.',
};

@@ -16,7 +16,7 @@

'html',
'svg'
'svg',
]);
const ignoredMemberExpressionObject = new Set([
'styled'
'styled',
]);

@@ -38,4 +38,4 @@

if (
object.type === 'Identifier' &&
ignoredMemberExpressionObject.has(object.name)
object.type === 'Identifier'
&& ignoredMemberExpressionObject.has(object.name)
) {

@@ -54,3 +54,3 @@ return true;

options = {
suggest: options
suggest: options,
};

@@ -63,3 +63,3 @@ }

fix: true,
...options
...options,
};

@@ -69,6 +69,7 @@ });

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {patterns} = {
patterns: {},
...context.options[0]
...context.options[0],
};

@@ -78,64 +79,60 @@ const replacements = getReplacements(patterns);

if (replacements.length === 0) {
return {};
return;
}
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 messageData = {
const {fix: autoFix, message = defaultMessage, match, suggest, regex} = replacement;
const problem = {
node,
message,
data: {
match,
suggest
};
const problem = {
suggest,
},
};
const fixed = string.replace(regex, suggest);
const fix = type === 'Literal'
? fixer => fixer.replaceText(
node,
message,
data: messageData
};
escapeString(fixed, raw[0]),
)
: fixer => replaceTemplateElement(
fixer,
node,
escapeTemplateElementRaw(fixed),
);
const fixed = string.replace(regex, suggest);
const fix = type === 'Literal' ?
fixer => fixer.replaceText(
node,
quoteString(fixed, raw[0])
) :
fixer => replaceTemplateElement(
fixer,
node,
escapeTemplateElementRaw(fixed)
);
if (autoFix) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: SUGGESTION_MESSAGE_ID,
fix,
},
];
}
if (autoFix) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: SUGGESTION_MESSAGE_ID,
data: messageData,
fix
}
];
}
return problem;
}
};
return problem;
});
};

@@ -146,2 +143,3 @@

type: 'object',
additionalProperties: false,
properties: {

@@ -153,3 +151,3 @@ patterns: {

{
type: 'string'
type: 'string',
},

@@ -159,26 +157,26 @@ {

required: [
'suggest'
'suggest',
],
properties: {
suggest: {
type: 'string'
type: 'string',
},
fix: {
type: 'boolean'
type: 'boolean',
// Default: true
},
message: {
type: 'string'
type: 'string',
// Default: ''
}
},
},
additionalProperties: false
}
]
}}
additionalProperties: false,
},
],
}},
},
additionalProperties: false
}
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -189,9 +187,9 @@ create,

docs: {
description: 'Enforce better string content.'
description: 'Enforce better string content.',
},
fixable: 'code',
hasSuggestions: true,
schema,
messages,
hasSuggestions: true
}
},
};
'use strict';
const {matches} = require('./selectors/index.js');
const {switchCallExpressionToNewExpression} = require('./fix/index.js');
const messageId = 'throw-new-error';
const messages = {
[messageId]: 'Use `new` when throwing an error.'
[messageId]: 'Use `new` when throwing an error.',
};

@@ -11,32 +11,34 @@

const selector = [
'ThrowStatement',
' > ',
'CallExpression.argument',
matches([
// `throw FooError()`
[
'[callee.type="Identifier"]',
`[callee.name=/${customError.source}/]`
].join(''),
// `throw lib.FooError()`
[
'[callee.type="MemberExpression"]',
'[callee.computed=false]',
'[callee.property.type="Identifier"]',
`[callee.property.name=/${customError.source}/]`
].join('')
])
].join('');
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
CallExpression(node) {
if (!(
node.parent.type === 'ThrowStatement'
&& node.parent.argument === node
)) {
return;
}
const create = () => ({
[selector]: node => {
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 => fixer.insertTextBefore(node, 'new ')
fix: fixer => switchCallExpressionToNewExpression(node, context.sourceCode, fixer),
};
}
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

@@ -47,7 +49,7 @@ create,

docs: {
description: 'Require `new` when throwing an error.'
description: 'Require `new` when throwing an error.',
},
fixable: 'code',
messages
}
messages,
},
};

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

function assertToken(token, {test, expected, ruleId}) {
if (test && test(token)) {
if (test?.(token)) {
return;

@@ -14,7 +14,7 @@ }

if (
!test &&
expected.some(
!test
&& expected.some(
expectedToken =>
Object.entries(expectedToken)
.every(([key, value]) => token[key] === value)
.every(([key, value]) => token[key] === value),
)

@@ -21,0 +21,0 @@ ) {

'use strict';
const reservedWords = require('reserved-words');
const {
isIdentifierName,
isStrictReservedWord,
isKeyword,
} = require('@babel/helper-validator-identifier');
const resolveVariableName = require('./resolve-variable-name.js');
const getReferences = require('./get-references.js');
const indexifyName = (name, index) => name + '_'.repeat(index);
// https://github.com/microsoft/TypeScript/issues/2536#issuecomment-87194347
const typescriptReservedWords = new Set([
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'import',
'in',
'instanceof',
'new',
'null',
'return',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'as',
'implements',
'interface',
'let',
'package',
'private',
'protected',
'public',
'static',
'yield',
'any',
'boolean',
'constructor',
'declare',
'get',
'module',
'require',
'number',
'set',
'string',
'symbol',
'type',
'from',
'of',
]);
const scopeHasArgumentsSpecial = scope => {
while (scope) {
/* istanbul ignore next: `someScopeHasVariableName` seems already handle this */
if (scope.taints.get('arguments')) {
return true;
}
// Copied from https://github.com/babel/babel/blob/fce35af69101c6b316557e28abf60bdbf77d6a36/packages/babel-types/src/validators/isValidIdentifier.ts#L7
// Use this function instead of `require('@babel/types').isIdentifier`, since `@babel/helper-validator-identifier` package is much smaller
const isValidIdentifier = name =>
typeof name === 'string'
&& !isKeyword(name)
&& !isStrictReservedWord(name, true)
&& isIdentifierName(name)
&& name !== 'arguments'
&& !typescriptReservedWords.has(name);
scope = scope.upper;
}
return false;
};
const someScopeHasVariableName = (name, scopes) => scopes.some(scope => resolveVariableName(name, scope));
const someScopeIsStrict = scopes => scopes.some(scope => scope.isStrict);
const nameCollidesWithArgumentsSpecial = (name, scopes, isStrict) => {
if (name !== 'arguments') {
return false;
}
return isStrict || scopes.some(scope => scopeHasArgumentsSpecial(scope));
};
/*

@@ -49,18 +101,8 @@ Unresolved reference is probably from the global scope. We should avoid using that name.

*/
const isUnresolvedName = (name, scopes) => scopes.some(scope =>
scope.references.some(reference => reference.identifier && reference.identifier.name === name && !reference.resolved) ||
isUnresolvedName(name, scope.childScopes)
);
const isUnresolvedName = (name, scope) =>
getReferences(scope).some(({identifier, resolved}) => identifier?.name === name && !resolved);
const isSafeName = (name, scopes, ecmaVersion, isStrict) => {
ecmaVersion = Math.min(6, ecmaVersion); // 6 is the latest version understood by `reservedWords`
const isSafeName = (name, scopes) =>
!scopes.some(scope => resolveVariableName(name, scope) || isUnresolvedName(name, scope));
return (
!someScopeHasVariableName(name, scopes) &&
!reservedWords.check(name, ecmaVersion, isStrict) &&
!nameCollidesWithArgumentsSpecial(name, scopes, isStrict) &&
!isUnresolvedName(name, scopes)
);
};
const alwaysTrue = () => true;

@@ -72,5 +114,5 @@

@callback isSafe
@param {string} indexifiedName - The generated candidate name.
@param {string} name - The generated candidate name.
@param {Scope[]} scopes - The same list of scopes you pass to `avoidCapture`.
@returns {boolean} - `true` if the `indexifiedName` is ok.
@returns {boolean} - `true` if the `name` is ok.
*/

@@ -89,20 +131,19 @@

@param {Scope[]} scopes - The list of scopes the new variable will be referenced in.
@param {number} ecmaVersion - The language version, get it from `context.parserOptions.ecmaVersion`.
@param {isSafe} [isSafe] - Rule-specific name check function.
@returns {string} - Either `name` as is, or a string like `${name}_` suffixed with underscores to make the name unique.
*/
module.exports = (name, scopes, ecmaVersion, isSafe = alwaysTrue) => {
const isStrict = someScopeIsStrict(scopes);
module.exports = (name, scopes, isSafe = alwaysTrue) => {
if (!isValidIdentifier(name)) {
name += '_';
let index = 0;
let indexifiedName = indexifyName(name, index);
while (
!isSafeName(indexifiedName, scopes, ecmaVersion, isStrict) ||
!isSafe(indexifiedName, scopes)
) {
index++;
indexifiedName = indexifyName(name, index);
if (!isValidIdentifier(name)) {
return;
}
}
return indexifiedName;
while (!isSafeName(name, scopes) || !isSafe(name, scopes)) {
name += '_';
}
return name;
};

@@ -5,31 +5,21 @@ 'use strict';

const isLogicNot = node =>
node &&
node.type === 'UnaryExpression' &&
node.operator === '!';
const isLogicNotArgument = node =>
isLogicNot(node.parent) &&
node.parent.argument === node;
const isBooleanCallArgument = node =>
isBooleanCall(node.parent) &&
node.parent.arguments[0] === node;
const isLogicNot = node => node?.type === 'UnaryExpression' && node.operator === '!';
const isLogicNotArgument = node => isLogicNot(node.parent) && node.parent.argument === node;
const isBooleanCallArgument = node => isBooleanCall(node.parent) && node.parent.arguments[0] === node;
const isBooleanCall = node =>
node &&
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'Boolean' &&
node.arguments.length === 1;
node?.type === 'CallExpression'
&& node.callee.type === 'Identifier'
&& node.callee.name === 'Boolean'
&& node.arguments.length === 1;
const isVueBooleanAttributeValue = node =>
node &&
node.type === 'VExpressionContainer' &&
node.parent.type === 'VAttribute' &&
node.parent.directive &&
node.parent.value === node &&
node.parent.key.type === 'VDirectiveKey' &&
node.parent.key.name.type === 'VIdentifier' &&
(
node.parent.key.name.rawName === 'if' ||
node.parent.key.name.rawName === 'else-if' ||
node.parent.key.name.rawName === 'show'
node?.type === 'VExpressionContainer'
&& node.parent.type === 'VAttribute'
&& node.parent.directive
&& node.parent.value === node
&& node.parent.key.type === 'VDirectiveKey'
&& node.parent.key.name.type === 'VIdentifier'
&& (
node.parent.key.name.rawName === 'if'
|| node.parent.key.name.rawName === 'else-if'
|| node.parent.key.name.rawName === 'show'
);

@@ -45,6 +35,6 @@

if (
isLogicNot(node) ||
isLogicNotArgument(node) ||
isBooleanCall(node) ||
isBooleanCallArgument(node)
isLogicNot(node)
|| isLogicNotArgument(node)
|| isBooleanCall(node)
|| isBooleanCallArgument(node)
) {

@@ -61,9 +51,9 @@ return true;

(
parent.type === 'IfStatement' ||
parent.type === 'ConditionalExpression' ||
parent.type === 'WhileStatement' ||
parent.type === 'DoWhileStatement' ||
parent.type === 'ForStatement'
) &&
parent.test === node
parent.type === 'IfStatement'
|| parent.type === 'ConditionalExpression'
|| parent.type === 'WhileStatement'
|| parent.type === 'DoWhileStatement'
|| parent.type === 'ForStatement'
)
&& parent.test === node
) {

@@ -70,0 +60,0 @@ return true;

'use strict';
const typedArray = require('../shared/typed-array.js');

@@ -7,13 +8,6 @@ const enforceNew = [

'ArrayBuffer',
'BigInt64Array',
'BigUint64Array',
'DataView',
'Date',
'Error',
'Float32Array',
'Float64Array',
'Function',
'Int8Array',
'Int16Array',
'Int32Array',
'Map',

@@ -25,6 +19,7 @@ 'WeakMap',

'RegExp',
'Uint8Array',
'Uint16Array',
'Uint32Array',
'Uint8ClampedArray'
'SharedArrayBuffer',
'Proxy',
'WeakRef',
'FinalizationRegistry',
...typedArray,
];

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

'String',
'Symbol'
'Symbol',
];

@@ -43,3 +38,3 @@

enforceNew,
disallowNew
disallowNew,
};
'use strict';
const getTotal = combinations => {
let total = 1;
for (const {length} of combinations) {
total *= length;
}
return total;
};
module.exports = (combinations, length = Number.POSITIVE_INFINITY) => {
const total = getTotal(combinations);
const total = combinations.reduce((total, {length}) => total * length, 1);

@@ -31,4 +22,4 @@ const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => {

total,
samples
samples,
};
};
'use strict';
const packageJson = require('../../package.json');
const repoUrl = 'https://github.com/sindresorhus/eslint-plugin-unicorn';
/** @returns {{ [ruleName: string]: import('eslint').Rule.RuleModule }} */
function createDeprecatedRules(data) {

@@ -13,10 +15,9 @@ return Object.fromEntries(

docs: {
url: `${repoUrl}/blob/v${packageJson.version}/docs/deprecated-rules.md#${ruleId}`
url: `${repoUrl}/blob/v${packageJson.version}/docs/deprecated-rules.md#${ruleId}`,
},
deprecated: true,
replacedBy: Array.isArray(replacedBy) ? replacedBy : [replacedBy],
schema: []
}
}
])
},
},
]),
);

@@ -23,0 +24,0 @@ }

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

/(?<=(?:^|[^\\])(?:\\\\)*)(?<symbol>(?:`|\$(?={)))/g,
'\\$<symbol>'
'\\$<symbol>',
);
'use strict';
const {isOpeningParenToken} = require('eslint-utils');
const {isOpeningParenToken} = require('@eslint-community/eslint-utils');

@@ -17,3 +17,3 @@ /**

openingParenthesisToken.range[1],
closingParenthesisToken.range[0]
closingParenthesisToken.range[0],
);

@@ -20,0 +20,0 @@ };

'use strict';
const path = require('path');
const path = require('node:path');
const packageJson = require('../../package.json');

@@ -4,0 +4,0 @@

'use strict';
const {uniq} = require('lodash');
const getReferences = scope => uniq([
...scope.references,
...scope.childScopes.flatMap(scope => getReferences(scope))
]);
const getScopes = require('./get-scopes.js');
const getReferences = scope => [...new Set(
getScopes(scope).flatMap(({references}) => references),
)];
module.exports = getReferences;
'use strict';
const {uniq} = require('lodash');
// Get identifiers of given variable
module.exports = ({identifiers, references}) => uniq([
module.exports = ({identifiers, references}) => [...new Set([
...identifiers,
...references.map(({identifier}) => identifier)
]);
...references.map(({identifier}) => identifier),
])];
'use strict';
module.exports = (node1, node2) =>
node1 &&
node2 &&
node1.range[0] === node2.range[0] &&
node1.range[1] === node2.range[1];
node1
&& node2
&& node1.range[0] === node2.range[0]
&& node1.range[1] === node2.range[1];
'use strict';
const {findVariable} = require('eslint-utils');
const {findVariable} = require('@eslint-community/eslint-utils');

@@ -17,3 +17,3 @@ const getReferences = (scope, nodeOrName) => {

function isFunctionSelfUsedInside(functionNode, functionScope) {
/* istanbul ignore next */
/* c8 ignore next 3 */
if (functionScope.block !== functionNode) {

@@ -20,0 +20,0 @@ throw new Error('"functionScope" should be the scope of "functionNode".');

'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 === 'UpdateExpression' && node.parent.argument === node) ||
(
node.parent.type === 'UnaryExpression' &&
node.parent.operator === 'delete' &&
node.parent.argument === 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'
&& node.parent.operator === 'delete'
&& node.parent.argument === node
);
module.exports = isLeftHandSide;

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

const isLogicalExpression = node =>
node &&
node.type === 'LogicalExpression' &&
(node.operator === '&&' || node.operator === '||');
node?.type === 'LogicalExpression'
&& (node.operator === '&&' || node.operator === '||');
module.exports = isLogicalExpression;
'use strict';
const isMethodNamed = (node, name) =>
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === name;
node.type === 'CallExpression'
&& node.callee.type === 'MemberExpression'
&& node.callee.property.type === 'Identifier'
&& node.callee.property.name === name;
module.exports = isMethodNamed;
'use strict';
const {isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
const {isOpeningParenToken, isClosingParenToken} = require('@eslint-community/eslint-utils');

@@ -21,7 +21,7 @@ /**

// The expression should end with its own parens, for example, `new new Foo()` is not a new expression with parens.
return isOpeningParenToken(penultimateToken) &&
isClosingParenToken(lastToken) &&
node.callee.range[1] < node.range[1];
return isOpeningParenToken(penultimateToken)
&& isClosingParenToken(lastToken)
&& node.callee.range[1] < node.range[1];
}
module.exports = isNewExpressionWithParentheses;

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

return (
(node.type === 'Identifier' && node.name === name) ||
(name === 'this' && node.type === 'ThisExpression')
(node.type === 'Identifier' && node.name === name)
|| (name === 'this' && node.type === 'ThisExpression')
);

@@ -27,7 +27,7 @@ }

if (
node.type !== 'MemberExpression' ||
node.optional ||
node.computed ||
node.property.type !== 'Identifier' ||
node.property.name !== name
node.type !== 'MemberExpression'
|| node.optional
|| node.computed
|| node.property.type !== 'Identifier'
|| node.property.name !== name
) {

@@ -54,3 +54,3 @@ return false;

isNodeMatchesNameOrPath,
isNodeMatches
isNodeMatches,
};

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

return (
callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
callee.object.name === object &&
callee.property.type === 'Identifier' &&
callee.property.name === method
callee.type === 'MemberExpression'
&& callee.object.type === 'Identifier'
&& callee.object.name === object
&& callee.property.type === 'Identifier'
&& callee.property.name === method
);
};
'use strict';
const {getStaticValue} = require('eslint-utils');
const {getStaticValue} = require('@eslint-community/eslint-utils');
// Copied from https://github.com/eslint/eslint/blob/c3e9accce2f61b04ab699fd37c90703305281aa3/lib/rules/utils/ast-utils.js#L379
// Copied from https://github.com/eslint/eslint/blob/94ba68d76a6940f68ff82eea7332c6505f93df76/lib/rules/utils/ast-utils.js#L392

@@ -39,18 +39,21 @@ /**

switch (node && node.type) {
case 'MemberExpression':
switch (node?.type) {
case 'MemberExpression': {
property = node.property;
break;
}
/* istanbul ignore next: Hard to test */
case 'ChainExpression':
/* c8 ignore next 2 */
case 'ChainExpression': {
return getStaticPropertyName(node.expression);
}
/* istanbul ignore next: Only reachable when use this to get class/object member key */
// Only reachable when use this to get class/object member key
/* c8 ignore next */
case 'Property':
case 'MethodDefinition':
/* istanbul ignore next */
case 'MethodDefinition': {
/* c8 ignore next 2 */
property = node.key;
/* istanbul ignore next */
break;
}

@@ -84,6 +87,6 @@ // No default

return Boolean(
left.regex &&
right.regex &&
left.regex.pattern === right.regex.pattern &&
left.regex.flags === right.regex.flags
left.regex
&& right.regex
&& left.regex.pattern === right.regex.pattern
&& left.regex.flags === right.regex.flags,
);

@@ -126,13 +129,18 @@ }

case 'Super':
case 'ThisExpression':
case 'ThisExpression': {
return true;
}
case 'Identifier':
case 'PrivateIdentifier': {
return left.name === right.name;
}
case 'Literal':
case 'Literal': {
return equalLiteralValue(left, right);
}
case 'ChainExpression':
case 'ChainExpression': {
return isSameReference(left.expression, right.expression);
}

@@ -142,12 +150,25 @@ case 'MemberExpression': {

// X.y = x["y"]
// `x.y = x["y"]`
if (nameA !== undefined) {
return (
isSameReference(left.object, right.object)
&& nameA === getStaticPropertyName(right)
);
}
/*
`x[0] = x[0]`
`x[y] = x[y]`
`x.y = x.y`
*/
return (
typeof nameA !== 'undefined' &&
isSameReference(left.object, right.object) &&
nameA === getStaticPropertyName(right)
left.computed === right.computed
&& isSameReference(left.object, right.object)
&& isSameReference(left.property, right.property)
);
}
default:
default: {
return false;
}
}

@@ -154,0 +175,0 @@ }

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

return (
reference &&
reference.resolved &&
reference.resolved.defs.length > 0
reference?.resolved
&& reference.resolved.defs.length > 0
);

@@ -33,0 +32,0 @@ }

'use strict';
const isShorthandPropertyValue = require('./is-shorthand-property-value.js');
const isShorthandPropertyAssignmentPatternLeft = identifier =>
identifier.parent.type === 'AssignmentPattern' &&
identifier.parent.left === identifier &&
isShorthandPropertyValue(identifier.parent);
identifier.parent.type === 'AssignmentPattern'
&& identifier.parent.left === identifier
&& isShorthandPropertyValue(identifier.parent);
module.exports = isShorthandPropertyAssignmentPatternLeft;
'use strict';
const isShorthandPropertyValue = identifier =>
identifier.parent.type === 'Property' &&
identifier.parent.shorthand &&
identifier === identifier.parent.value;
identifier.parent.type === 'Property'
&& identifier.parent.shorthand
&& identifier === identifier.parent.value;
module.exports = isShorthandPropertyValue;
'use strict';
module.exports = ({parent}) => !parent || parent.type === 'ExpressionStatement';
const {isExpressionStatement} = require('../ast/index.js');
module.exports = node => isExpressionStatement(node.parent);

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

'Numeric',
'RegularExpression'
'RegularExpression',
]);

@@ -22,3 +22,3 @@

',',
'.'
'.',
]);

@@ -37,4 +37,4 @@

if (
code === '' ||
(code && !charactersMightNeedsSemicolon.has(code.charAt(0)))
code === ''
|| (code && !charactersMightNeedsSemicolon.has(code.charAt(0)))
) {

@@ -41,0 +41,0 @@ return false;

'use strict';
const {isNumberLiteral, isBigIntLiteral} = require('../ast/index.js');
// Determine whether this node is a decimal integer literal.

@@ -7,8 +9,6 @@ // Copied from https://github.com/eslint/eslint/blob/cc4871369645c3409dc56ded7a555af8a9f63d51/lib/rules/utils/ast-utils.js#L1237

const isDecimalInteger = text => DECIMAL_INTEGER_PATTERN.test(text);
const isDecimalIntegerNode = node => isNumber(node) && isDecimalInteger(node.raw);
const isDecimalIntegerNode = node => isNumberLiteral(node) && isDecimalInteger(node.raw);
const isNumber = node => typeof node.value === 'number';
const isBigInt = node => Boolean(node.bigint);
const isNumeric = node => isNumber(node) || isBigInt(node);
const isLegacyOctal = node => isNumber(node) && /^0\d+$/.test(node.raw);
const isNumeric = node => isNumberLiteral(node) || isBigIntLiteral(node);
const isLegacyOctal = node => isNumberLiteral(node) && /^0\d+$/.test(node.raw);

@@ -32,3 +32,3 @@ function getPrefix(text) {

sign = '',
power = ''
power = '',
} = text.match(/^(?<number>[\d._]*?)(?:(?<mark>[Ee])(?<sign>[+-])?(?<power>[\d_]+))?$/).groups;

@@ -50,4 +50,2 @@

isDecimalInteger,
isNumber,
isBigInt,
isNumeric,

@@ -57,3 +55,3 @@ isLegacyOctal,

parseNumber,
parseFloatNumber
parseFloatNumber,
};
'use strict';
const {isParenthesized, isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
const {isParenthesized, isOpeningParenToken, isClosingParenToken} = require('@eslint-community/eslint-utils');

@@ -12,7 +12,2 @@ /*

function getParenthesizedTimes(node, sourceCode) {
// Workaround for https://github.com/mysticatea/eslint-utils/pull/25
if (!node.parent) {
return 0;
}
let times = 0;

@@ -43,3 +38,3 @@

...sourceCode.getTokensBefore(node, {count, filter: isOpeningParenToken}),
...sourceCode.getTokensAfter(node, {count, filter: isClosingParenToken})
...sourceCode.getTokensAfter(node, {count, filter: isClosingParenToken}),
];

@@ -79,3 +74,3 @@ }

getParenthesizedRange,
getParenthesizedText
getParenthesizedText,
};
'use strict';
const path = require('path');
const fs = require('fs');
const path = require('node:path');
const fs = require('node:fs');
const getDocumentationUrl = require('./get-documentation-url.js');
const isIterable = object => typeof object[Symbol.iterator] === 'function';
const isIterable = object => typeof object?.[Symbol.iterator] === 'function';
function reportListenerProblems(listener, context) {
// Listener arguments can be `codePath, node` or `node`
return function (...listenerArguments) {
let problems = listener(...listenerArguments);
class FixAbortError extends Error {}
const fixOptions = {
abort() {
throw new FixAbortError('Fix aborted.');
},
};
if (!problems) {
return;
function wrapFixFunction(fix) {
return fixer => {
const result = fix(fixer, fixOptions);
if (isIterable(result)) {
try {
return [...result];
} catch (error) {
if (error instanceof FixAbortError) {
return;
}
/* c8 ignore next */
throw error;
}
}
if (!isIterable(problems)) {
problems = [problems];
return result;
};
}
function reportListenerProblems(problems, context) {
if (!problems) {
return;
}
if (!isIterable(problems)) {
problems = [problems];
}
for (const problem of problems) {
if (problem.fix) {
problem.fix = wrapFixFunction(problem.fix);
}
// TODO: Allow `fix` function to abort
for (const problem of problems) {
if (problem) {
context.report(problem);
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,
};
}
}
};
context.report(problem);
}
}

@@ -37,7 +74,51 @@

const wrapped = context => Object.fromEntries(
Object.entries(create(context))
.map(([selector, listener]) => [selector, reportListenerProblems(listener, context)])
);
const wrapped = context => {
const listeners = {};
const addListener = (selector, listener) => {
listeners[selector] ??= [];
listeners[selector].push(listener);
};
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);
}
return Object.fromEntries(
Object.entries(listeners)
.map(([selector, listeners]) => [
selector,
// Listener arguments can be `codePath, node` or `node`
(...listenerArguments) => {
for (const listener of listeners) {
reportListenerProblems(listener(...listenerArguments), context);
}
},
]),
);
};
wrappedFunctions.add(wrapped);

@@ -50,6 +131,6 @@

const {
visitScriptBlock
visitScriptBlock,
} = {
visitScriptBlock: true,
...options
...options,
};

@@ -61,11 +142,9 @@

const listeners = create(context);
const {parserServices} = context.sourceCode;
// `vue-eslint-parser`
if (
context.parserServices &&
context.parserServices.defineTemplateBodyVisitor
) {
return visitScriptBlock ?
context.parserServices.defineTemplateBodyVisitor(listeners, listeners) :
context.parserServices.defineTemplateBodyVisitor(listeners);
if (parserServices?.defineTemplateBodyVisitor) {
return visitScriptBlock
? parserServices.defineTemplateBodyVisitor(listeners, listeners)
: parserServices.defineTemplateBodyVisitor(listeners);
}

@@ -80,2 +159,3 @@

/** @returns {import('eslint').Rule.RuleModule} */
function loadRule(ruleId) {

@@ -92,6 +172,6 @@ const rule = require(`../${ruleId}`);

...rule.meta.docs,
url: getDocumentationUrl(ruleId)
}
url: getDocumentationUrl(ruleId),
},
},
create: reportProblems(rule.create)
create: reportProblems(rule.create),
};

@@ -107,3 +187,3 @@ }

return [ruleId, loadRule(ruleId)];
})
}),
);

@@ -115,3 +195,3 @@ }

loadRules,
checkVueTemplate
checkVueTemplate,
};

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

function shouldAddParenthesesToConditionalExpressionChild(node) {
return node.type === 'AwaitExpression' ||
return node.type === 'AwaitExpression'
// Lower precedence, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
node.type === 'AssignmentExpression' ||
node.type === 'YieldExpression' ||
node.type === 'SequenceExpression';
|| node.type === 'AssignmentExpression'
|| node.type === 'YieldExpression'
|| node.type === 'SequenceExpression';
}
module.exports = shouldAddParenthesesToConditionalExpressionChild;

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

switch (node.type) {
case 'ObjectExpression':
case 'ObjectExpression': {
return true;
case 'AssignmentExpression':
}
case 'AssignmentExpression': {
return node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern';
default:
}
default: {
return false;
}
}

@@ -20,0 +25,0 @@ }

@@ -10,11 +10,19 @@ 'use strict';

function shouldAddParenthesesToLogicalExpressionChild(node, {operator, property}) {
/* istanbul ignore next: When operator or property is different, need check `LogicalExpression` operator precedence, not implemented */
if (operator !== '??' || property !== 'left') {
throw new Error('Not supported.');
// We are not using this, but we can improve this function with it
/* c8 ignore next 3 */
if (!property) {
throw new Error('`property` is required.');
}
if (
node.type === 'LogicalExpression'
&& node.operator === operator
) {
return false;
}
// Not really needed, but more readable
if (
node.type === 'AwaitExpression' ||
node.type === 'BinaryExpression'
node.type === 'AwaitExpression'
|| node.type === 'BinaryExpression'
) {

@@ -27,7 +35,8 @@ return true;

if (
node.type === 'ConditionalExpression' ||
node.type === 'AssignmentExpression' ||
node.type === 'AssignmentExpression' ||
node.type === 'YieldExpression' ||
node.type === 'SequenceExpression'
node.type === 'LogicalExpression'
|| node.type === 'ConditionalExpression'
|| node.type === 'AssignmentExpression'
|| node.type === 'ArrowFunctionExpression'
|| node.type === 'YieldExpression'
|| node.type === 'SequenceExpression'
) {

@@ -34,0 +43,0 @@ return true;

@@ -24,8 +24,12 @@ 'use strict';

case 'ArrayExpression':
case 'FunctionExpression':
case 'FunctionExpression': {
return false;
case 'NewExpression':
}
case 'NewExpression': {
return !isNewExpressionWithParentheses(node, sourceCode);
}
case 'Literal': {
/* istanbul ignore next */
/* c8 ignore next */
if (isDecimalIntegerNode(node)) {

@@ -38,4 +42,5 @@ return true;

default:
default: {
return true;
}
}

@@ -42,0 +47,0 @@ }

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

'TemplateLiteral',
'ThisExpression'
'ThisExpression',
]);

@@ -13,0 +13,0 @@

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

start: sourceCode.getLocFromIndex(start + startOffset),
end: sourceCode.getLocFromIndex(end + endOffset)
end: sourceCode.getLocFromIndex(end + endOffset),
};

@@ -20,0 +20,0 @@ }

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc