├── .npmrc ├── .gitattributes ├── .markdownlintignore ├── test ├── snapshots │ ├── no-null.mjs.snap │ ├── prefer-at.mjs.snap │ ├── better-regex.mjs.snap │ ├── import-style.mjs.snap │ ├── no-for-loop.mjs.snap │ ├── no-lonely-if.mjs.snap │ ├── no-new-array.mjs.snap │ ├── no-thenable.mjs.snap │ ├── error-message.mjs.snap │ ├── filename-case.mjs.snap │ ├── no-empty-file.mjs.snap │ ├── no-hex-escape.mjs.snap │ ├── no-new-buffer.mjs.snap │ ├── no-process-exit.mjs.snap │ ├── prefer-date-now.mjs.snap │ ├── prefer-includes.mjs.snap │ ├── prefer-module.mjs.snap │ ├── prefer-set-has.mjs.snap │ ├── prefer-set-size.mjs.snap │ ├── prefer-spread.mjs.snap │ ├── prefer-switch.mjs.snap │ ├── template-indent.mjs.snap │ ├── throw-new-error.mjs.snap │ ├── new-for-builtins.mjs.snap │ ├── no-array-for-each.mjs.snap │ ├── no-console-spaces.mjs.snap │ ├── no-nested-ternary.mjs.snap │ ├── no-useless-spread.mjs.snap │ ├── no-zero-fractions.mjs.snap │ ├── prefer-array-flat.mjs.snap │ ├── prefer-array-some.mjs.snap │ ├── prefer-code-point.mjs.snap │ ├── prefer-math-trunc.mjs.snap │ ├── prefer-string-raw.mjs.snap │ ├── prefer-type-error.mjs.snap │ ├── empty-brace-spaces.mjs.snap │ ├── no-array-push-push.mjs.snap │ ├── no-document-cookie.mjs.snap │ ├── no-instanceof-array.mjs.snap │ ├── no-negated-condition.mjs.snap │ ├── no-static-only-class.mjs.snap │ ├── no-this-assignment.mjs.snap │ ├── no-typeof-undefined.mjs.snap │ ├── no-unnecessary-await.mjs.snap │ ├── no-unreadable-iife.mjs.snap │ ├── no-unused-properties.mjs.snap │ ├── no-useless-undefined.mjs.snap │ ├── number-literal-case.mjs.snap │ ├── prefer-event-target.mjs.snap │ ├── prefer-export-from.mjs.snap │ ├── prefer-global-this.mjs.snap │ ├── prefer-math-min-max.mjs.snap │ ├── prefer-node-protocol.mjs.snap │ ├── prefer-regexp-test.mjs.snap │ ├── prefer-string-slice.mjs.snap │ ├── relative-url-style.mjs.snap │ ├── switch-case-braces.mjs.snap │ ├── explicit-length-check.mjs.snap │ ├── no-length-as-slice-end.mjs.snap │ ├── no-useless-switch-case.mjs.snap │ ├── prefer-array-flat-map.mjs.snap │ ├── prefer-array-index-of.mjs.snap │ ├── prefer-dom-node-remove.mjs.snap │ ├── prefer-negative-index.mjs.snap │ ├── prefer-query-selector.mjs.snap │ ├── prefer-top-level-await.mjs.snap │ ├── no-abusive-eslint-disable.mjs.snap │ ├── no-invalid-fetch-options.mjs.snap │ ├── no-magic-array-flat-depth.mjs.snap │ ├── no-useless-length-check.mjs.snap │ ├── numeric-separators-style.mjs.snap │ ├── prefer-add-event-listener.mjs.snap │ ├── prefer-dom-node-dataset.mjs.snap │ ├── prefer-json-parse-buffer.mjs.snap │ ├── prefer-keyboard-event-key.mjs.snap │ ├── prefer-modern-math-apis.mjs.snap │ ├── prefer-number-properties.mjs.snap │ ├── prefer-prototype-methods.mjs.snap │ ├── prefer-string-replace-all.mjs.snap │ ├── prefer-structured-clone.mjs.snap │ ├── consistent-function-scoping.mjs.snap │ ├── no-anonymous-default-export.mjs.snap │ ├── no-array-callback-reference.mjs.snap │ ├── no-await-expression-member.mjs.snap │ ├── no-await-in-promise-methods.mjs.snap │ ├── prefer-blob-reading-methods.mjs.snap │ ├── prefer-object-from-entries.mjs.snap │ ├── consistent-empty-array-spread.mjs.snap │ ├── no-array-method-this-argument.mjs.snap │ ├── no-negation-in-equality-check.mjs.snap │ ├── no-object-as-default-parameter.mjs.snap │ ├── no-useless-fallback-in-spread.mjs.snap │ ├── prefer-dom-node-text-content.mjs.snap │ ├── prefer-optional-catch-binding.mjs.snap │ ├── prefer-string-starts-ends-with.mjs.snap │ ├── prefer-string-trim-start-end.mjs.snap │ ├── require-array-join-separator.mjs.snap │ ├── text-encoding-identifier-case.mjs.snap │ ├── consistent-existence-index-check.mjs.snap │ ├── no-invalid-remove-event-listener.mjs.snap │ ├── prefer-native-coercion-functions.mjs.snap │ ├── no-unreadable-array-destructuring.mjs.snap │ ├── require-post-message-target-origin.mjs.snap │ ├── no-single-promise-in-promise-methods.mjs.snap │ ├── prefer-logical-operator-over-ternary.mjs.snap │ ├── require-number-to-fixed-digits-argument.mjs.snap │ ├── no-process-exit.mjs.md │ ├── better-regex.mjs.md │ ├── no-hex-escape.mjs.md │ ├── prefer-type-error.mjs.md │ ├── no-unused-properties.mjs.md │ ├── empty-brace-spaces.mjs.md │ ├── prefer-blob-reading-methods.mjs.md │ ├── template-indent.mjs.md │ ├── numeric-separators-style.mjs.md │ ├── no-this-assignment.mjs.md │ └── no-magic-array-flat-depth.mjs.md ├── integration │ ├── fixtures-local │ │ ├── conflicts-no-array-for-each-and-prevent-abbreviations.js │ │ └── error-name-conflicts.js │ └── readme.md ├── unit │ ├── snapshots │ │ ├── assert-token.mjs.snap │ │ └── assert-token.mjs.md │ └── get-documentation-url.mjs ├── utils │ ├── default-options.mjs │ ├── not-dom-node-types.mjs │ └── not-function-types.mjs ├── prefer-blob-reading-methods.mjs ├── no-magic-array-flat-depth.mjs ├── prefer-array-index-of.mjs ├── require-number-to-fixed-digits-argument.mjs ├── no-negation-in-equality-check.mjs ├── no-unreadable-iife.mjs ├── prefer-code-point.mjs ├── no-length-as-slice-end.mjs ├── require-post-message-target-origin.mjs ├── prefer-logical-operator-over-ternary.mjs ├── no-this-assignment.mjs ├── prefer-string-trim-start-end.mjs ├── no-document-cookie.mjs ├── prefer-dom-node-text-content.mjs ├── prefer-string-raw.mjs └── consistent-empty-array-spread.mjs ├── .github ├── pull_request_template.md ├── funding.yml ├── security.md ├── ISSUE_TEMPLATE │ └── bug_report.md ├── workflows │ ├── smoke-test.yml │ └── title-formatter.yml └── contributing.md ├── codecov.yml ├── .markdownlint.json ├── .gitignore ├── configs ├── flat-config-base.js └── legacy-config-base.js ├── rules ├── ast │ ├── function-types.js │ ├── is-undefined.js │ ├── is-directive.js │ ├── is-function.js │ ├── is-arrow-function-body.js │ ├── is-expression-statement.js │ ├── is-negative-one.js │ ├── is-static-require.js │ ├── is-empty-node.js │ ├── is-tagged-template-literal.js │ ├── literal.js │ └── index.js ├── utils │ ├── get-builtin-rule.js │ ├── is-on-same-line.js │ ├── is-value-not-usable.js │ ├── has-same-range.js │ ├── is-same-identifier.js │ ├── escape-template-element-raw.js │ ├── get-references.js │ ├── is-shorthand-property-value.js │ ├── get-variable-identifiers.js │ ├── is-method-named.js │ ├── is-shorthand-export-local.js │ ├── is-shorthand-import-local.js │ ├── get-indent-string.js │ ├── is-object-method.js │ ├── get-scopes.js │ ├── get-documentation-url.js │ ├── is-shorthand-property-assignment-pattern-left.js │ ├── get-ancestor.js │ ├── has-optional-chain-element.js │ ├── get-previous-node.js │ ├── singular.js │ ├── resolve-variable-name.js │ ├── builtins.js │ ├── is-node-value-not-dom-node.js │ ├── get-class-head-location.js │ ├── escape-string.js │ ├── should-add-parentheses-to-conditional-expression-child.js │ ├── should-add-parentheses-to-await-expression-argument.js │ ├── is-logical-expression.js │ ├── is-left-hand-side.js │ ├── create-deprecated-rules.js │ ├── should-add-parentheses-to-expression-statement-expression.js │ ├── to-location.js │ ├── get-switch-case-head-location.js │ ├── cartesian-product-samples.js │ ├── should-add-parentheses-to-call-expression-callee.js │ ├── get-call-expression-arguments-text.js │ ├── is-shadowed.js │ ├── is-new-expression-with-parentheses.js │ ├── assert-token.js │ ├── is-node-value-not-function.js │ ├── should-add-parentheses-to-new-expression-callee.js │ ├── is-function-self-used-inside.js │ ├── get-call-expression-tokens.js │ ├── is-node-matches.js │ └── should-add-parentheses-to-member-expression-object.js ├── fix │ ├── replace-argument.js │ ├── replace-template-element.js │ ├── remove-parentheses.js │ ├── replace-string-raw.js │ ├── rename-variable.js │ ├── remove-member-expression-property.js │ ├── remove-spaces-after.js │ ├── replace-string-literal.js │ ├── extend-fix-range.js │ ├── add-parenthesizes-to-return-or-throw-expression.js │ ├── remove-method-call.js │ ├── append-argument.js │ ├── switch-call-expression-to-new-expression.js │ ├── replace-node-or-token-and-spaces-before.js │ ├── fix-space-around-keywords.js │ ├── remove-argument.js │ ├── index.js │ └── replace-reference-identifier.js ├── shared │ ├── typed-array.js │ ├── event-keys.js │ └── negative-index.js ├── no-document-cookie.js ├── no-this-assignment.js ├── prefer-array-index-of.js ├── throw-new-error.js ├── no-unreadable-iife.js ├── prefer-blob-reading-methods.js ├── prefer-dom-node-append.js ├── prefer-string-trim-start-end.js ├── number-literal-case.js ├── no-abusive-eslint-disable.js ├── no-object-as-default-parameter.js ├── no-empty-file.js └── no-magic-array-flat-depth.js ├── scripts ├── internal-rules │ ├── package.json │ ├── prefer-negative-boolean-attribute.js │ └── index.js └── template │ ├── test.mjs.jst │ └── documentation.md.jst ├── .editorconfig ├── index.d.ts ├── .npmpackagejsonlintrc.json ├── docs └── rules │ ├── empty-brace-spaces.md │ ├── no-negation-in-equality-check.md │ ├── no-instanceof-array.md │ ├── prefer-optional-catch-binding.md │ ├── prefer-set-size.md │ ├── no-hex-escape.md │ ├── escape-case.md │ ├── no-unreadable-iife.md │ ├── no-unnecessary-await.md │ ├── throw-new-error.md │ ├── prefer-string-raw.md │ ├── no-length-as-slice-end.md │ ├── no-useless-fallback-in-spread.md │ ├── consistent-empty-array-spread.md │ ├── no-empty-file.md │ ├── prefer-dom-node-append.md │ ├── error-message.md │ ├── no-static-only-class.md │ ├── no-magic-array-flat-depth.md │ ├── prefer-event-target.md │ ├── no-await-in-promise-methods.md │ ├── prefer-dom-node-remove.md │ ├── no-process-exit.md │ ├── prefer-top-level-await.md │ ├── require-number-to-fixed-digits-argument.md │ ├── no-object-as-default-parameter.md │ ├── no-invalid-fetch-options.md │ ├── prefer-dom-node-text-content.md │ ├── prefer-date-now.md │ ├── require-array-join-separator.md │ ├── prefer-prototype-methods.md │ ├── prefer-query-selector.md │ ├── prefer-reflect-apply.md │ ├── no-document-cookie.md │ ├── text-encoding-identifier-case.md │ ├── no-zero-fractions.md │ ├── prefer-string-slice.md │ ├── relative-url-style.md │ ├── no-await-expression-member.md │ ├── no-console-spaces.md │ ├── no-this-assignment.md │ ├── prefer-code-point.md │ ├── no-useless-switch-case.md │ ├── prefer-blob-reading-methods.md │ ├── prefer-set-has.md │ ├── prefer-string-trim-start-end.md │ ├── no-anonymous-default-export.md │ ├── no-single-promise-in-promise-methods.md │ ├── better-regex.md │ ├── require-post-message-target-origin.md │ └── prefer-logical-operator-over-ternary.md ├── .eslint-doc-generatorrc.js ├── license └── eslint.dogfooding.config.mjs /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/snapshots 3 | test/integration/fixtures 4 | -------------------------------------------------------------------------------- /test/snapshots/no-null.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-null.mjs.snap -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/snapshots/prefer-at.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-at.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/better-regex.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/better-regex.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/import-style.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/import-style.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-for-loop.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-for-loop.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-lonely-if.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-lonely-if.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-new-array.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-new-array.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-thenable.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-thenable.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/error-message.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/error-message.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/filename-case.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/filename-case.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-empty-file.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-empty-file.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-hex-escape.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-hex-escape.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-new-buffer.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-new-buffer.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-process-exit.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-process-exit.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-date-now.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-date-now.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-includes.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-includes.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-module.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-module.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-set-has.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-set-has.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-set-size.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-set-size.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-spread.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-spread.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-switch.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-switch.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/template-indent.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/template-indent.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/throw-new-error.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/throw-new-error.mjs.snap -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 99% 6 | threshold: 1% 7 | patch: off 8 | -------------------------------------------------------------------------------- /test/integration/fixtures-local/conflicts-no-array-for-each-and-prevent-abbreviations.js: -------------------------------------------------------------------------------- 1 | const btn = bar; 2 | foo.forEach(btn => click(btn)) 3 | -------------------------------------------------------------------------------- /test/snapshots/new-for-builtins.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/new-for-builtins.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-array-for-each.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-array-for-each.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-console-spaces.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-console-spaces.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-nested-ternary.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-nested-ternary.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-useless-spread.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-useless-spread.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-zero-fractions.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-zero-fractions.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-array-flat.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-array-flat.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-array-some.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-array-some.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-code-point.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-code-point.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-math-trunc.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-math-trunc.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-string-raw.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-string-raw.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-type-error.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-type-error.mjs.snap -------------------------------------------------------------------------------- /test/unit/snapshots/assert-token.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/unit/snapshots/assert-token.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/empty-brace-spaces.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/empty-brace-spaces.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-array-push-push.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-array-push-push.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-document-cookie.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-document-cookie.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-instanceof-array.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-instanceof-array.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-negated-condition.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-negated-condition.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-static-only-class.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-static-only-class.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-this-assignment.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-this-assignment.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-typeof-undefined.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-typeof-undefined.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-unnecessary-await.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-unnecessary-await.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-unreadable-iife.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-unreadable-iife.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-unused-properties.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-unused-properties.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-useless-undefined.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-useless-undefined.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/number-literal-case.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/number-literal-case.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-event-target.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-event-target.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-export-from.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-export-from.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-global-this.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-global-this.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-math-min-max.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-math-min-max.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-node-protocol.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-node-protocol.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-regexp-test.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-regexp-test.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-string-slice.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-string-slice.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/relative-url-style.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/relative-url-style.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/switch-case-braces.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/switch-case-braces.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/explicit-length-check.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/explicit-length-check.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-length-as-slice-end.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-length-as-slice-end.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-useless-switch-case.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-useless-switch-case.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-array-flat-map.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-array-flat-map.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-array-index-of.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-array-index-of.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-dom-node-remove.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-dom-node-remove.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-negative-index.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-negative-index.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-query-selector.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-query-selector.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-top-level-await.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-top-level-await.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-abusive-eslint-disable.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-abusive-eslint-disable.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-invalid-fetch-options.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-invalid-fetch-options.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-magic-array-flat-depth.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-magic-array-flat-depth.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-useless-length-check.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-useless-length-check.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/numeric-separators-style.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/numeric-separators-style.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-add-event-listener.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-add-event-listener.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-dom-node-dataset.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-dom-node-dataset.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-json-parse-buffer.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-json-parse-buffer.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-keyboard-event-key.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-keyboard-event-key.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-modern-math-apis.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-modern-math-apis.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-number-properties.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-number-properties.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-prototype-methods.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-prototype-methods.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-string-replace-all.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-string-replace-all.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-structured-clone.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-structured-clone.mjs.snap -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: [sindresorhus, fisker] 2 | open_collective: sindresorhus 3 | buy_me_a_coffee: sindresorhus 4 | custom: https://sindresorhus.com/donate 5 | -------------------------------------------------------------------------------- /test/snapshots/consistent-function-scoping.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/consistent-function-scoping.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-anonymous-default-export.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-anonymous-default-export.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-array-callback-reference.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-array-callback-reference.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-await-expression-member.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-await-expression-member.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-await-in-promise-methods.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-await-in-promise-methods.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-blob-reading-methods.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-blob-reading-methods.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-object-from-entries.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-object-from-entries.mjs.snap -------------------------------------------------------------------------------- /test/utils/default-options.mjs: -------------------------------------------------------------------------------- 1 | const defaultOptions = { 2 | languageOptions: { 3 | sourceType: 'module', 4 | }, 5 | }; 6 | 7 | export default defaultOptions; 8 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "line-length": false, 3 | "no-duplicate-heading": false, 4 | "no-hard-tabs": false, 5 | "ul-style": { 6 | "style": "dash" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/snapshots/consistent-empty-array-spread.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/consistent-empty-array-spread.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-array-method-this-argument.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-array-method-this-argument.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-negation-in-equality-check.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-negation-in-equality-check.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-object-as-default-parameter.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-object-as-default-parameter.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-useless-fallback-in-spread.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-useless-fallback-in-spread.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-dom-node-text-content.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-dom-node-text-content.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-optional-catch-binding.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-optional-catch-binding.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-string-starts-ends-with.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-string-starts-ends-with.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-string-trim-start-end.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-string-trim-start-end.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/require-array-join-separator.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/require-array-join-separator.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/text-encoding-identifier-case.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/text-encoding-identifier-case.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/consistent-existence-index-check.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/consistent-existence-index-check.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/no-invalid-remove-event-listener.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-invalid-remove-event-listener.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-native-coercion-functions.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-native-coercion-functions.mjs.snap -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | coverage 4 | package-lock.json 5 | /test/integration/fixtures 6 | .cache-eslint-remote-tester 7 | eslint-remote-tester-results 8 | *.log 9 | -------------------------------------------------------------------------------- /test/snapshots/no-unreadable-array-destructuring.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-unreadable-array-destructuring.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/require-post-message-target-origin.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/require-post-message-target-origin.mjs.snap -------------------------------------------------------------------------------- /configs/flat-config-base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const globals = require('globals'); 3 | 4 | module.exports = { 5 | languageOptions: { 6 | globals: globals.builtin, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /test/snapshots/no-single-promise-in-promise-methods.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/no-single-promise-in-promise-methods.mjs.snap -------------------------------------------------------------------------------- /test/snapshots/prefer-logical-operator-over-ternary.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/prefer-logical-operator-over-ternary.mjs.snap -------------------------------------------------------------------------------- /rules/ast/function-types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const functionTypes = ['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']; 4 | 5 | module.exports = functionTypes; 6 | -------------------------------------------------------------------------------- /test/snapshots/require-number-to-fixed-digits-argument.mjs.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tonyoko/eslint-plugin-unicorn/HEAD/test/snapshots/require-number-to-fixed-digits-argument.mjs.snap -------------------------------------------------------------------------------- /rules/ast/is-undefined.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isUndefined(node) { 4 | return node.type === 'Identifier' && node.name === 'undefined'; 5 | } 6 | 7 | module.exports = isUndefined; 8 | -------------------------------------------------------------------------------- /scripts/internal-rules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "eslint-plugin-internal-rules", 4 | "version": "0.0.0", 5 | "description": "Internal rules", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /rules/ast/is-directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isDirective = node => 4 | node.type === 'ExpressionStatement' 5 | && typeof node.directive === 'string'; 6 | 7 | module.exports = isDirective; 8 | -------------------------------------------------------------------------------- /.github/security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 4 | -------------------------------------------------------------------------------- /rules/utils/get-builtin-rule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getBuiltinRule(id) { 4 | return require('eslint/use-at-your-own-risk').builtinRules.get(id); 5 | } 6 | 7 | module.exports = getBuiltinRule; 8 | -------------------------------------------------------------------------------- /configs/legacy-config-base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | env: { 4 | es2024: true, 5 | }, 6 | parserOptions: { 7 | ecmaVersion: 'latest', 8 | sourceType: 'module', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /rules/ast/is-function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const functionTypes = require('./function-types.js'); 3 | 4 | function isFunction(node) { 5 | return functionTypes.includes(node.type); 6 | } 7 | 8 | module.exports = isFunction; 9 | -------------------------------------------------------------------------------- /rules/utils/is-on-same-line.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isOnSameLine(nodeOrTokenA, nodeOrTokenB) { 4 | return nodeOrTokenA.loc.start.line === nodeOrTokenB.loc.start.line; 5 | } 6 | 7 | module.exports = isOnSameLine; 8 | -------------------------------------------------------------------------------- /rules/utils/is-value-not-usable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isExpressionStatement} = require('../ast/index.js'); 4 | 5 | const isValueNotUsable = node => isExpressionStatement(node.parent); 6 | module.exports = isValueNotUsable; 7 | -------------------------------------------------------------------------------- /test/integration/fixtures-local/error-name-conflicts.js: -------------------------------------------------------------------------------- 1 | function foo() { 2 | try { 3 | } catch (err) { 4 | console.log(err); 5 | 6 | if (test) { 7 | throw a; 8 | } else { 9 | throw b; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /rules/ast/is-arrow-function-body.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isArrowFunctionBody(node) { 4 | return node.parent.type === 'ArrowFunctionExpression' && node.parent.body === node; 5 | } 6 | 7 | module.exports = isArrowFunctionBody; 8 | -------------------------------------------------------------------------------- /rules/utils/has-same-range.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const hasSameRange = (node1, node2) => 4 | node1 5 | && node2 6 | && node1.range[0] === node2.range[0] 7 | && node1.range[1] === node2.range[1]; 8 | module.exports = hasSameRange; 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /rules/utils/is-same-identifier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isSameIdentifier = (nodeA, nodeB) => 4 | nodeA.type === 'Identifier' 5 | && nodeB.type === 'Identifier' 6 | && nodeA.name === nodeB.name; 7 | 8 | module.exports = isSameIdentifier; 9 | -------------------------------------------------------------------------------- /rules/utils/escape-template-element-raw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const escapeTemplateElementRaw = string => string.replaceAll( 4 | /(?<=(?:^|[^\\])(?:\\\\)*)(?(?:`|\$(?={)))/g, 5 | String.raw`\$`, 6 | ); 7 | module.exports = escapeTemplateElementRaw; 8 | -------------------------------------------------------------------------------- /rules/utils/get-references.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getScopes = require('./get-scopes.js'); 4 | 5 | const getReferences = scope => [...new Set( 6 | getScopes(scope).flatMap(({references}) => references), 7 | )]; 8 | 9 | module.exports = getReferences; 10 | -------------------------------------------------------------------------------- /rules/utils/is-shorthand-property-value.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isShorthandPropertyValue = identifier => 4 | identifier.parent.type === 'Property' 5 | && identifier.parent.shorthand 6 | && identifier === identifier.parent.value; 7 | 8 | module.exports = isShorthandPropertyValue; 9 | -------------------------------------------------------------------------------- /rules/utils/get-variable-identifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Get identifiers of given variable 4 | const getVariableIdentifiers = ({identifiers, references}) => [...new Set([ 5 | ...identifiers, 6 | ...references.map(({identifier}) => identifier), 7 | ])]; 8 | module.exports = getVariableIdentifiers; 9 | -------------------------------------------------------------------------------- /scripts/template/test.mjs.jst: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | 'const foo = "🦄";', 9 | ], 10 | invalid: [ 11 | 'const foo = "unicorn";', 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /rules/fix/replace-argument.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {getParenthesizedRange} = require('../utils/parentheses.js'); 3 | 4 | function replaceArgument(fixer, node, text, sourceCode) { 5 | return fixer.replaceTextRange(getParenthesizedRange(node, sourceCode), text); 6 | } 7 | 8 | module.exports = replaceArgument; 9 | -------------------------------------------------------------------------------- /rules/utils/is-method-named.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isMethodNamed = (node, name) => 4 | node.type === 'CallExpression' 5 | && node.callee.type === 'MemberExpression' 6 | && node.callee.property.type === 'Identifier' 7 | && node.callee.property.name === name; 8 | 9 | module.exports = isMethodNamed; 10 | -------------------------------------------------------------------------------- /test/integration/readme.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | 3 | To run the integration tests, go to the project root, and run `$ npm run integration`. 4 | 5 | To run tests on specific projects, run `$ npm run integration projectName1 projectName2 … projectNameN`. The project names can be found in [`projects.mjs`](projects.mjs). 6 | -------------------------------------------------------------------------------- /rules/ast/is-expression-statement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isExpressionStatement(node) { 4 | return node.type === 'ExpressionStatement' 5 | || ( 6 | node.type === 'ChainExpression' 7 | && node.parent.type === 'ExpressionStatement' 8 | ); 9 | } 10 | 11 | module.exports = isExpressionStatement; 12 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type {ESLint, Linter} from 'eslint'; 2 | 3 | declare const eslintPluginUnicorn: ESLint.Plugin & { 4 | configs: { 5 | recommended: Linter.Config; 6 | all: Linter.Config; 7 | 'flat/all': Linter.FlatConfig; 8 | 'flat/recommended': Linter.FlatConfig; 9 | }; 10 | }; 11 | 12 | export = eslintPluginUnicorn; 13 | -------------------------------------------------------------------------------- /rules/fix/replace-template-element.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const replaceTemplateElement = (fixer, node, replacement) => { 4 | const {range: [start, end], tail} = node; 5 | return fixer.replaceTextRange( 6 | [start + 1, end - (tail ? 1 : 2)], 7 | replacement, 8 | ); 9 | }; 10 | 11 | module.exports = replaceTemplateElement; 12 | -------------------------------------------------------------------------------- /rules/ast/is-negative-one.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isNumberLiteral} = require('./literal.js'); 4 | 5 | function isNegativeOne(node) { 6 | return node?.type === 'UnaryExpression' 7 | && node.operator === '-' 8 | && isNumberLiteral(node.argument) 9 | && node.argument.value === 1; 10 | } 11 | 12 | module.exports = isNegativeOne; 13 | -------------------------------------------------------------------------------- /rules/utils/is-shorthand-export-local.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const hasSameRange = require('./has-same-range.js'); 3 | 4 | const isShorthandExportLocal = node => { 5 | const {type, local, exported} = node.parent; 6 | return type === 'ExportSpecifier' && hasSameRange(local, exported) && local === node; 7 | }; 8 | 9 | module.exports = isShorthandExportLocal; 10 | -------------------------------------------------------------------------------- /rules/utils/is-shorthand-import-local.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const hasSameRange = require('./has-same-range.js'); 3 | 4 | const isShorthandImportLocal = node => { 5 | const {type, local, imported} = node.parent; 6 | return type === 'ImportSpecifier' && hasSameRange(local, imported) && local === node; 7 | }; 8 | 9 | module.exports = isShorthandImportLocal; 10 | -------------------------------------------------------------------------------- /rules/utils/get-indent-string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getIndentString(node, sourceCode) { 4 | const {line, column} = sourceCode.getLocFromIndex(node.range[0]); 5 | const lines = sourceCode.getLines(); 6 | const before = lines[line - 1].slice(0, column); 7 | 8 | return before.match(/\s*$/)[0]; 9 | } 10 | 11 | module.exports = getIndentString; 12 | -------------------------------------------------------------------------------- /scripts/template/documentation.md.jst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ## Fail 7 | 8 | ```js 9 | const foo = 'unicorn'; 10 | ``` 11 | 12 | ## Pass 13 | 14 | ```js 15 | const foo = '🦄'; 16 | ``` 17 | -------------------------------------------------------------------------------- /rules/fix/remove-parentheses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {getParentheses} = require('../utils/parentheses.js'); 3 | 4 | function * removeParentheses(node, fixer, sourceCode) { 5 | const parentheses = getParentheses(node, sourceCode); 6 | for (const token of parentheses) { 7 | yield fixer.remove(token); 8 | } 9 | } 10 | 11 | module.exports = removeParentheses; 12 | -------------------------------------------------------------------------------- /rules/fix/replace-string-raw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Replace `StringLiteral` or `TemplateLiteral` node with raw text 4 | const replaceStringRaw = (fixer, node, raw) => 5 | fixer.replaceTextRange( 6 | // Ignore quotes and backticks 7 | [ 8 | node.range[0] + 1, 9 | node.range[1] - 1, 10 | ], 11 | raw, 12 | ); 13 | 14 | module.exports = replaceStringRaw; 15 | -------------------------------------------------------------------------------- /rules/utils/is-object-method.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function isObjectMethod(node, object, method) { 3 | const {callee} = node; 4 | return ( 5 | callee.type === 'MemberExpression' 6 | && callee.object.type === 'Identifier' 7 | && callee.object.name === object 8 | && callee.property.type === 'Identifier' 9 | && callee.property.name === method 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /rules/utils/get-scopes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Gather a list of all Scopes starting recursively from the input Scope. 5 | 6 | @param {Scope} scope - The Scope to start checking from. 7 | @returns {Scope[]} - The resulting Scopes. 8 | */ 9 | const getScopes = scope => [ 10 | scope, 11 | ...scope.childScopes.flatMap(scope => getScopes(scope)), 12 | ]; 13 | 14 | module.exports = getScopes; 15 | -------------------------------------------------------------------------------- /rules/ast/is-static-require.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isStringLiteral} = require('./literal.js'); 4 | const {isCallExpression} = require('./call-or-new-expression.js'); 5 | 6 | const isStaticRequire = node => 7 | isCallExpression(node, { 8 | name: 'require', 9 | argumentsLength: 1, 10 | optional: false, 11 | }) 12 | && isStringLiteral(node.arguments[0]); 13 | 14 | module.exports = isStaticRequire; 15 | -------------------------------------------------------------------------------- /rules/shared/typed-array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#description 4 | module.exports = [ 5 | 'Int8Array', 6 | 'Uint8Array', 7 | 'Uint8ClampedArray', 8 | 'Int16Array', 9 | 'Uint16Array', 10 | 'Int32Array', 11 | 'Uint32Array', 12 | 'Float32Array', 13 | 'Float64Array', 14 | 'BigInt64Array', 15 | 'BigUint64Array', 16 | ]; 17 | -------------------------------------------------------------------------------- /rules/fix/rename-variable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const getVariableIdentifiers = require('../utils/get-variable-identifiers.js'); 3 | const replaceReferenceIdentifier = require('./replace-reference-identifier.js'); 4 | 5 | const renameVariable = (variable, name, fixer) => 6 | getVariableIdentifiers(variable) 7 | .map(identifier => replaceReferenceIdentifier(identifier, name, fixer)); 8 | 9 | module.exports = renameVariable; 10 | -------------------------------------------------------------------------------- /rules/utils/get-documentation-url.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('node:path'); 3 | const packageJson = require('../../package.json'); 4 | 5 | const repoUrl = 'https://github.com/sindresorhus/eslint-plugin-unicorn'; 6 | 7 | module.exports = function getDocumentationUrl(filename) { 8 | const ruleName = path.basename(filename, '.js'); 9 | return `${repoUrl}/blob/v${packageJson.version}/docs/rules/${ruleName}.md`; 10 | }; 11 | -------------------------------------------------------------------------------- /rules/utils/is-shorthand-property-assignment-pattern-left.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isShorthandPropertyValue = require('./is-shorthand-property-value.js'); 4 | 5 | const isShorthandPropertyAssignmentPatternLeft = identifier => 6 | identifier.parent.type === 'AssignmentPattern' 7 | && identifier.parent.left === identifier 8 | && isShorthandPropertyValue(identifier.parent); 9 | 10 | module.exports = isShorthandPropertyAssignmentPatternLeft; 11 | -------------------------------------------------------------------------------- /rules/fix/remove-member-expression-property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {getParenthesizedRange} = require('../utils/parentheses.js'); 3 | 4 | function removeMemberExpressionProperty(fixer, memberExpression, sourceCode) { 5 | const [, start] = getParenthesizedRange(memberExpression.object, sourceCode); 6 | const [, end] = memberExpression.range; 7 | 8 | return fixer.removeRange([start, end]); 9 | } 10 | 11 | module.exports = removeMemberExpressionProperty; 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: A rule isn't working as it should? 4 | labels: bug 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ```js 16 | const code = 'that should be ok'; 17 | ``` 18 | -------------------------------------------------------------------------------- /rules/ast/is-empty-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function isEmptyNode(node, additionalEmpty) { 3 | const {type} = node; 4 | 5 | if (type === 'BlockStatement') { 6 | return node.body.every(currentNode => isEmptyNode(currentNode, additionalEmpty)); 7 | } 8 | 9 | if (type === 'EmptyStatement') { 10 | return true; 11 | } 12 | 13 | if (additionalEmpty?.(node)) { 14 | return true; 15 | } 16 | 17 | return false; 18 | } 19 | 20 | module.exports = isEmptyNode; 21 | -------------------------------------------------------------------------------- /rules/utils/get-ancestor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // TODO: Support more types 4 | function getPredicate(options) { 5 | if (typeof options === 'string') { 6 | return node => node.type === options; 7 | } 8 | } 9 | 10 | function getAncestor(node, options) { 11 | const predicate = getPredicate(options); 12 | 13 | for (;node.parent; node = node.parent) { 14 | if (predicate(node)) { 15 | return node; 16 | } 17 | } 18 | } 19 | 20 | module.exports = getAncestor; 21 | -------------------------------------------------------------------------------- /test/snapshots/no-process-exit.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/no-process-exit.mjs` 2 | 3 | The actual snapshot is saved in `no-process-exit.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): process.exit(1); 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | process.exit(1);␊ 13 | ` 14 | 15 | > Error 1/1 16 | 17 | `␊ 18 | > 1 | process.exit(1);␊ 19 | | ^^^^^^^^^^^^^^^ Only use \`process.exit()\` in CLI apps. Throw an error instead.␊ 20 | ` 21 | -------------------------------------------------------------------------------- /test/snapshots/better-regex.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/better-regex.mjs` 2 | 3 | The actual snapshot is saved in `better-regex.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): /(?!a)+/g 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | /(?!a)+/g␊ 13 | ` 14 | 15 | > Error 1/1 16 | 17 | `␊ 18 | > 1 | /(?!a)+/g␊ 19 | | ^^^^^^^^^ Problem parsing /(?!a)+/g: ␊ 20 | ␊ 21 | /(?!a)+/g␊ 22 | ^␊ 23 | Unexpected token: "+" at 1:6.␊ 24 | ` 25 | -------------------------------------------------------------------------------- /rules/utils/has-optional-chain-element.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isChainElement = node => node.type === 'MemberExpression' || node.type === 'CallExpression'; 4 | 5 | function hasOptionalChainElement(node) { 6 | if (!isChainElement(node)) { 7 | return false; 8 | } 9 | 10 | if (node.optional) { 11 | return true; 12 | } 13 | 14 | if (node.type === 'MemberExpression') { 15 | return hasOptionalChainElement(node.object); 16 | } 17 | 18 | return false; 19 | } 20 | 21 | module.exports = hasOptionalChainElement; 22 | -------------------------------------------------------------------------------- /rules/fix/remove-spaces-after.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function removeSpacesAfter(indexOrNodeOrToken, sourceCode, fixer) { 4 | let index = indexOrNodeOrToken; 5 | if (typeof indexOrNodeOrToken === 'object' && Array.isArray(indexOrNodeOrToken.range)) { 6 | index = indexOrNodeOrToken.range[1]; 7 | } 8 | 9 | const textAfter = sourceCode.text.slice(index); 10 | const [leadingSpaces] = textAfter.match(/^\s*/); 11 | return fixer.removeRange([index, index + leadingSpaces.length]); 12 | } 13 | 14 | module.exports = removeSpacesAfter; 15 | -------------------------------------------------------------------------------- /rules/fix/replace-string-literal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function replaceStringLiteral(fixer, node, text, relativeRangeStart, relativeRangeEnd) { 4 | const firstCharacterIndex = node.range[0] + 1; 5 | const start = Number.isInteger(relativeRangeEnd) ? relativeRangeStart + firstCharacterIndex : firstCharacterIndex; 6 | const end = Number.isInteger(relativeRangeEnd) ? relativeRangeEnd + firstCharacterIndex : node.range[1] - 1; 7 | 8 | return fixer.replaceTextRange([start, end], text); 9 | } 10 | 11 | module.exports = replaceStringLiteral; 12 | -------------------------------------------------------------------------------- /.github/workflows/smoke-test.yml: -------------------------------------------------------------------------------- 1 | name: Smoke test 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * SUN" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | - run: npm install 15 | - uses: AriPerkkio/eslint-remote-tester-run-action@v5 16 | with: 17 | issue-title: "Results of weekly scheduled smoke test" 18 | eslint-remote-tester-config: test/smoke/eslint-remote-tester.config.mjs 19 | -------------------------------------------------------------------------------- /rules/fix/extend-fix-range.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Extend fix range to prevent changes from other rules. 5 | https://github.com/eslint/eslint/pull/13748/files#diff-c692f3fde09eda7c89f1802c908511a3fb59f5d207fe95eb009cb52e46a99e84R348 6 | 7 | @param {ruleFixer} fixer - The fixer to fix. 8 | @param {int[]} range - The extended range node. 9 | */ 10 | function * extendFixRange(fixer, range) { 11 | yield fixer.insertTextBeforeRange(range, ''); 12 | yield fixer.insertTextAfterRange(range, ''); 13 | } 14 | 15 | module.exports = extendFixRange; 16 | -------------------------------------------------------------------------------- /test/prefer-blob-reading-methods.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | 3 | const {test} = getTester(import.meta); 4 | 5 | test.snapshot({ 6 | valid: [ 7 | 'blob.arrayBuffer()', 8 | 'blob.text()', 9 | 'new Response(blob).arrayBuffer()', 10 | 'new Response(blob).text()', 11 | 'fileReader.readAsDataURL(blob)', 12 | 'fileReader.readAsBinaryString(blob)', 13 | 'fileReader.readAsText(blob, "ascii")', 14 | ], 15 | invalid: [ 16 | 'fileReader.readAsArrayBuffer(blob)', 17 | 'fileReader.readAsText(blob)', 18 | ], 19 | }); 20 | -------------------------------------------------------------------------------- /test/snapshots/no-hex-escape.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/no-hex-escape.mjs` 2 | 3 | The actual snapshot is saved in `no-hex-escape.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): const foo = "\xb1" 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | const foo = "\\xb1"␊ 13 | ` 14 | 15 | > Output 16 | 17 | `␊ 18 | 1 | const foo = "\\u00b1"␊ 19 | ` 20 | 21 | > Error 1/1 22 | 23 | `␊ 24 | > 1 | const foo = "\\xb1"␊ 25 | | ^^^^^^ Use Unicode escapes instead of hexadecimal escapes.␊ 26 | ` 27 | -------------------------------------------------------------------------------- /rules/utils/get-previous-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getPreviousNode(node, sourceCode) { 4 | const {parent} = node; 5 | const visitorKeys = sourceCode.visitorKeys[parent.type] || Object.keys(parent); 6 | 7 | for (const property of visitorKeys) { 8 | const value = parent[property]; 9 | 10 | if (value === node) { 11 | return; 12 | } 13 | 14 | if (Array.isArray(value)) { 15 | const index = value.indexOf(node); 16 | 17 | if (index !== -1) { 18 | return value[index - 1]; 19 | } 20 | } 21 | } 22 | } 23 | 24 | module.exports = getPreviousNode; 25 | -------------------------------------------------------------------------------- /rules/utils/singular.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {singular: pluralizeSingular} = require('pluralize'); 4 | 5 | /** 6 | Singularizes a word/name, i.e. `items` to `item`. 7 | 8 | @param {string} original - The word/name to singularize. 9 | @returns {string|undefined} - The singularized result, or `undefined` if attempting singularization resulted in no change. 10 | */ 11 | const singular = original => { 12 | const singularized = pluralizeSingular(original); 13 | if (singularized !== original) { 14 | return singularized; 15 | } 16 | }; 17 | 18 | module.exports = singular; 19 | -------------------------------------------------------------------------------- /test/unit/snapshots/assert-token.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/assert-token.mjs` 2 | 3 | The actual snapshot is saved in `assert-token.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## Error message 8 | 9 | > Snapshot 1 10 | 11 | Error { 12 | message: `Expected token '{"value":"expectedValue"}' or '{"type":"expectedType"}', got '{"value":"b","type":"a"}'.␊ 13 | Please open an issue at https://github.com/sindresorhus/eslint-plugin-unicorn/issues/new?title=%60test-rule%60%3A%20Unexpected%20token%20'%7B%22value%22%3A%22b%22%2C%22type%22%3A%22a%22%7D'.`, 14 | } 15 | -------------------------------------------------------------------------------- /rules/utils/resolve-variable-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Finds a variable named `name` in the scope `scope` (or it's parents). 5 | 6 | @param {string} name - The variable name to be resolve. 7 | @param {import('eslint').Scope.Scope} scope - The scope to look for the variable in. 8 | @returns {import('eslint').Scope.Variable | void} - The found variable, if any. 9 | */ 10 | module.exports = function resolveVariableName(name, scope) { 11 | while (scope) { 12 | const variable = scope.set.get(name); 13 | 14 | if (variable) { 15 | return variable; 16 | } 17 | 18 | scope = scope.upper; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/utils/not-dom-node-types.mjs: -------------------------------------------------------------------------------- 1 | const notDomNodeTypes = [ 2 | // ArrayExpression 3 | '[]', 4 | '[element]', 5 | '[...elements]', 6 | // ArrowFunctionExpression 7 | '() => {}', 8 | // ClassExpression 9 | 'class Node {}', 10 | // FunctionExpression 11 | 'function() {}', 12 | // Literal 13 | '0', 14 | '1', 15 | '0.1', 16 | '""', 17 | '"string"', 18 | '/regex/', 19 | 'null', 20 | '0n', 21 | '1n', 22 | 'true', 23 | 'false', 24 | // ObjectExpression 25 | '{}', 26 | // TemplateLiteral 27 | '`templateLiteral`', 28 | // Undefined 29 | 'undefined', 30 | ]; 31 | 32 | export default notDomNodeTypes; 33 | -------------------------------------------------------------------------------- /rules/utils/builtins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const typedArray = require('../shared/typed-array.js'); 3 | 4 | const enforceNew = [ 5 | 'Object', 6 | 'Array', 7 | 'ArrayBuffer', 8 | 'DataView', 9 | 'Date', 10 | 'Error', 11 | 'Function', 12 | 'Map', 13 | 'WeakMap', 14 | 'Set', 15 | 'WeakSet', 16 | 'Promise', 17 | 'RegExp', 18 | 'SharedArrayBuffer', 19 | 'Proxy', 20 | 'WeakRef', 21 | 'FinalizationRegistry', 22 | ...typedArray, 23 | ]; 24 | 25 | const disallowNew = [ 26 | 'BigInt', 27 | 'Boolean', 28 | 'Number', 29 | 'String', 30 | 'Symbol', 31 | ]; 32 | 33 | module.exports = { 34 | enforceNew, 35 | disallowNew, 36 | }; 37 | -------------------------------------------------------------------------------- /.github/workflows/title-formatter.yml: -------------------------------------------------------------------------------- 1 | name: Title formatter 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, edited] 6 | issues: 7 | types: [opened, edited] 8 | 9 | jobs: 10 | Rule: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | pull-requests: write 14 | issues: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Auto-format rule names in titles 18 | uses: fregante/title-replacer-action@v2 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | pattern-path: docs/rules 22 | replacement: '`$0`' 23 | trim-wrappers: true 24 | -------------------------------------------------------------------------------- /rules/utils/is-node-value-not-dom-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isUndefined} = require('../ast/index.js'); 3 | 4 | // AST Types: 5 | // https://github.com/eslint/espree/blob/master/lib/ast-node-types.js#L18 6 | // Only types possible to be `callee` or `argument` are listed 7 | const impossibleNodeTypes = new Set([ 8 | 'ArrayExpression', 9 | 'ArrowFunctionExpression', 10 | 'ClassExpression', 11 | 'FunctionExpression', 12 | 'Literal', 13 | 'ObjectExpression', 14 | 'TemplateLiteral', 15 | ]); 16 | 17 | const isNodeValueNotDomNode = node => 18 | impossibleNodeTypes.has(node.type) 19 | || isUndefined(node); 20 | 21 | module.exports = isNodeValueNotDomNode; 22 | -------------------------------------------------------------------------------- /rules/utils/get-class-head-location.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | @typedef {line: number, column: number} Position 5 | 6 | Get the location of the given class node for reporting. 7 | 8 | @param {Node} node - The class node to get. 9 | @param {SourceCode} sourceCode - The source code object to get tokens. 10 | @returns {{start: Position, end: Position}} The location of the class node for reporting. 11 | */ 12 | function getClassHeadLocation(node, sourceCode) { 13 | const {loc, body} = node; 14 | const tokenBeforeBody = sourceCode.getTokenBefore(body); 15 | 16 | const {start} = loc; 17 | const {end} = tokenBeforeBody.loc; 18 | 19 | return {start, end}; 20 | } 21 | 22 | module.exports = getClassHeadLocation; 23 | -------------------------------------------------------------------------------- /rules/utils/escape-string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const jsesc = require('jsesc'); 4 | 5 | /** 6 | Escape string and wrap the result in quotes. 7 | 8 | @param {string} string - The string to be quoted. 9 | @param {string} [quote] - The quote character. 10 | @returns {string} - The quoted and escaped string. 11 | */ 12 | module.exports = function escapeString(string, quote = '\'') { 13 | /* c8 ignore start */ 14 | if (typeof string !== 'string') { 15 | throw new TypeError('Unexpected string.'); 16 | } 17 | /* c8 ignore end */ 18 | 19 | return jsesc(string, { 20 | quotes: quote === '"' ? 'double' : 'single', 21 | wrap: true, 22 | es6: true, 23 | minimal: true, 24 | lowercaseHex: false, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /test/no-magic-array-flat-depth.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | 3 | const {test} = getTester(import.meta); 4 | 5 | test.snapshot({ 6 | valid: [ 7 | 'array.flat(1)', 8 | 'array.flat(1.0)', 9 | 'array.flat(0x01)', 10 | 'array.flat(unknown)', 11 | 'array.flat(Number.POSITIVE_INFINITY)', 12 | 'array.flat(Infinity)', 13 | 'array.flat(/* explanation */2)', 14 | 'array.flat(2/* explanation */)', 15 | 'array.flat()', 16 | 'array.flat(2, extraArgument)', 17 | 'new array.flat(2)', 18 | 'array.flat?.(2)', 19 | 'array.notFlat(2)', 20 | 'flat(2)', 21 | ], 22 | invalid: [ 23 | 'array.flat(2)', 24 | 'array?.flat(2)', 25 | 'array.flat(99,)', 26 | 'array.flat(0b10,)', 27 | ], 28 | }); 29 | -------------------------------------------------------------------------------- /.npmpackagejsonlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-duplicate-properties": "error", 4 | "no-repeated-dependencies": "error", 5 | "prefer-alphabetical-dependencies": "error", 6 | "prefer-alphabetical-devDependencies": "error", 7 | "prefer-alphabetical-optionalDependencies": "error", 8 | "prefer-alphabetical-bundledDependencies": "error", 9 | "prefer-alphabetical-scripts": "error", 10 | "prefer-caret-version-dependencies": "error", 11 | "prefer-caret-version-devDependencies": [ 12 | "error", 13 | { 14 | "exceptions": [ 15 | "eslint-plugin-internal-rules" 16 | ] 17 | } 18 | ], 19 | "prefer-scripts": [ 20 | "error", 21 | [ 22 | "lint", 23 | "test" 24 | ] 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rules/ast/is-tagged-template-literal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isNodeMatches} = require('../utils/is-node-matches.js'); 4 | 5 | /** 6 | Check if the given node is a tagged template literal. 7 | 8 | @param {Node} node - The AST node to check. 9 | @param {string[]} tags - The object name or key paths. 10 | @returns {boolean} 11 | */ 12 | function isTaggedTemplateLiteral(node, tags) { 13 | if ( 14 | node.type !== 'TemplateLiteral' 15 | || node.parent.type !== 'TaggedTemplateExpression' 16 | || node.parent.quasi !== node 17 | ) { 18 | return false; 19 | } 20 | 21 | if (tags) { 22 | return isNodeMatches(node.parent.tag, tags); 23 | } 24 | 25 | return true; 26 | } 27 | 28 | module.exports = isTaggedTemplateLiteral; 29 | -------------------------------------------------------------------------------- /rules/utils/should-add-parentheses-to-conditional-expression-child.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Check if parentheses should be added to a `node` when it's used as child of `ConditionalExpression`. 5 | 6 | @param {Node} node - The AST node to check. 7 | @returns {boolean} 8 | */ 9 | function shouldAddParenthesesToConditionalExpressionChild(node) { 10 | return node.type === 'AwaitExpression' 11 | // Lower precedence, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table 12 | || node.type === 'AssignmentExpression' 13 | || node.type === 'YieldExpression' 14 | || node.type === 'SequenceExpression'; 15 | } 16 | 17 | module.exports = shouldAddParenthesesToConditionalExpressionChild; 18 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | ## I have an idea for a new rule 4 | 5 | Open an issue with your proposal. Make sure you elaborate on what problem it solves and include fail/pass examples. [(Example)](https://github.com/sindresorhus/eslint-plugin-unicorn/issues/166) 6 | 7 | ## I have an idea for a new rule and I also want to implement it 8 | 9 | First open an issue with your proposal. When the rule is accepted, see the [docs on creating and submitting a new rule](../docs/new-rule.md). 10 | 11 | ## I want to implement a rule from an open issue 12 | 13 | See the [docs on creating and submitting a new rule](../docs/new-rule.md). 14 | 15 | ## How to write tests 16 | 17 | See the [docs on writing tests](../docs/write-tests.md). 18 | -------------------------------------------------------------------------------- /docs/rules/empty-brace-spaces.md: -------------------------------------------------------------------------------- 1 | # Enforce no spaces between braces 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | ## Fail 11 | 12 | ```js 13 | class Unicorn { 14 | } 15 | ``` 16 | 17 | ```js 18 | try { 19 | foo(); 20 | } catch { } 21 | ``` 22 | 23 | ## Pass 24 | 25 | ```js 26 | class Unicorn {} 27 | ``` 28 | 29 | ```js 30 | try { 31 | foo(); 32 | } catch {} 33 | ``` 34 | -------------------------------------------------------------------------------- /rules/utils/should-add-parentheses-to-await-expression-argument.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Check if parentheses should be added to a `node` when it's used as `argument` of `AwaitExpression`. 5 | 6 | @param {Node} node - The AST node to check. 7 | @returns {boolean} 8 | */ 9 | function shouldAddParenthesesToAwaitExpressionArgument(node) { 10 | return ( 11 | node.type === 'SequenceExpression' 12 | || node.type === 'YieldExpression' 13 | || node.type === 'ArrowFunctionExpression' 14 | || node.type === 'ConditionalExpression' 15 | || node.type === 'AssignmentExpression' 16 | || node.type === 'LogicalExpression' 17 | || node.type === 'BinaryExpression' 18 | ); 19 | } 20 | 21 | module.exports = shouldAddParenthesesToAwaitExpressionArgument; 22 | -------------------------------------------------------------------------------- /rules/fix/add-parenthesizes-to-return-or-throw-expression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isSemicolonToken} = require('@eslint-community/eslint-utils'); 3 | 4 | function * addParenthesizesToReturnOrThrowExpression(fixer, node, sourceCode) { 5 | if (node.type !== 'ReturnStatement' && node.type !== 'ThrowStatement') { 6 | return; 7 | } 8 | 9 | const returnOrThrowToken = sourceCode.getFirstToken(node); 10 | yield fixer.insertTextAfter(returnOrThrowToken, ' ('); 11 | 12 | const lastToken = sourceCode.getLastToken(node); 13 | if (!isSemicolonToken(lastToken)) { 14 | yield fixer.insertTextAfter(node, ')'); 15 | return; 16 | } 17 | 18 | yield fixer.insertTextBefore(lastToken, ')'); 19 | } 20 | 21 | module.exports = addParenthesizesToReturnOrThrowExpression; 22 | -------------------------------------------------------------------------------- /rules/fix/remove-method-call.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {getParenthesizedRange} = require('../utils/parentheses.js'); 3 | const removeMemberExpressionProperty = require('./remove-member-expression-property.js'); 4 | 5 | function * removeMethodCall(fixer, callExpression, sourceCode) { 6 | const memberExpression = callExpression.callee; 7 | 8 | // `(( (( foo )).bar ))()` 9 | // ^^^^ 10 | yield removeMemberExpressionProperty(fixer, memberExpression, sourceCode); 11 | 12 | // `(( (( foo )).bar ))()` 13 | // ^^ 14 | const [, start] = getParenthesizedRange(memberExpression, sourceCode); 15 | const [, end] = callExpression.range; 16 | 17 | yield fixer.removeRange([start, end]); 18 | } 19 | 20 | module.exports = removeMethodCall; 21 | -------------------------------------------------------------------------------- /rules/utils/is-logical-expression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Check if the given node is a true logical expression or not. 5 | 6 | The three binary expressions logical-or (`||`), logical-and (`&&`), and coalesce (`??`) are known as `ShortCircuitExpression`, but ESTree represents these by the `LogicalExpression` node type. This function rejects coalesce expressions of `LogicalExpression` node type. 7 | 8 | @param {Node} node - The node to check. 9 | @returns {boolean} `true` if the node is `&&` or `||`. 10 | @see https://tc39.es/ecma262/#prod-ShortCircuitExpression 11 | */ 12 | const isLogicalExpression = node => 13 | node?.type === 'LogicalExpression' 14 | && (node.operator === '&&' || node.operator === '||'); 15 | 16 | module.exports = isLogicalExpression; 17 | -------------------------------------------------------------------------------- /rules/fix/append-argument.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isCommaToken} = require('@eslint-community/eslint-utils'); 3 | 4 | function appendArgument(fixer, node, text, sourceCode) { 5 | // This function should also work for `NewExpression` 6 | // But parentheses of `NewExpression` could be omitted, add this check to prevent accident use on it 7 | /* c8 ignore next 3 */ 8 | if (node.type !== 'CallExpression') { 9 | throw new Error(`Unexpected node "${node.type}".`); 10 | } 11 | 12 | const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2); 13 | if (node.arguments.length > 0) { 14 | text = isCommaToken(penultimateToken) ? ` ${text},` : `, ${text}`; 15 | } 16 | 17 | return fixer.insertTextBefore(lastToken, text); 18 | } 19 | 20 | module.exports = appendArgument; 21 | -------------------------------------------------------------------------------- /rules/utils/is-left-hand-side.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isLeftHandSide = node => 4 | ( 5 | (node.parent.type === 'AssignmentExpression' || node.parent.type === 'AssignmentPattern') 6 | && node.parent.left === node 7 | ) 8 | || (node.parent.type === 'UpdateExpression' && node.parent.argument === node) 9 | || (node.parent.type === 'ArrayPattern' && node.parent.elements.includes(node)) 10 | || ( 11 | node.parent.type === 'Property' 12 | && node.parent.value === node 13 | && node.parent.parent.type === 'ObjectPattern' 14 | && node.parent.parent.properties.includes(node.parent) 15 | ) 16 | || ( 17 | node.parent.type === 'UnaryExpression' 18 | && node.parent.operator === 'delete' 19 | && node.parent.argument === node 20 | ); 21 | 22 | module.exports = isLeftHandSide; 23 | -------------------------------------------------------------------------------- /rules/utils/create-deprecated-rules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const packageJson = require('../../package.json'); 3 | 4 | const repoUrl = 'https://github.com/sindresorhus/eslint-plugin-unicorn'; 5 | 6 | /** @returns {{ [ruleName: string]: import('eslint').Rule.RuleModule }} */ 7 | function createDeprecatedRules(data) { 8 | return Object.fromEntries( 9 | Object.entries(data).map(([ruleId, replacedBy = []]) => [ 10 | ruleId, 11 | { 12 | create: () => ({}), 13 | meta: { 14 | docs: { 15 | url: `${repoUrl}/blob/v${packageJson.version}/docs/deprecated-rules.md#${ruleId}`, 16 | }, 17 | deprecated: true, 18 | replacedBy: Array.isArray(replacedBy) ? replacedBy : [replacedBy], 19 | }, 20 | }, 21 | ]), 22 | ); 23 | } 24 | 25 | module.exports = createDeprecatedRules; 26 | -------------------------------------------------------------------------------- /rules/utils/should-add-parentheses-to-expression-statement-expression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Check if parentheses should to be added to a `node` when it's used as an `expression` of `ExpressionStatement`. 5 | 6 | @param {Node} node - The AST node to check. 7 | @param {SourceCode} sourceCode - The source code object. 8 | @returns {boolean} 9 | */ 10 | function shouldAddParenthesesToExpressionStatementExpression(node) { 11 | switch (node.type) { 12 | case 'ObjectExpression': { 13 | return true; 14 | } 15 | 16 | case 'AssignmentExpression': { 17 | return node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern'; 18 | } 19 | 20 | default: { 21 | return false; 22 | } 23 | } 24 | } 25 | 26 | module.exports = shouldAddParenthesesToExpressionStatementExpression; 27 | -------------------------------------------------------------------------------- /test/prefer-array-index-of.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | import createSimpleArraySearchRuleTestFixtures from './shared/simple-array-search-rule-tests.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | const indexOfOverFindIndexFixtures = createSimpleArraySearchRuleTestFixtures({ 7 | method: 'findIndex', 8 | replacement: 'indexOf', 9 | }); 10 | 11 | test.snapshot(indexOfOverFindIndexFixtures.snapshot); 12 | test.typescript(indexOfOverFindIndexFixtures.typescript); 13 | 14 | const lastIndexOfOverFindLastIndexFixtures = createSimpleArraySearchRuleTestFixtures({ 15 | method: 'findLastIndex', 16 | replacement: 'lastIndexOf', 17 | }); 18 | 19 | test.snapshot(lastIndexOfOverFindLastIndexFixtures.snapshot); 20 | test.typescript(lastIndexOfOverFindLastIndexFixtures.typescript); 21 | -------------------------------------------------------------------------------- /test/snapshots/prefer-type-error.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/prefer-type-error.mjs` 2 | 3 | The actual snapshot is saved in `prefer-type-error.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): if (!isFinite(foo)) { throw new Error(); } 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | if (!isFinite(foo)) {␊ 13 | 2 | throw new Error();␊ 14 | 3 | }␊ 15 | ` 16 | 17 | > Output 18 | 19 | `␊ 20 | 1 | if (!isFinite(foo)) {␊ 21 | 2 | throw new TypeError();␊ 22 | 3 | }␊ 23 | ` 24 | 25 | > Error 1/1 26 | 27 | `␊ 28 | 1 | if (!isFinite(foo)) {␊ 29 | > 2 | throw new Error();␊ 30 | | ^^^^^ \`new Error()\` is too unspecific for a type check. Use \`new TypeError()\` instead.␊ 31 | 3 | }␊ 32 | ` 33 | -------------------------------------------------------------------------------- /rules/utils/to-location.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Get location info for the given node or range. 5 | 6 | @param {import('estree').Node | number[]} nodeOrRange - The AST node or range to get the location for. 7 | @param {import('eslint').SourceCode} sourceCode - The source code object. 8 | @param {int} [startOffset] - Start position offset. 9 | @param {int} [endOffset] - End position offset. 10 | @returns {import('estree').SourceLocation} 11 | */ 12 | function toLocation(nodeOrRange, sourceCode, startOffset = 0, endOffset = 0) { 13 | const [start, end] = Array.isArray(nodeOrRange) ? nodeOrRange : nodeOrRange.range; 14 | 15 | return { 16 | start: sourceCode.getLocFromIndex(start + startOffset), 17 | end: sourceCode.getLocFromIndex(end + endOffset), 18 | }; 19 | } 20 | 21 | module.exports = toLocation; 22 | -------------------------------------------------------------------------------- /docs/rules/no-negation-in-equality-check.md: -------------------------------------------------------------------------------- 1 | # Disallow negated expression in equality check 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Using a negated expression in equality check is most likely a mistake. 11 | 12 | ## Fail 13 | 14 | ```js 15 | if (!foo === bar) {} 16 | ``` 17 | 18 | ```js 19 | if (!foo !== bar) {} 20 | ``` 21 | 22 | ## Pass 23 | 24 | ```js 25 | if (foo !== bar) {} 26 | ``` 27 | 28 | ```js 29 | if (!(foo === bar)) {} 30 | ``` 31 | -------------------------------------------------------------------------------- /test/snapshots/no-unused-properties.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/no-unused-properties.mjs` 2 | 3 | The actual snapshot is saved in `no-unused-properties.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): function foo() { const bar = { b: 2, u: 3 }; console.log(bar.b); } 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | function foo() {␊ 13 | 2 | const bar = {␊ 14 | 3 | b: 2,␊ 15 | 4 | u: 3␊ 16 | 5 | };␊ 17 | 6 | console.log(bar.b);␊ 18 | 7 | }␊ 19 | ` 20 | 21 | > Error 1/1 22 | 23 | `␊ 24 | 1 | function foo() {␊ 25 | 2 | const bar = {␊ 26 | 3 | b: 2,␊ 27 | > 4 | u: 3␊ 28 | | ^^^^ Property \`u\` is defined but never used.␊ 29 | 5 | };␊ 30 | 6 | console.log(bar.b);␊ 31 | 7 | }␊ 32 | ` 33 | -------------------------------------------------------------------------------- /.eslint-doc-generatorrc.js: -------------------------------------------------------------------------------- 1 | /* eslint unicorn/prevent-abbreviations:"off" -- https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2015 */ 2 | 3 | /** @type {import('eslint-doc-generator').GenerateOptions} */ 4 | const config = { 5 | ignoreConfig: ['all', 'flat/all', 'flat/recommended'], 6 | ignoreDeprecatedRules: true, 7 | ruleDocTitleFormat: 'desc', 8 | ruleListColumns: [ 9 | 'name', 10 | 'description', 11 | 'configsError', 12 | // Omit `configsOff` since we don't intend to convey meaning by setting rules to `off` in the `recommended` config. 13 | 'configsWarn', 14 | 'fixable', 15 | 'hasSuggestions', 16 | 'requiresTypeChecking', 17 | ], 18 | urlConfigs: 'https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs', 19 | }; 20 | 21 | module.exports = config; 22 | -------------------------------------------------------------------------------- /rules/no-document-cookie.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {GlobalReferenceTracker} = require('./utils/global-reference-tracker.js'); 3 | 4 | const MESSAGE_ID = 'no-document-cookie'; 5 | const messages = { 6 | [MESSAGE_ID]: 'Do not use `document.cookie` directly.', 7 | }; 8 | 9 | const tracker = new GlobalReferenceTracker({ 10 | object: 'document.cookie', 11 | filter: ({node}) => node.parent.type === 'AssignmentExpression' && node.parent.left === node, 12 | handle: ({node}) => ({node, messageId: MESSAGE_ID}), 13 | }); 14 | 15 | /** @type {import('eslint').Rule.RuleModule} */ 16 | module.exports = { 17 | create: context => tracker.createListeners(context), 18 | meta: { 19 | type: 'problem', 20 | docs: { 21 | description: 'Do not use `document.cookie` directly.', 22 | recommended: true, 23 | }, 24 | messages, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /rules/utils/get-switch-case-head-location.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isColonToken} = require('@eslint-community/eslint-utils'); 4 | 5 | /** 6 | @typedef {line: number, column: number} Position 7 | 8 | Get the location of the given `SwitchCase` node for reporting. 9 | 10 | @param {Node} node - The `SwitchCase` node to get. 11 | @param {SourceCode} sourceCode - The source code object to get tokens from. 12 | @returns {{start: Position, end: Position}} The location of the class node for reporting. 13 | */ 14 | function getSwitchCaseHeadLocation(node, sourceCode) { 15 | const startToken = node.test || sourceCode.getFirstToken(node); 16 | const colonToken = sourceCode.getTokenAfter(startToken, isColonToken); 17 | 18 | return {start: node.loc.start, end: colonToken.loc.end}; 19 | } 20 | 21 | module.exports = getSwitchCaseHeadLocation; 22 | -------------------------------------------------------------------------------- /test/require-number-to-fixed-digits-argument.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | 3 | const {test} = getTester(import.meta); 4 | 5 | test.snapshot({ 6 | valid: [ 7 | 'number.toFixed(0)', 8 | 'number.toFixed(...[])', 9 | 'number.toFixed(2)', 10 | 'number.toFixed(1,2,3)', 11 | 'number[toFixed]()', 12 | 'number["toFixed"]()', 13 | 'number?.toFixed()', 14 | 'number.toFixed?.()', 15 | 'number.notToFixed();', 16 | 17 | // `callee` is a `NewExpression` 18 | 'new BigNumber(1).toFixed()', 19 | 'new Number(1).toFixed()', 20 | ], 21 | invalid: [ 22 | 'const string = number.toFixed();', 23 | 'const string = number.toFixed( /* comment */ );', 24 | 'Number(1).toFixed()', 25 | 26 | // False positive cases 27 | 'const bigNumber = new BigNumber(1); const string = bigNumber.toFixed();', 28 | ], 29 | }); 30 | -------------------------------------------------------------------------------- /test/snapshots/empty-brace-spaces.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/empty-brace-spaces.mjs` 2 | 3 | The actual snapshot is saved in `empty-brace-spaces.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): try { foo(); } catch (error) { } 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | try {␊ 13 | 2 | foo();␊ 14 | 3 | } catch (error) {␊ 15 | 4 | ␊ 16 | 5 | }␊ 17 | ` 18 | 19 | > Output 20 | 21 | `␊ 22 | 1 | try {␊ 23 | 2 | foo();␊ 24 | 3 | } catch (error) {}␊ 25 | ` 26 | 27 | > Error 1/1 28 | 29 | `␊ 30 | 1 | try {␊ 31 | 2 | foo();␊ 32 | > 3 | } catch (error) {␊ 33 | | ^␊ 34 | > 4 | ␊ 35 | | ^^^^^^^^␊ 36 | > 5 | }␊ 37 | | ^ Do not add spaces between braces.␊ 38 | ` 39 | -------------------------------------------------------------------------------- /rules/fix/switch-call-expression-to-new-expression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isParenthesized} = require('../utils/parentheses.js'); 3 | const shouldAddParenthesesToNewExpressionCallee = require('../utils/should-add-parentheses-to-new-expression-callee.js'); 4 | const fixSpaceAroundKeyword = require('./fix-space-around-keywords.js'); 5 | 6 | function * switchCallExpressionToNewExpression(node, sourceCode, fixer) { 7 | yield * fixSpaceAroundKeyword(fixer, node, sourceCode); 8 | yield fixer.insertTextBefore(node, 'new '); 9 | 10 | const {callee} = node; 11 | if ( 12 | !isParenthesized(callee, sourceCode) 13 | && shouldAddParenthesesToNewExpressionCallee(callee) 14 | ) { 15 | yield fixer.insertTextBefore(callee, '('); 16 | yield fixer.insertTextAfter(callee, ')'); 17 | } 18 | } 19 | 20 | module.exports = switchCallExpressionToNewExpression; 21 | -------------------------------------------------------------------------------- /rules/utils/cartesian-product-samples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function cartesianProductSamples(combinations, length = Number.POSITIVE_INFINITY) { 4 | const total = combinations.reduce((total, {length}) => total * length, 1); 5 | 6 | const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => { 7 | let indexRemaining = sampleIndex; 8 | const combination = []; 9 | for (let combinationIndex = combinations.length - 1; combinationIndex >= 0; combinationIndex--) { 10 | const items = combinations[combinationIndex]; 11 | const {length} = items; 12 | const index = indexRemaining % length; 13 | indexRemaining = (indexRemaining - index) / length; 14 | combination.unshift(items[index]); 15 | } 16 | 17 | return combination; 18 | }); 19 | 20 | return { 21 | total, 22 | samples, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /rules/utils/should-add-parentheses-to-call-expression-callee.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Check if parentheses should be added to a `node` when it's used as `callee` of `CallExpression`. 5 | 6 | @param {Node} node - The AST node to check. 7 | @returns {boolean} 8 | */ 9 | function shouldAddParenthesesToCallExpressionCallee(node) { 10 | return node.type === 'SequenceExpression' 11 | || node.type === 'YieldExpression' 12 | || node.type === 'ArrowFunctionExpression' 13 | || node.type === 'ConditionalExpression' 14 | || node.type === 'AssignmentExpression' 15 | || node.type === 'LogicalExpression' 16 | || node.type === 'BinaryExpression' 17 | || node.type === 'UnaryExpression' 18 | || node.type === 'UpdateExpression' 19 | || node.type === 'NewExpression'; 20 | } 21 | 22 | module.exports = shouldAddParenthesesToCallExpressionCallee; 23 | -------------------------------------------------------------------------------- /docs/rules/no-instanceof-array.md: -------------------------------------------------------------------------------- 1 | # Require `Array.isArray()` instead of `instanceof Array` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | The `instanceof Array` check doesn't work across realms/contexts, for example, frames/windows in browsers or the `vm` module in Node.js. 11 | 12 | ## Fail 13 | 14 | ```js 15 | array instanceof Array; 16 | [1,2,3] instanceof Array; 17 | ``` 18 | 19 | ## Pass 20 | 21 | ```js 22 | Array.isArray(array); 23 | Array.isArray([1,2,3]); 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/rules/prefer-optional-catch-binding.md: -------------------------------------------------------------------------------- 1 | # Prefer omitting the `catch` binding parameter 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | If the `catch` binding parameter is not used, it should be omitted. 11 | 12 | ## Fail 13 | 14 | ```js 15 | try {} catch (notUsedError) {} 16 | ``` 17 | 18 | ```js 19 | try {} catch ({message}) {} 20 | ``` 21 | 22 | ## Pass 23 | 24 | ```js 25 | try {} catch {} 26 | ``` 27 | 28 | ```js 29 | try {} catch (error) { 30 | console.error(error); 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /rules/fix/replace-node-or-token-and-spaces-before.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {getParentheses} = require('../utils/parentheses.js'); 3 | 4 | function * replaceNodeOrTokenAndSpacesBefore(nodeOrToken, replacement, fixer, sourceCode, tokenStore = sourceCode) { 5 | const tokens = getParentheses(nodeOrToken, tokenStore); 6 | 7 | for (const token of tokens) { 8 | yield * replaceNodeOrTokenAndSpacesBefore(token, '', fixer, sourceCode, tokenStore); 9 | } 10 | 11 | let [start, end] = nodeOrToken.range; 12 | 13 | const textBefore = sourceCode.text.slice(0, start); 14 | const [trailingSpaces] = textBefore.match(/\s*$/); 15 | const [lineBreak] = trailingSpaces.match(/(?:\r?\n|\r){0,1}/); 16 | start -= trailingSpaces.length; 17 | 18 | yield fixer.replaceTextRange([start, end], `${lineBreak}${replacement}`); 19 | } 20 | 21 | module.exports = replaceNodeOrTokenAndSpacesBefore; 22 | -------------------------------------------------------------------------------- /rules/ast/literal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isLiteral(node, value) { 4 | if (node?.type !== 'Literal') { 5 | return false; 6 | } 7 | 8 | if (value === null) { 9 | return node.raw === 'null'; 10 | } 11 | 12 | return node.value === value; 13 | } 14 | 15 | const isStringLiteral = node => node?.type === 'Literal' && typeof node.value === 'string'; 16 | const isNumberLiteral = node => node.type === 'Literal' && typeof node.value === 'number'; 17 | const isRegexLiteral = node => node.type === 'Literal' && Boolean(node.regex); 18 | // eslint-disable-next-line unicorn/no-null 19 | const isNullLiteral = node => isLiteral(node, null); 20 | const isBigIntLiteral = node => node.type === 'Literal' && Boolean(node.bigint); 21 | 22 | module.exports = { 23 | isLiteral, 24 | isStringLiteral, 25 | isNumberLiteral, 26 | isBigIntLiteral, 27 | isNullLiteral, 28 | isRegexLiteral, 29 | }; 30 | -------------------------------------------------------------------------------- /docs/rules/prefer-set-size.md: -------------------------------------------------------------------------------- 1 | # Prefer using `Set#size` instead of `Array#length` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | Prefer using `Set#size` directly instead of first converting it to an array and then using its `.length` property. 11 | 12 | ## Fail 13 | 14 | ```js 15 | function isUnique(array) { 16 | return [...new Set(array)].length === array.length; 17 | } 18 | ``` 19 | 20 | ## Pass 21 | 22 | ```js 23 | function isUnique(array) { 24 | return new Set(array).size === array.length; 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /test/unit/get-documentation-url.mjs: -------------------------------------------------------------------------------- 1 | import {createRequire} from 'node:module'; 2 | import url from 'node:url'; 3 | import test from 'ava'; 4 | import getDocumentationUrl from '../../rules/utils/get-documentation-url.js'; 5 | 6 | const require = createRequire(import.meta.url); 7 | const packageJson = require('../../package.json'); 8 | 9 | const filename = url.fileURLToPath(import.meta.url).replace(/\.mjs$/, '.js'); 10 | 11 | test('returns the URL of the a named rule\'s documentation', t => { 12 | const url = `https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v${packageJson.version}/docs/rules/foo.md`; 13 | t.is(getDocumentationUrl('foo.js'), url); 14 | }); 15 | 16 | test('determines the rule name from the file', t => { 17 | const url = `https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v${packageJson.version}/docs/rules/get-documentation-url.md`; 18 | t.is(getDocumentationUrl(filename), url); 19 | }); 20 | -------------------------------------------------------------------------------- /rules/utils/get-call-expression-arguments-text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const getCallExpressionTokens = require('./get-call-expression-tokens.js'); 3 | 4 | /** @typedef {import('estree').CallExpression} CallExpression */ 5 | 6 | /** 7 | Get the text of the arguments list of `CallExpression`. 8 | 9 | @param {import('eslint').SourceCode} sourceCode - The source code object. 10 | @param {CallExpression} callExpression - The `CallExpression` node. 11 | @param {SourceCode} sourceCode - The source code object. 12 | @returns {string} 13 | */ 14 | function getCallExpressionArgumentsText(sourceCode, callExpression) { 15 | const { 16 | openingParenthesisToken, 17 | closingParenthesisToken, 18 | } = getCallExpressionTokens(sourceCode, callExpression); 19 | 20 | return sourceCode.text.slice( 21 | openingParenthesisToken.range[1], 22 | closingParenthesisToken.range[0], 23 | ); 24 | } 25 | 26 | module.exports = getCallExpressionArgumentsText; 27 | -------------------------------------------------------------------------------- /test/snapshots/prefer-blob-reading-methods.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/prefer-blob-reading-methods.mjs` 2 | 3 | The actual snapshot is saved in `prefer-blob-reading-methods.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): fileReader.readAsArrayBuffer(blob) 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | fileReader.readAsArrayBuffer(blob)␊ 13 | ` 14 | 15 | > Error 1/1 16 | 17 | `␊ 18 | > 1 | fileReader.readAsArrayBuffer(blob)␊ 19 | | ^^^^^^^^^^^^^^^^^ Prefer \`Blob#arrayBuffer()\` over \`FileReader#readAsArrayBuffer(blob)\`.␊ 20 | ` 21 | 22 | ## invalid(2): fileReader.readAsText(blob) 23 | 24 | > Input 25 | 26 | `␊ 27 | 1 | fileReader.readAsText(blob)␊ 28 | ` 29 | 30 | > Error 1/1 31 | 32 | `␊ 33 | > 1 | fileReader.readAsText(blob)␊ 34 | | ^^^^^^^^^^ Prefer \`Blob#text()\` over \`FileReader#readAsText(blob)\`.␊ 35 | ` 36 | -------------------------------------------------------------------------------- /docs/rules/no-hex-escape.md: -------------------------------------------------------------------------------- 1 | # Enforce the use of Unicode escapes instead of hexadecimal escapes 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | Enforces a convention of using [Unicode escapes](https://mathiasbynens.be/notes/javascript-escapes#unicode) instead of [hexadecimal escapes](https://mathiasbynens.be/notes/javascript-escapes#hexadecimal) for consistency and clarity. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const foo = '\x1B'; 16 | const foo = `\x1B${bar}`; 17 | ``` 18 | 19 | ## Pass 20 | 21 | ```js 22 | const foo = '\u001B'; 23 | const foo = `\u001B${bar}`; 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/rules/escape-case.md: -------------------------------------------------------------------------------- 1 | # Require escape sequences to use uppercase values 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | Enforces defining escape sequence values with uppercase characters rather than lowercase ones. This promotes readability by making the escaped value more distinguishable from the identifier. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const foo = '\xa9'; 16 | const foo = '\ud834'; 17 | const foo = '\u{1d306}'; 18 | const foo = '\ca'; 19 | ``` 20 | 21 | ## Pass 22 | 23 | ```js 24 | const foo = '\xA9'; 25 | const foo = '\uD834'; 26 | const foo = '\u{1D306}'; 27 | const foo = '\cA'; 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/rules/no-unreadable-iife.md: -------------------------------------------------------------------------------- 1 | # Disallow unreadable IIFEs 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) with parenthesized arrow function body is considered unreadable. 9 | 10 | ## Fail 11 | 12 | ```js 13 | const foo = (bar => (bar ? bar.baz : baz))(getBar()); 14 | ``` 15 | 16 | ```js 17 | const foo = ((bar, baz) => ({bar, baz}))(bar, baz); 18 | ``` 19 | 20 | ## Pass 21 | 22 | ```js 23 | const bar = getBar(); 24 | const foo = bar ? bar.baz : baz; 25 | ``` 26 | 27 | ```js 28 | const getBaz = bar => (bar ? bar.baz : baz); 29 | const foo = getBaz(getBar()); 30 | ``` 31 | 32 | ```js 33 | const foo = (bar => { 34 | return bar ? bar.baz : baz; 35 | })(getBar()); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/rules/no-unnecessary-await.md: -------------------------------------------------------------------------------- 1 | # Disallow awaiting non-promise values 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | The [`await` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) should only be used on [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) values. 11 | 12 | ## Fail 13 | 14 | ```js 15 | await await promise; 16 | ``` 17 | 18 | ```js 19 | await [promise1, promise2]; 20 | ``` 21 | 22 | ## Pass 23 | 24 | ```js 25 | await promise; 26 | ``` 27 | 28 | ```js 29 | await Promise.allSettled([promise1, promise2]); 30 | ``` 31 | -------------------------------------------------------------------------------- /test/snapshots/template-indent.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/template-indent.mjs` 2 | 3 | The actual snapshot is saved in `template-indent.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): expect(foo).toMatchInlineSnapshot(` one three ` ) 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | expect(foo).toMatchInlineSnapshot(\`␊ 13 | 2 | one␊ 14 | 3 | three␊ 15 | 4 | \`␊ 16 | 5 | )␊ 17 | ` 18 | 19 | > Output 20 | 21 | `␊ 22 | 1 | expect(foo).toMatchInlineSnapshot(\`␊ 23 | 2 | one␊ 24 | 3 | three␊ 25 | 4 | \`␊ 26 | 5 | )␊ 27 | ` 28 | 29 | > Error 1/1 30 | 31 | `␊ 32 | > 1 | expect(foo).toMatchInlineSnapshot(\`␊ 33 | | ^␊ 34 | > 2 | one␊ 35 | | ^^^^^^^␊ 36 | > 3 | three␊ 37 | | ^^^^^^^␊ 38 | > 4 | \`␊ 39 | | ^^^^^^^^ Templates should be properly indented.␊ 40 | 5 | )␊ 41 | ` 42 | -------------------------------------------------------------------------------- /docs/rules/throw-new-error.md: -------------------------------------------------------------------------------- 1 | # Require `new` when creating an error 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | While it's possible to create a new error without using the `new` keyword, it's better to be explicit. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const error = Error('unicorn'); 16 | ``` 17 | 18 | ```js 19 | throw TypeError('unicorn'); 20 | ``` 21 | 22 | ```js 23 | throw lib.TypeError('unicorn'); 24 | ``` 25 | 26 | ## Pass 27 | 28 | ```js 29 | const error = new Error('unicorn'); 30 | ``` 31 | 32 | ```js 33 | throw new TypeError('unicorn'); 34 | ``` 35 | 36 | ```js 37 | throw new lib.TypeError('unicorn'); 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/rules/prefer-string-raw.md: -------------------------------------------------------------------------------- 1 | # Prefer using the `String.raw` tag to avoid escaping `\` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | [`String.raw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw) can be used to avoid escaping `\`. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const file = "C:\\windows\\style\\path\\to\\file.js"; 16 | ``` 17 | 18 | ```js 19 | const regexp = new RegExp('foo\\.bar'); 20 | ``` 21 | 22 | ## Pass 23 | 24 | ```js 25 | const file = String.raw`C:\windows\style\path\to\file.js`; 26 | ``` 27 | 28 | ```js 29 | const regexp = new RegExp(String.raw`foo\.bar`); 30 | ``` 31 | -------------------------------------------------------------------------------- /test/no-negation-in-equality-check.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | '!foo instanceof bar', 9 | '+foo === bar', 10 | '!(foo === bar)', 11 | '!!foo === bar', 12 | '!!!foo === bar', 13 | // We are not checking right side 14 | 'foo === !bar', 15 | ], 16 | invalid: [ 17 | '!foo === bar', 18 | '!foo !== bar', 19 | '!foo == bar', 20 | '!foo != bar', 21 | outdent` 22 | function x() { 23 | return!foo === bar; 24 | } 25 | `, 26 | outdent` 27 | function x() { 28 | return! 29 | foo === bar; 30 | throw! 31 | foo === bar; 32 | } 33 | `, 34 | outdent` 35 | foo 36 | !(a) === b 37 | `, 38 | outdent` 39 | foo 40 | ![a, b].join('') === c 41 | `, 42 | outdent` 43 | foo 44 | ! [a, b].join('') === c 45 | `, 46 | outdent` 47 | foo 48 | !/* comment */[a, b].join('') === c 49 | `, 50 | ], 51 | }); 52 | -------------------------------------------------------------------------------- /docs/rules/no-length-as-slice-end.md: -------------------------------------------------------------------------------- 1 | # Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | When calling `{String,Array,TypedArray}#slice(start, end)`, omitting the `end` argument defaults it to the object's `.length`. Passing it explicitly is unnecessary. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const foo = string.slice(1, string.length); 16 | ``` 17 | 18 | ```js 19 | const foo = array.slice(1, array.length); 20 | ``` 21 | 22 | ## Pass 23 | 24 | ```js 25 | const foo = string.slice(1); 26 | ``` 27 | 28 | ```js 29 | const foo = bar.slice(1, baz.length); 30 | ``` 31 | -------------------------------------------------------------------------------- /test/no-unreadable-iife.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | 'const foo = (bar => bar)();', 9 | outdent` 10 | const foo = (() => { 11 | return a ? b : c 12 | })(); 13 | `, 14 | ], 15 | invalid: [ 16 | 'const foo = (() => (a ? b : c))();', 17 | outdent` 18 | const foo = (() => ( 19 | a ? b : c 20 | ))(); 21 | `, 22 | outdent` 23 | const foo = ( 24 | () => ( 25 | a ? b : c 26 | ) 27 | )(); 28 | `, 29 | outdent` 30 | const foo = (() => ( 31 | a, b 32 | ))(); 33 | `, 34 | outdent` 35 | const foo = (() => ({ 36 | a: b, 37 | }))(); 38 | `, 39 | 'const foo = (bar => (bar))();', 40 | outdent` 41 | (async () => ({ 42 | bar, 43 | }))(); 44 | `, 45 | outdent` 46 | const foo = (async (bar) => ({ 47 | bar: await baz(), 48 | }))(); 49 | `, 50 | '(async () => (( {bar} )))();', 51 | ], 52 | }); 53 | -------------------------------------------------------------------------------- /scripts/internal-rules/prefer-negative-boolean-attribute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('node:path'); 3 | 4 | const messageId = path.basename(__filename, '.js'); 5 | 6 | const shouldReport = (string, value) => { 7 | const index = string.indexOf(`=${value}]`); 8 | 9 | if (index === -1) { 10 | return false; 11 | } 12 | 13 | return string[index - 1] !== '!'; 14 | }; 15 | 16 | module.exports = { 17 | create(context) { 18 | return { 19 | 'TemplateElement, Literal'(node) { 20 | const string = node.value; 21 | if (typeof string !== 'string') { 22 | return; 23 | } 24 | 25 | for (const value of [true, false]) { 26 | if (shouldReport(string, value)) { 27 | context.report({ 28 | node, 29 | messageId, 30 | data: { 31 | preferred: String(!value), 32 | }, 33 | }); 34 | } 35 | } 36 | }, 37 | }; 38 | }, 39 | meta: { 40 | messages: { 41 | [messageId]: 'Prefer use `[…!={{preferred}}]` in esquery selector.', 42 | }, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /test/prefer-code-point.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | 3 | const {test} = getTester(import.meta); 4 | 5 | test.snapshot({ 6 | valid: [ 7 | '"🦄".codePointAt(0)', 8 | 'foo.charCodeAt', 9 | 'new foo.charCodeAt', 10 | 'charCodeAt(0)', 11 | 'foo.charCodeAt?.(0)', 12 | 'foo?.charCodeAt(0)', 13 | 'foo[charCodeAt](0)', 14 | 'foo["charCodeAt"](0)', 15 | 'foo.notCharCodeAt(0)', 16 | 17 | 'String.fromCodePoint(0x1f984)', 18 | 'String.fromCodePoint', 19 | 'new String.fromCodePoint', 20 | 'fromCodePoint(foo)', 21 | 'String.fromCodePoint?.(foo)', 22 | 'String?.fromCodePoint(foo)', 23 | 'window.String.fromCodePoint(foo)', 24 | 'String[fromCodePoint](foo)', 25 | 'String["fromCodePoint"](foo)', 26 | 'String.notFromCodePoint(foo)', 27 | 'NotString.fromCodePoint(foo)', 28 | ], 29 | invalid: [ 30 | 'string.charCodeAt(index)', 31 | '(( (( string )).charCodeAt( ((index)), )))', 32 | 'String.fromCharCode( code )', 33 | '(( (( String )).fromCharCode( ((code)), ) ))', 34 | ], 35 | }); 36 | -------------------------------------------------------------------------------- /rules/no-this-assignment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const MESSAGE_ID = 'no-this-assignment'; 3 | const messages = { 4 | [MESSAGE_ID]: 'Do not assign `this` to `{{name}}`.', 5 | }; 6 | 7 | function getProblem(variableNode, valueNode) { 8 | if ( 9 | variableNode.type !== 'Identifier' 10 | || valueNode?.type !== 'ThisExpression' 11 | ) { 12 | return; 13 | } 14 | 15 | return { 16 | node: valueNode.parent, 17 | data: {name: variableNode.name}, 18 | messageId: MESSAGE_ID, 19 | }; 20 | } 21 | 22 | /** @param {import('eslint').Rule.RuleContext} context */ 23 | const create = context => { 24 | context.on('VariableDeclarator', node => getProblem(node.id, node.init)); 25 | context.on('AssignmentExpression', node => getProblem(node.left, node.right)); 26 | }; 27 | 28 | /** @type {import('eslint').Rule.RuleModule} */ 29 | module.exports = { 30 | create, 31 | meta: { 32 | type: 'suggestion', 33 | docs: { 34 | description: 'Disallow assigning `this` to a variable.', 35 | recommended: true, 36 | }, 37 | messages, 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /rules/prefer-array-index-of.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const simpleArraySearchRule = require('./shared/simple-array-search-rule.js'); 3 | 4 | const indexOfOverFindIndexRule = simpleArraySearchRule({ 5 | method: 'findIndex', 6 | replacement: 'indexOf', 7 | }); 8 | 9 | const lastIndexOfOverFindLastIndexRule = simpleArraySearchRule({ 10 | method: 'findLastIndex', 11 | replacement: 'lastIndexOf', 12 | }); 13 | 14 | /** @type {import('eslint').Rule.RuleModule} */ 15 | module.exports = { 16 | create(context) { 17 | indexOfOverFindIndexRule.listen(context); 18 | lastIndexOfOverFindLastIndexRule.listen(context); 19 | }, 20 | meta: { 21 | type: 'suggestion', 22 | docs: { 23 | description: 'Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item.', 24 | recommended: true, 25 | }, 26 | fixable: 'code', 27 | hasSuggestions: true, 28 | messages: { 29 | ...indexOfOverFindIndexRule.messages, 30 | ...lastIndexOfOverFindLastIndexRule.messages, 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /test/no-length-as-slice-end.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | 3 | const {test} = getTester(import.meta); 4 | 5 | test.snapshot({ 6 | valid: [ 7 | 'foo.slice?.(1, foo.length)', 8 | 'foo.slice(foo.length, 1)', 9 | 'foo.slice()', 10 | 'foo.slice(1)', 11 | 'foo.slice(1, foo.length - 1)', 12 | 'foo.slice(1, foo.length, extraArgument)', 13 | 'foo.slice(...[1], foo.length)', 14 | 'foo.notSlice(1, foo.length)', 15 | 'new foo.slice(1, foo.length)', 16 | 'slice(1, foo.length)', 17 | 'foo.slice(1, foo.notLength)', 18 | 'foo.slice(1, length)', 19 | 'foo[slice](1, foo.length)', 20 | 'foo.slice(1, foo[length])', 21 | 'foo.slice(1, bar.length)', 22 | // `isSameReference` consider they are not the same reference 23 | 'foo().slice(1, foo().length)', 24 | ], 25 | invalid: [ 26 | 'foo.slice(1, foo.length)', 27 | 'foo?.slice(1, foo.length)', 28 | 'foo.slice(1, foo.length,)', 29 | 'foo.slice(1, (( foo.length )))', 30 | 'foo.slice(1, foo?.length)', 31 | 'foo?.slice(1, foo?.length)', 32 | ], 33 | }); 34 | -------------------------------------------------------------------------------- /rules/utils/is-shadowed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Finds the eslint-scope reference in the given scope. 5 | * @param {Object} scope The scope to search. 6 | * @param {ASTNode} node The identifier node. 7 | * @returns {Reference|undefined} Returns the found reference or null if none were found. 8 | */ 9 | function findReference(scope, node) { 10 | const references = scope.references 11 | .filter(reference => reference.identifier === node); 12 | 13 | if (references.length === 1) { 14 | return references[0]; 15 | } 16 | } 17 | 18 | /** 19 | * Checks if the given identifier node is shadowed in the given scope. 20 | * @param {Object} scope The current scope. 21 | * @param {string} node The identifier node to check 22 | * @returns {boolean} Whether or not the name is shadowed. 23 | */ 24 | function isShadowed(scope, node) { 25 | const reference = findReference(scope, node); 26 | 27 | return ( 28 | Boolean(reference?.resolved) 29 | && reference.resolved.defs.length > 0 30 | ); 31 | } 32 | 33 | module.exports = isShadowed; 34 | -------------------------------------------------------------------------------- /test/require-post-message-target-origin.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | 3 | const {test} = getTester(import.meta); 4 | 5 | test.snapshot({ 6 | valid: [ 7 | 'window.postMessage(message, targetOrigin)', 8 | 'postMessage(message)', 9 | 'window.postMessage', 10 | 'window.postMessage()', 11 | 'window.postMessage(message, targetOrigin, transfer)', 12 | 'window.postMessage(...message)', 13 | 'window[postMessage](message)', 14 | 'window["postMessage"](message)', 15 | 'window.notPostMessage(message)', 16 | 'window.postMessage?.(message)', 17 | 'window?.postMessage(message)', 18 | ], 19 | invalid: [ 20 | 'window.postMessage(message)', 21 | 'self.postMessage(message)', 22 | 'globalThis.postMessage(message)', 23 | 'foo.postMessage(message )', 24 | 'foo.postMessage( ((message)) )', 25 | 'foo.postMessage(message,)', 26 | 'foo.postMessage(message , )', 27 | 'foo.window.postMessage(message)', 28 | 'document.defaultView.postMessage(message)', 29 | 'getWindow().postMessage(message)', 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /test/snapshots/numeric-separators-style.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/numeric-separators-style.mjs` 2 | 3 | The actual snapshot is saved in `numeric-separators-style.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): console.log(0XdeEdBeeFn) 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | console.log(0XdeEdBeeFn)␊ 13 | ` 14 | 15 | > Output 16 | 17 | `␊ 18 | 1 | console.log(0Xde_Ed_Be_eFn)␊ 19 | ` 20 | 21 | > Error 1/1 22 | 23 | `␊ 24 | > 1 | console.log(0XdeEdBeeFn)␊ 25 | | ^^^^^^^^^^^ Invalid group length in numeric value.␊ 26 | ` 27 | 28 | ## invalid(2): const foo = 12345678..toString() 29 | 30 | > Input 31 | 32 | `␊ 33 | 1 | const foo = 12345678..toString()␊ 34 | ` 35 | 36 | > Output 37 | 38 | `␊ 39 | 1 | const foo = 12_345_678..toString()␊ 40 | ` 41 | 42 | > Error 1/1 43 | 44 | `␊ 45 | > 1 | const foo = 12345678..toString()␊ 46 | | ^^^^^^^^^ Invalid group length in numeric value.␊ 47 | ` 48 | -------------------------------------------------------------------------------- /docs/rules/no-useless-fallback-in-spread.md: -------------------------------------------------------------------------------- 1 | # Disallow useless fallback when spreading in object literals 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | Spreading [falsy values](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) in object literals won't add any unexpected properties, so it's unnecessary to add an empty object as fallback. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const object = {...(foo || {})}; 16 | ``` 17 | 18 | ```js 19 | const object = {...(foo ?? {})}; 20 | ``` 21 | 22 | ## Pass 23 | 24 | ```js 25 | const object = {...foo}; 26 | ``` 27 | 28 | ```js 29 | const object = {...(foo && {})}; 30 | ``` 31 | 32 | ```js 33 | const array = [...(foo || [])]; 34 | ``` 35 | -------------------------------------------------------------------------------- /test/prefer-logical-operator-over-ternary.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | 'foo ? foo1 : bar;', 9 | 'foo.bar ? foo.bar1 : foo.baz', 10 | 'foo.bar ? foo1.bar : foo.baz', 11 | '++foo ? ++foo : bar;', 12 | 13 | // Not checking 14 | '!!bar ? foo : bar;', 15 | ], 16 | invalid: [ 17 | 'foo ? foo : bar;', 18 | 'foo.bar ? foo.bar : foo.baz', 19 | 'foo?.bar ? foo.bar : baz', 20 | '!bar ? foo : bar;', 21 | '!!bar ? foo : !bar;', 22 | 23 | 'foo() ? foo() : bar', 24 | 25 | // Children parentheses 26 | 'foo ? foo : a && b', 27 | 'foo ? foo : a || b', 28 | 'foo ? foo : a ?? b', 29 | 'a && b ? a && b : bar', 30 | 'a || b ? a || b : bar', 31 | 'a ?? b ? a ?? b : bar', 32 | 'foo ? foo : await a', 33 | 'await a ? await a : foo', 34 | 35 | // ASI 36 | outdent` 37 | const foo = [] 38 | !+a ? b : +a 39 | `, 40 | outdent` 41 | const foo = [] 42 | a && b ? a && b : 1 43 | `, 44 | ], 45 | }); 46 | -------------------------------------------------------------------------------- /test/snapshots/no-this-assignment.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/no-this-assignment.mjs` 2 | 3 | The actual snapshot is saved in `no-this-assignment.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): const foo = this; 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | const foo = this;␊ 13 | ` 14 | 15 | > Error 1/1 16 | 17 | `␊ 18 | > 1 | const foo = this;␊ 19 | | ^^^^^^^^^^ Do not assign \`this\` to \`foo\`.␊ 20 | ` 21 | 22 | ## invalid(2): let foo;foo = this; 23 | 24 | > Input 25 | 26 | `␊ 27 | 1 | let foo;foo = this;␊ 28 | ` 29 | 30 | > Error 1/1 31 | 32 | `␊ 33 | > 1 | let foo;foo = this;␊ 34 | | ^^^^^^^^^^ Do not assign \`this\` to \`foo\`.␊ 35 | ` 36 | 37 | ## invalid(3): var foo = bar, baz = this; 38 | 39 | > Input 40 | 41 | `␊ 42 | 1 | var foo = bar, baz = this;␊ 43 | ` 44 | 45 | > Error 1/1 46 | 47 | `␊ 48 | > 1 | var foo = bar, baz = this;␊ 49 | | ^^^^^^^^^^ Do not assign \`this\` to \`baz\`.␊ 50 | ` 51 | -------------------------------------------------------------------------------- /docs/rules/consistent-empty-array-spread.md: -------------------------------------------------------------------------------- 1 | # Prefer consistent types when spreading a ternary in an array literal 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | When spreading a ternary in an array, we can use both `[]` and `''` as fallbacks, but it's better to have consistent types in both branches. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const array = [ 16 | a, 17 | ...(foo ? [b, c] : ''), 18 | ]; 19 | ``` 20 | 21 | ```js 22 | const array = [ 23 | a, 24 | ...(foo ? 'bc' : []), 25 | ]; 26 | ``` 27 | 28 | ## Pass 29 | 30 | ```js 31 | const array = [ 32 | a, 33 | ...(foo ? [b, c] : []), 34 | ]; 35 | ``` 36 | 37 | ```js 38 | const array = [ 39 | a, 40 | ...(foo ? 'bc' : ''), 41 | ]; 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/rules/no-empty-file.md: -------------------------------------------------------------------------------- 1 | # Disallow empty files 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | Meaningless files clutter a codebase. 9 | 10 | Disallow any files only containing the following: 11 | 12 | - Whitespace 13 | - Comments 14 | - Directives 15 | - Empty statements 16 | - Empty block statements 17 | - Hashbang 18 | 19 | ## Fail 20 | 21 | ```js 22 | 23 | ``` 24 | 25 | ```js 26 | // Comment 27 | ``` 28 | 29 | ```js 30 | /* Comment */ 31 | ``` 32 | 33 | ```js 34 | 'use strict'; 35 | ``` 36 | 37 | ```js 38 | ; 39 | ``` 40 | 41 | ```js 42 | { 43 | } 44 | ``` 45 | 46 | ```js 47 | #!/usr/bin/env node 48 | ``` 49 | 50 | ## Pass 51 | 52 | ```js 53 | const x = 0; 54 | ``` 55 | 56 | ```js 57 | 'use strict'; 58 | const x = 0; 59 | ``` 60 | 61 | ```js 62 | ;; 63 | const x = 0; 64 | ``` 65 | 66 | ```js 67 | { 68 | const x = 0; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/rules/prefer-dom-node-append.md: -------------------------------------------------------------------------------- 1 | # Prefer `Node#append()` over `Node#appendChild()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | Enforces the use of, for example, `document.body.append(div);` over `document.body.appendChild(div);` for DOM nodes. There are [some advantages of using `Node#append()`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append), like the ability to append multiple nodes and to append both [`DOMString`](https://developer.mozilla.org/en-US/docs/Web/API/DOMString) and DOM node objects. 11 | 12 | ## Fail 13 | 14 | ```js 15 | foo.appendChild(bar); 16 | ``` 17 | 18 | ## Pass 19 | 20 | ```js 21 | foo.append(bar); 22 | foo.append('bar'); 23 | foo.append(bar, 'baz'); 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/rules/error-message.md: -------------------------------------------------------------------------------- 1 | # Enforce passing a `message` value when creating a built-in error 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | This rule enforces a `message` value to be passed in when creating an instance of a built-in [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object, which leads to more readable and debuggable code. 9 | 10 | ## Fail 11 | 12 | ```js 13 | throw Error(); 14 | ``` 15 | 16 | ```js 17 | throw Error(''); 18 | ``` 19 | 20 | ```js 21 | throw new TypeError(); 22 | ``` 23 | 24 | ```js 25 | const error = new AggregateError(errors); 26 | ``` 27 | 28 | ## Pass 29 | 30 | ```js 31 | throw Error('Unexpected property.'); 32 | ``` 33 | 34 | ```js 35 | throw new TypeError('Array expected.'); 36 | ``` 37 | 38 | ```js 39 | const error = new AggregateError(errors, 'Promises rejected.'); 40 | ``` 41 | -------------------------------------------------------------------------------- /rules/utils/is-new-expression-with-parentheses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isOpeningParenToken, isClosingParenToken} = require('@eslint-community/eslint-utils'); 4 | 5 | /** 6 | Determine if a constructor function is newed-up with parens. 7 | 8 | @param {Node} node - The `NewExpression` node to be checked. 9 | @param {SourceCode} sourceCode - The source code object. 10 | @returns {boolean} True if the constructor is called with parens. 11 | 12 | Copied from https://github.com/eslint/eslint/blob/cc4871369645c3409dc56ded7a555af8a9f63d51/lib/rules/no-extra-parens.js#L252 13 | */ 14 | function isNewExpressionWithParentheses(node, sourceCode) { 15 | if (node.arguments.length > 0) { 16 | return true; 17 | } 18 | 19 | const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2); 20 | // The expression should end with its own parens, for example, `new new Foo()` is not a new expression with parens. 21 | return isOpeningParenToken(penultimateToken) 22 | && isClosingParenToken(lastToken) 23 | && node.callee.range[1] < node.range[1]; 24 | } 25 | 26 | module.exports = isNewExpressionWithParentheses; 27 | -------------------------------------------------------------------------------- /rules/fix/fix-space-around-keywords.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {getParenthesizedRange} = require('../utils/parentheses.js'); 3 | 4 | const isProblematicToken = ({type, value}) => ( 5 | (type === 'Keyword' && /^[a-z]*$/.test(value)) 6 | // ForOfStatement 7 | || (type === 'Identifier' && value === 'of') 8 | // AwaitExpression 9 | || (type === 'Identifier' && value === 'await') 10 | ); 11 | 12 | function * fixSpaceAroundKeyword(fixer, node, sourceCode) { 13 | const range = getParenthesizedRange(node, sourceCode); 14 | const tokenBefore = sourceCode.getTokenBefore({range}, {includeComments: true}); 15 | 16 | if ( 17 | tokenBefore 18 | && range[0] === tokenBefore.range[1] 19 | && isProblematicToken(tokenBefore) 20 | ) { 21 | yield fixer.insertTextAfter(tokenBefore, ' '); 22 | } 23 | 24 | const tokenAfter = sourceCode.getTokenAfter({range}, {includeComments: true}); 25 | 26 | if ( 27 | tokenAfter 28 | && range[1] === tokenAfter.range[0] 29 | && isProblematicToken(tokenAfter) 30 | ) { 31 | yield fixer.insertTextBefore(tokenAfter, ' '); 32 | } 33 | } 34 | 35 | module.exports = fixSpaceAroundKeyword; 36 | -------------------------------------------------------------------------------- /rules/fix/remove-argument.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isCommaToken} = require('@eslint-community/eslint-utils'); 3 | const {getParentheses} = require('../utils/parentheses.js'); 4 | 5 | function removeArgument(fixer, node, sourceCode) { 6 | const callExpression = node.parent; 7 | const index = callExpression.arguments.indexOf(node); 8 | const parentheses = getParentheses(node, sourceCode); 9 | const firstToken = parentheses[0] || node; 10 | const lastToken = parentheses.at(-1) || node; 11 | 12 | let [start] = firstToken.range; 13 | let [, end] = lastToken.range; 14 | 15 | if (index !== 0) { 16 | start = sourceCode.getTokenBefore(firstToken).range[0]; 17 | } 18 | 19 | // If the removed argument is the only argument, the trailing comma must be removed too 20 | /* c8 ignore start */ 21 | if (callExpression.arguments.length === 1) { 22 | const tokenAfter = sourceCode.getTokenBefore(lastToken); 23 | if (isCommaToken(tokenAfter)) { 24 | end = tokenAfter[1]; 25 | } 26 | } 27 | /* c8 ignore end */ 28 | 29 | return fixer.replaceTextRange([start, end], ''); 30 | } 31 | 32 | module.exports = removeArgument; 33 | -------------------------------------------------------------------------------- /docs/rules/no-static-only-class.md: -------------------------------------------------------------------------------- 1 | # Disallow classes that only have static members 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | A class with only static members could just be an object instead. 11 | 12 | ## Fail 13 | 14 | ```js 15 | class X { 16 | static foo = false; 17 | static bar() {}; 18 | } 19 | ``` 20 | 21 | ## Pass 22 | 23 | ```js 24 | const X = { 25 | foo: false, 26 | bar() {} 27 | }; 28 | ``` 29 | 30 | ```js 31 | class X { 32 | static foo = false; 33 | static bar() {}; 34 | 35 | constructor() {} 36 | } 37 | ``` 38 | 39 | ```js 40 | class X { 41 | static foo = false; 42 | static bar() {}; 43 | 44 | unicorn() {} 45 | } 46 | ``` 47 | 48 | ```js 49 | class X { 50 | static #foo = false; 51 | static bar() {} 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /docs/rules/no-magic-array-flat-depth.md: -------------------------------------------------------------------------------- 1 | # Disallow a magic number as the `depth` argument in `Array#flat(…).` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | When calling [`Array#flat(depth)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat), the depth argument should normally be `1` or `Infinity`, otherwise it should be a meaningful variable name or explained with a comment. 9 | 10 | ## Fail 11 | 12 | ```js 13 | const foo = array.flat(2); 14 | ``` 15 | 16 | ```js 17 | const foo = array.flat(99); 18 | ``` 19 | 20 | ## Pass 21 | 22 | ```js 23 | const foo = array.flat(); 24 | ``` 25 | 26 | ```js 27 | const foo = array.flat(Number.POSITIVE_INFINITY); 28 | ``` 29 | 30 | ```js 31 | const foo = array.flat(Infinity); 32 | ``` 33 | 34 | ```js 35 | const foo = array.flat(depth); 36 | ``` 37 | 38 | ```js 39 | const foo = array.flat(/* The depth is always 2 */ 2); 40 | ``` 41 | -------------------------------------------------------------------------------- /rules/shared/event-keys.js: -------------------------------------------------------------------------------- 1 | /* eslint sort-keys: ["error", "asc", {natural: true}] */ 2 | 'use strict'; 3 | // https://github.com/facebook/react/blob/b87aabd/packages/react-dom/src/events/getEventKey.js#L36 4 | // Only meta characters which can't be deciphered from `String.fromCharCode()` 5 | module.exports = { 6 | 8: 'Backspace', 7 | 9: 'Tab', 8 | 12: 'Clear', 9 | 13: 'Enter', 10 | 16: 'Shift', 11 | 17: 'Control', 12 | 18: 'Alt', 13 | 19: 'Pause', 14 | 20: 'CapsLock', 15 | 27: 'Escape', 16 | 32: ' ', 17 | 33: 'PageUp', 18 | 34: 'PageDown', 19 | 35: 'End', 20 | 36: 'Home', 21 | 37: 'ArrowLeft', 22 | 38: 'ArrowUp', 23 | 39: 'ArrowRight', 24 | 40: 'ArrowDown', 25 | 45: 'Insert', 26 | 46: 'Delete', 27 | 112: 'F1', 28 | 113: 'F2', 29 | 114: 'F3', 30 | 115: 'F4', 31 | 116: 'F5', 32 | 117: 'F6', 33 | 118: 'F7', 34 | 119: 'F8', 35 | 120: 'F9', 36 | 121: 'F10', 37 | 122: 'F11', 38 | 123: 'F12', 39 | 144: 'NumLock', 40 | 145: 'ScrollLock', 41 | 186: ';', 42 | 187: '=', 43 | 188: ',', 44 | 189: '-', 45 | 190: '.', 46 | 191: '/', 47 | 219: '[', 48 | 220: '\\', 49 | 221: ']', 50 | 222: '\'', 51 | 224: 'Meta', 52 | }; 53 | -------------------------------------------------------------------------------- /docs/rules/prefer-event-target.md: -------------------------------------------------------------------------------- 1 | # Prefer `EventTarget` over `EventEmitter` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | While [`EventEmitter`](https://nodejs.org/api/events.html#class-eventemitter) is only available in Node.js, [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) is also available in _Deno_ and browsers. 9 | 10 | This rule reduces the bundle size and makes your code more cross-platform friendly. 11 | 12 | See the [differences](https://nodejs.org/api/events.html#eventtarget-and-event-api) between `EventEmitter` and `EventTarget`. 13 | 14 | ## Fail 15 | 16 | ```js 17 | import {EventEmitter} from 'node:event'; 18 | 19 | class Foo extends EventEmitter {} 20 | ``` 21 | 22 | ```js 23 | const emitter = new EventEmitter(); 24 | ``` 25 | 26 | ## Pass 27 | 28 | ```js 29 | class Foo extends EventTarget {} 30 | ``` 31 | 32 | ```js 33 | const target = new EventTarget(); 34 | ``` 35 | -------------------------------------------------------------------------------- /rules/utils/assert-token.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ISSUE_LINK_PREFIX = 'https://github.com/sindresorhus/eslint-plugin-unicorn/issues/new?'; 4 | function assertToken(token, {test, expected, ruleId}) { 5 | if (test?.(token)) { 6 | return; 7 | } 8 | 9 | expected = Array.isArray(expected) ? expected : [expected]; 10 | expected = expected.map(expectedToken => typeof expectedToken === 'string' ? {value: expectedToken} : expectedToken); 11 | 12 | if ( 13 | !test 14 | && expected.some( 15 | expectedToken => 16 | Object.entries(expectedToken) 17 | .every(([key, value]) => token[key] === value), 18 | ) 19 | ) { 20 | return; 21 | } 22 | 23 | const actual = `'${JSON.stringify({value: token.value, type: token.type})}'`; 24 | expected = expected.map(expectedToken => `'${JSON.stringify(expectedToken)}'`).join(' or '); 25 | const title = `\`${ruleId}\`: Unexpected token ${actual}`; 26 | const issueLink = `${ISSUE_LINK_PREFIX}title=${encodeURIComponent(title)}`; 27 | const message = `Expected token ${expected}, got ${actual}.\nPlease open an issue at ${issueLink}.`; 28 | 29 | throw new Error(message); 30 | } 31 | 32 | module.exports = assertToken; 33 | -------------------------------------------------------------------------------- /docs/rules/no-await-in-promise-methods.md: -------------------------------------------------------------------------------- 1 | # Disallow using `await` in `Promise` method parameters 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Using `await` on promises passed as arguments to `Promise.all()`, `Promise.allSettled()`, `Promise.any()`, or `Promise.race()` is likely a mistake. 11 | 12 | ## Fail 13 | 14 | ```js 15 | Promise.all([await promise, anotherPromise]); 16 | 17 | Promise.allSettled([await promise, anotherPromise]); 18 | 19 | Promise.any([await promise, anotherPromise]); 20 | 21 | Promise.race([await promise, anotherPromise]); 22 | ``` 23 | 24 | ## Pass 25 | 26 | ```js 27 | Promise.all([promise, anotherPromise]); 28 | 29 | Promise.allSettled([promise, anotherPromise]); 30 | 31 | Promise.any([promise, anotherPromise]); 32 | 33 | Promise.race([promise, anotherPromise]); 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/rules/prefer-dom-node-remove.md: -------------------------------------------------------------------------------- 1 | # Prefer `childNode.remove()` over `parentNode.removeChild(childNode)` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Enforces the use of, for example, `child.remove();` over `child.parentNode.removeChild(child);`. The DOM function [`Node#remove()`](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove) is preferred over the indirect removal of an object with [`Node#removeChild()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild). 11 | 12 | ## Fail 13 | 14 | ```js 15 | parentNode.removeChild(foo); 16 | parentNode.removeChild(this); 17 | ``` 18 | 19 | ## Pass 20 | 21 | ```js 22 | foo.remove(); 23 | this.remove(); 24 | ``` 25 | -------------------------------------------------------------------------------- /test/no-this-assignment.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | 'const {property} = this;', 9 | 'const property = this.property;', 10 | 'const [element] = this;', 11 | 'const element = this[0];', 12 | '([element] = this);', 13 | 'element = this[0];', 14 | 'property = this.property;', 15 | 'const [element] = [this];', 16 | '([element] = [this]);', 17 | 'const {property} = {property: this};', 18 | '({property} = {property: this});', 19 | 'const self = true && this;', 20 | 'const self = false || this;', 21 | 'const self = false ?? this;', 22 | 'foo.bar = this;', 23 | 'function foo(a = this) {}', 24 | 'function foo({a = this}) {}', 25 | 'function foo([a = this]) {}', 26 | ], 27 | invalid: [ 28 | 'const foo = this;', 29 | 'let foo;foo = this;', 30 | 'var foo = bar, baz = this;', 31 | ], 32 | }); 33 | 34 | test.babel({ 35 | valid: [ 36 | outdent` 37 | class A { 38 | foo = this; 39 | } 40 | `, 41 | outdent` 42 | class A { 43 | static foo = this; 44 | } 45 | `, 46 | ], 47 | invalid: [], 48 | }); 49 | -------------------------------------------------------------------------------- /rules/utils/is-node-value-not-function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isUndefined, isCallExpression, isMethodCall} = require('../ast/index.js'); 3 | 4 | // AST Types: 5 | // https://github.com/eslint/espree/blob/master/lib/ast-node-types.js#L18 6 | // Only types possible to be `argument` are listed 7 | const impossibleNodeTypes = new Set([ 8 | 'ArrayExpression', 9 | 'BinaryExpression', 10 | 'ClassExpression', 11 | 'Literal', 12 | 'ObjectExpression', 13 | 'TemplateLiteral', 14 | 'UnaryExpression', 15 | 'UpdateExpression', 16 | ]); 17 | 18 | // Technically these nodes could be a function, but most likely not 19 | const mostLikelyNotNodeTypes = new Set([ 20 | 'AssignmentExpression', 21 | 'AwaitExpression', 22 | 'NewExpression', 23 | 'TaggedTemplateExpression', 24 | 'ThisExpression', 25 | ]); 26 | 27 | const isNodeValueNotFunction = node => ( 28 | impossibleNodeTypes.has(node.type) 29 | || mostLikelyNotNodeTypes.has(node.type) 30 | || isUndefined(node) 31 | || ( 32 | isCallExpression(node) 33 | && !(isMethodCall(node, { 34 | method: 'bind', 35 | optionalCall: false, 36 | optionalMember: false, 37 | })) 38 | ) 39 | ); 40 | 41 | module.exports = isNodeValueNotFunction; 42 | -------------------------------------------------------------------------------- /rules/utils/should-add-parentheses-to-new-expression-callee.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Copied from https://github.com/eslint/eslint/blob/aa87329d919f569404ca573b439934552006572f/lib/rules/no-extra-parens.js#L448 4 | /** 5 | Check if a member expression contains a call expression. 6 | 7 | @param {ASTNode} node - The `MemberExpression` node to evaluate. 8 | @returns {boolean} true if found, false if not. 9 | */ 10 | function doesMemberExpressionContainCallExpression(node) { 11 | let currentNode = node.object; 12 | let currentNodeType = node.object.type; 13 | 14 | while (currentNodeType === 'MemberExpression') { 15 | currentNode = currentNode.object; 16 | currentNodeType = currentNode.type; 17 | } 18 | 19 | return currentNodeType === 'CallExpression'; 20 | } 21 | 22 | /** 23 | Check if parentheses should be added to a `node` when it's used as `callee` of `NewExpression`. 24 | 25 | @param {Node} node - The AST node to check. 26 | @returns {boolean} 27 | */ 28 | function shouldAddParenthesesToNewExpressionCallee(node) { 29 | return node.type === 'MemberExpression' && doesMemberExpressionContainCallExpression(node); 30 | } 31 | 32 | module.exports = shouldAddParenthesesToNewExpressionCallee; 33 | -------------------------------------------------------------------------------- /test/prefer-string-trim-start-end.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | 'foo.trimStart()', 9 | 'foo.trimStart?.()', 10 | 'foo.trimEnd()', 11 | // Not `CallExpression` 12 | 'new foo.trimLeft();', 13 | // Not `MemberExpression` 14 | 'trimLeft();', 15 | // `callee.property` is not a `Identifier` 16 | 'foo[\'trimLeft\']();', 17 | // Computed 18 | 'foo[trimLeft]();', 19 | // Not `trimLeft`/`trimRight` 20 | 'foo.bar();', 21 | // More argument(s) 22 | 'foo.trimLeft(extra);', 23 | 'foo.trimLeft(...argumentsArray)', 24 | // `trimLeft` is in argument 25 | 'foo.bar(trimLeft)', 26 | 'foo.bar(foo.trimLeft)', 27 | // `trimLeft` is in `MemberExpression.object` 28 | 'trimLeft.foo()', 29 | 'foo.trimLeft.bar()', 30 | ], 31 | invalid: [ 32 | 'foo.trimLeft()', 33 | 'foo.trimRight()', 34 | 'trimLeft.trimRight()', 35 | 'foo.trimLeft.trimRight()', 36 | '"foo".trimLeft()', 37 | outdent` 38 | foo 39 | // comment 40 | .trimRight/* comment */( 41 | /* comment */ 42 | ) 43 | `, 44 | 'foo?.trimLeft()', 45 | ], 46 | }); 47 | -------------------------------------------------------------------------------- /docs/rules/no-process-exit.md: -------------------------------------------------------------------------------- 1 | # Disallow `process.exit()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | This rule is an extension to ESLint's [`no-process-exit` rule](https://eslint.org/docs/rules/no-process-exit), that allows `process.exit()` to be called in files that start with a [hashbang](https://en.wikipedia.org/wiki/Shebang_(Unix)) → `#!/usr/bin/env node`. It also allows `process.exit()` to be called in `process.on('', func)` event handlers and in files that imports `worker_threads`. 9 | 10 | ## Fail 11 | 12 | ```js 13 | process.exit(0); 14 | ``` 15 | 16 | ## Pass 17 | 18 | ```js 19 | #!/usr/bin/env node 20 | process.exit(0); 21 | ``` 22 | 23 | ```js 24 | process.on('SIGINT', () => { 25 | console.log('Got SIGINT'); 26 | process.exit(1); 27 | }); 28 | ``` 29 | 30 | ```js 31 | import workerThreads from 'node:worker_threads'; 32 | 33 | try { 34 | // Do something… 35 | process.exit(0); 36 | } catch (_) { 37 | process.exit(1); 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /test/no-document-cookie.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | 'document.cookie', 9 | 'const foo = document.cookie', 10 | 'foo = document.cookie', 11 | 'foo = document?.cookie', 12 | 'foo = document.cookie + ";foo=bar"', 13 | 'delete document.cookie', 14 | 'if (document.cookie.includes("foo")){}', 15 | 'Object.assign(document, {cookie: "foo=bar"})', 16 | 'document[CONSTANTS_COOKIE] = "foo=bar"', 17 | 'document[cookie] = "foo=bar"', 18 | outdent` 19 | const CONSTANTS_COOKIE = "cookie"; 20 | document[CONSTANTS_COOKIE] = "foo=bar"; 21 | `, 22 | ], 23 | invalid: [ 24 | 'document.cookie = "foo=bar"', 25 | 'document.cookie += ";foo=bar"', 26 | 'document.cookie = document.cookie + ";foo=bar"', 27 | 'document.cookie &&= true', 28 | 'document["coo" + "kie"] = "foo=bar"', 29 | 'foo = document.cookie = "foo=bar"', 30 | 'var doc = document; doc.cookie = "foo=bar"', 31 | 'let doc = document; doc.cookie = "foo=bar"', 32 | 'const doc = globalThis.document; doc.cookie = "foo=bar"', 33 | 'window.document.cookie = "foo=bar"', 34 | ], 35 | }); 36 | -------------------------------------------------------------------------------- /test/prefer-dom-node-text-content.mjs: -------------------------------------------------------------------------------- 1 | import {getTester} from './utils/test.mjs'; 2 | 3 | const {test} = getTester(import.meta); 4 | 5 | test.snapshot({ 6 | valid: [ 7 | 'innerText;', 8 | 'node.textContent;', 9 | 'node[innerText];', 10 | 'innerText = true;', 11 | 'node[\'innerText\'];', 12 | 'innerText.textContent', 13 | 'const [innerText] = node;', 14 | '[innerText] = node;', 15 | 'const {[innerText]: text} = node;', 16 | '({[innerText]: text} = node);', 17 | 'const foo = {innerText}', 18 | 'const foo = {innerText: text}', 19 | ], 20 | invalid: [ 21 | 'node.innerText;', 22 | 'node?.innerText;', 23 | 'node.innerText = \'foo\';', 24 | 'innerText.innerText;', 25 | 'const {innerText} = node;', 26 | 'const {innerText,} = node;', 27 | 'const {innerText: text} = node;', 28 | 'const {innerText = "default text"} = node;', 29 | 'const {innerText: text = "default text"} = node;', 30 | '({innerText} = node);', 31 | '({innerText: text} = node);', 32 | '({innerText = "default text"} = node);', 33 | '({innerText: text = "default text"} = node);', 34 | 'function foo({innerText}) {return innerText}', 35 | 'for (const [{innerText}] of elements);', 36 | ], 37 | }); 38 | -------------------------------------------------------------------------------- /test/utils/not-function-types.mjs: -------------------------------------------------------------------------------- 1 | const notFunctionTypes = [ 2 | // ArrayExpression 3 | '[]', 4 | '[element]', 5 | '[...elements]', 6 | // BinaryExpression 7 | '1 + fn', 8 | '"length" in fn', 9 | 'fn instanceof Function', 10 | // ClassExpression 11 | 'class ClassCantUseAsFunction {}', 12 | // Literal 13 | '0', 14 | '1', 15 | '0.1', 16 | '""', 17 | '"string"', 18 | '/regex/', 19 | 'null', 20 | '0n', 21 | '1n', 22 | 'true', 23 | 'false', 24 | // ObjectExpression 25 | '{}', 26 | // TemplateLiteral 27 | '`templateLiteral`', 28 | // Undefined 29 | 'undefined', 30 | // UnaryExpression 31 | '- fn', 32 | '+ fn', 33 | '~ fn', 34 | 'typeof fn', 35 | 'void fn', 36 | 'delete foo.fn', 37 | // UpdateExpression 38 | '++ fn', 39 | '-- fn', 40 | 41 | // Following are not safe 42 | 'a = fn', // Could be a function 43 | // 'await fn', // This requires async function to test, ignore for now 44 | 'fn()', // Could be a factory returns a function 45 | 'new ClassReturnsFunction()', // `class` constructor could return a function 46 | 'new Function()', // `function` 47 | 'fn``', // Same as `CallExpression` 48 | 'this', // Could be a function 49 | ]; 50 | 51 | export default notFunctionTypes; 52 | -------------------------------------------------------------------------------- /docs/rules/prefer-top-level-await.md: -------------------------------------------------------------------------------- 1 | # Prefer top-level await over top-level promises and async function calls 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | [Top-level await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await) is more readable and can prevent unhandled rejections. 11 | 12 | ## Fail 13 | 14 | ```js 15 | (async () => { 16 | try { 17 | await run(); 18 | } catch (error) { 19 | console.error(error); 20 | process.exit(1); 21 | } 22 | })(); 23 | ``` 24 | 25 | ```js 26 | run().catch(error => { 27 | console.error(error); 28 | process.exit(1); 29 | }); 30 | ``` 31 | 32 | ```js 33 | async function main() { 34 | try { 35 | await run(); 36 | } catch (error) { 37 | console.error(error); 38 | process.exit(1); 39 | } 40 | } 41 | 42 | main(); 43 | ``` 44 | 45 | ## Pass 46 | 47 | ```js 48 | await run(); 49 | ``` 50 | -------------------------------------------------------------------------------- /rules/throw-new-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {switchCallExpressionToNewExpression} = require('./fix/index.js'); 3 | 4 | const messageId = 'throw-new-error'; 5 | const messages = { 6 | [messageId]: 'Use `new` when creating an error.', 7 | }; 8 | 9 | const customError = /^(?:[A-Z][\da-z]*)*Error$/; 10 | 11 | /** @param {import('eslint').Rule.RuleContext} context */ 12 | const create = context => ({ 13 | CallExpression(node) { 14 | const {callee} = node; 15 | if (!( 16 | (callee.type === 'Identifier' && customError.test(callee.name)) 17 | || ( 18 | callee.type === 'MemberExpression' 19 | && !callee.computed 20 | && callee.property.type === 'Identifier' 21 | && customError.test(callee.property.name) 22 | ) 23 | )) { 24 | return; 25 | } 26 | 27 | return { 28 | node, 29 | messageId, 30 | fix: fixer => switchCallExpressionToNewExpression(node, context.sourceCode, fixer), 31 | }; 32 | }, 33 | }); 34 | 35 | /** @type {import('eslint').Rule.RuleModule} */ 36 | module.exports = { 37 | create, 38 | meta: { 39 | type: 'suggestion', 40 | docs: { 41 | description: 'Require `new` when creating an error.', 42 | recommended: true, 43 | }, 44 | fixable: 'code', 45 | messages, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /docs/rules/require-number-to-fixed-digits-argument.md: -------------------------------------------------------------------------------- 1 | # Enforce using the digits argument with `Number#toFixed()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | It's better to make it clear what the value of the `digits` argument is when calling [Number#toFixed()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed), instead of relying on the default value of `0`. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const string = number.toFixed(); 16 | ``` 17 | 18 | ## Pass 19 | 20 | ```js 21 | const string = foo.toFixed(0); 22 | ``` 23 | 24 | ```js 25 | const string = foo.toFixed(2); 26 | ``` 27 | 28 | ```js 29 | const integer = Math.floor(foo); 30 | ``` 31 | 32 | ```js 33 | const integer = Math.ceil(foo); 34 | ``` 35 | 36 | ```js 37 | const integer = Math.round(foo); 38 | ``` 39 | 40 | ```js 41 | const integer = Math.trunc(foo); 42 | ``` 43 | -------------------------------------------------------------------------------- /rules/utils/is-function-self-used-inside.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {findVariable} = require('@eslint-community/eslint-utils'); 3 | 4 | const getReferences = (scope, nodeOrName) => { 5 | const {references = []} = findVariable(scope, nodeOrName) || {}; 6 | return references; 7 | }; 8 | 9 | /** 10 | Check if `this`, `arguments`, or the function name is used inside of itself. 11 | 12 | @param {Node} functionNode - The function node. 13 | @param {Scope} functionScope - The scope of the function node. 14 | @returns {boolean} 15 | */ 16 | function isFunctionSelfUsedInside(functionNode, functionScope) { 17 | /* c8 ignore next 3 */ 18 | if (functionScope.block !== functionNode) { 19 | throw new Error('"functionScope" should be the scope of "functionNode".'); 20 | } 21 | 22 | const {type, id} = functionNode; 23 | 24 | if (type === 'ArrowFunctionExpression') { 25 | return false; 26 | } 27 | 28 | if (functionScope.thisFound) { 29 | return true; 30 | } 31 | 32 | if (getReferences(functionScope, 'arguments').some(({from}) => from === functionScope)) { 33 | return true; 34 | } 35 | 36 | if (id && getReferences(functionScope, id).length > 0) { 37 | return true; 38 | } 39 | 40 | return false; 41 | } 42 | 43 | module.exports = isFunctionSelfUsedInside; 44 | -------------------------------------------------------------------------------- /rules/no-unreadable-iife.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | isParenthesized, 4 | getParenthesizedRange, 5 | toLocation, 6 | } = require('./utils/index.js'); 7 | 8 | const MESSAGE_ID_ERROR = 'no-unreadable-iife'; 9 | const messages = { 10 | [MESSAGE_ID_ERROR]: 'IIFE with parenthesized arrow function body is considered unreadable.', 11 | }; 12 | 13 | /** @param {import('eslint').Rule.RuleContext} context */ 14 | const create = context => ({ 15 | CallExpression(callExpression) { 16 | const {sourceCode} = context; 17 | 18 | if ( 19 | callExpression.callee.type !== 'ArrowFunctionExpression' 20 | || callExpression.callee.body.type === 'BlockStatement' 21 | || !isParenthesized(callExpression.callee.body, sourceCode) 22 | ) { 23 | return; 24 | } 25 | 26 | return { 27 | node: callExpression, 28 | loc: toLocation(getParenthesizedRange(callExpression.callee.body, sourceCode), sourceCode), 29 | messageId: MESSAGE_ID_ERROR, 30 | }; 31 | }, 32 | }); 33 | 34 | /** @type {import('eslint').Rule.RuleModule} */ 35 | module.exports = { 36 | create, 37 | meta: { 38 | type: 'suggestion', 39 | docs: { 40 | description: 'Disallow unreadable IIFEs.', 41 | recommended: true, 42 | }, 43 | hasSuggestions: false, 44 | messages, 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /rules/prefer-blob-reading-methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isMethodCall} = require('./ast/index.js'); 3 | 4 | const MESSAGE_ID = 'error'; 5 | const messages = { 6 | [MESSAGE_ID]: 'Prefer `Blob#{{replacement}}()` over `FileReader#{{method}}(blob)`.', 7 | }; 8 | 9 | /** @param {import('eslint').Rule.RuleContext} context */ 10 | const create = () => ({ 11 | CallExpression(node) { 12 | if (!isMethodCall(node, { 13 | methods: ['readAsText', 'readAsArrayBuffer'], 14 | argumentsLength: 1, 15 | optionalCall: false, 16 | optionalMember: false, 17 | })) { 18 | return; 19 | } 20 | 21 | const method = node.callee.property; 22 | const methodName = method.name; 23 | 24 | return { 25 | node: method, 26 | messageId: MESSAGE_ID, 27 | data: { 28 | method: methodName, 29 | replacement: methodName === 'readAsArrayBuffer' ? 'arrayBuffer' : 'text', 30 | }, 31 | }; 32 | }, 33 | }); 34 | 35 | /** @type {import('eslint').Rule.RuleModule} */ 36 | module.exports = { 37 | create, 38 | meta: { 39 | type: 'suggestion', 40 | docs: { 41 | description: 'Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)`.', 42 | recommended: true, 43 | }, 44 | messages, 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /docs/rules/no-object-as-default-parameter.md: -------------------------------------------------------------------------------- 1 | # Disallow the use of objects as default parameters 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | Default parameters should not be passed to a function through an object literal. The `foo = {a: false}` parameter works fine if only used with one option. As soon as additional options are added, you risk replacing the whole `foo = {a: false, b: true}` object when passing only one option: `{a: true}`. For this reason, object destructuring should be used instead. 9 | 10 | ## Fail 11 | 12 | ```js 13 | const abc = (foo = {a: false}) => {}; 14 | ``` 15 | 16 | ```js 17 | function foo({a} = {a: false}) {} 18 | ``` 19 | 20 | ```js 21 | const abc = (foo = {a: false, b: 123}) => {}; 22 | ``` 23 | 24 | ## Pass 25 | 26 | ```js 27 | const abc = (foo = {}) => {}; 28 | ``` 29 | 30 | ```js 31 | function foo(options) { 32 | const {a} = {a: false, ...options}; 33 | } 34 | ``` 35 | 36 | ```js 37 | const abc = (foo = false) => {}; 38 | ``` 39 | 40 | ```js 41 | const foo = ({a = false, b = 123}) => {}; 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/rules/no-invalid-fetch-options.md: -------------------------------------------------------------------------------- 1 | # Disallow invalid options in `fetch()` and `new Request()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) throws a `TypeError` when the method is `GET` or `HEAD` and a body is provided. 9 | 10 | ## Fail 11 | 12 | ```js 13 | const response = await fetch('/', {body: 'foo=bar'}); 14 | ``` 15 | 16 | ```js 17 | const request = new Request('/', {body: 'foo=bar'}); 18 | ``` 19 | 20 | ```js 21 | const response = await fetch('/', {method: 'GET', body: 'foo=bar'}); 22 | ``` 23 | 24 | ```js 25 | const request = new Request('/', {method: 'GET', body: 'foo=bar'}); 26 | ``` 27 | 28 | ## Pass 29 | 30 | ```js 31 | const response = await fetch('/', {method: 'HEAD'}); 32 | ``` 33 | 34 | ```js 35 | const request = new Request('/', {method: 'HEAD'}); 36 | ``` 37 | 38 | ```js 39 | const response = await fetch('/', {method: 'POST', body: 'foo=bar'}); 40 | ``` 41 | 42 | ```js 43 | const request = new Request('/', {method: 'POST', body: 'foo=bar'}); 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/rules/prefer-dom-node-text-content.md: -------------------------------------------------------------------------------- 1 | # Prefer `.textContent` over `.innerText` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Enforces the use of `.textContent` over `.innerText` for DOM nodes. 11 | 12 | There are [some advantages of using `.textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent), like performance and more predictable behavior when updating it. 13 | 14 | Note that there are [differences](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#differences_from_innertext) between them. 15 | 16 | ## Fail 17 | 18 | ```js 19 | const text = foo.innerText; 20 | ``` 21 | 22 | ```js 23 | const {innerText} = foo; 24 | ``` 25 | 26 | ```js 27 | foo.innerText = '🦄'; 28 | ``` 29 | 30 | ## Pass 31 | 32 | ```js 33 | const text = foo.textContent; 34 | ``` 35 | 36 | ```js 37 | const {textContent} = foo; 38 | ``` 39 | 40 | ```js 41 | foo.textContent = '🦄'; 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/rules/prefer-date-now.md: -------------------------------------------------------------------------------- 1 | # Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) is shorter and nicer than [`new Date().getTime()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime), and avoids unnecessary instantiation of `Date` objects. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const foo = new Date().getTime(); 16 | ``` 17 | 18 | ```js 19 | const foo = new Date().valueOf(); 20 | ``` 21 | 22 | ```js 23 | const foo = +new Date; 24 | ``` 25 | 26 | ```js 27 | const foo = Number(new Date()); 28 | ``` 29 | 30 | ```js 31 | const foo = new Date() * 2; 32 | ``` 33 | 34 | ## Pass 35 | 36 | ```js 37 | const foo = Date.now(); 38 | ``` 39 | 40 | ```js 41 | const foo = Date.now() * 2; 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/rules/require-array-join-separator.md: -------------------------------------------------------------------------------- 1 | # Enforce using the separator argument with `Array#join()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | It's better to make it clear what the separator is when calling [Array#join()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join), instead of relying on the default comma (`','`) separator. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const string = array.join(); 16 | ``` 17 | 18 | ```js 19 | const string = Array.prototype.join.call(arrayLike); 20 | ``` 21 | 22 | ```js 23 | const string = [].join.call(arrayLike); 24 | ``` 25 | 26 | ## Pass 27 | 28 | ```js 29 | const string = array.join(','); 30 | ``` 31 | 32 | ```js 33 | const string = array.join('|'); 34 | ``` 35 | 36 | ```js 37 | const string = Array.prototype.join.call(arrayLike, ''); 38 | ``` 39 | 40 | ```js 41 | const string = [].join.call(arrayLike, '\n'); 42 | ``` 43 | -------------------------------------------------------------------------------- /rules/prefer-dom-node-append.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isMethodCall} = require('./ast/index.js'); 3 | const {isNodeValueNotDomNode, isValueNotUsable} = require('./utils/index.js'); 4 | 5 | const MESSAGE_ID = 'prefer-dom-node-append'; 6 | const messages = { 7 | [MESSAGE_ID]: 'Prefer `Node#append()` over `Node#appendChild()`.', 8 | }; 9 | 10 | /** @param {import('eslint').Rule.RuleContext} context */ 11 | const create = () => ({ 12 | CallExpression(node) { 13 | if ( 14 | !isMethodCall(node, { 15 | method: 'appendChild', 16 | argumentsLength: 1, 17 | optionalCall: false, 18 | }) 19 | || isNodeValueNotDomNode(node.callee.object) 20 | || isNodeValueNotDomNode(node.arguments[0]) 21 | ) { 22 | return; 23 | } 24 | 25 | const fix = isValueNotUsable(node) 26 | ? fixer => fixer.replaceText(node.callee.property, 'append') 27 | : undefined; 28 | 29 | return { 30 | node, 31 | messageId: MESSAGE_ID, 32 | fix, 33 | }; 34 | }, 35 | }); 36 | 37 | /** @type {import('eslint').Rule.RuleModule} */ 38 | module.exports = { 39 | create, 40 | meta: { 41 | type: 'suggestion', 42 | docs: { 43 | description: 'Prefer `Node#append()` over `Node#appendChild()`.', 44 | recommended: true, 45 | }, 46 | fixable: 'code', 47 | messages, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /rules/utils/get-call-expression-tokens.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | isOpeningParenToken, 5 | isCommaToken, 6 | } = require('@eslint-community/eslint-utils'); 7 | 8 | /** @typedef {import('estree').CallExpression} CallExpression */ 9 | /** @typedef {import('eslint').AST.Token} Token */ 10 | 11 | /** 12 | Get the `openingParenthesisToken`, `closingParenthesisToken`, and `trailingCommaToken` of `CallExpression`. 13 | 14 | @param {import('eslint').SourceCode} sourceCode - The source code object. 15 | @param {CallExpression} callExpression - The `CallExpression` node. 16 | @returns {{ 17 | openingParenthesisToken: Token, 18 | closingParenthesisToken: Token, 19 | trailingCommaToken: Token | undefined, 20 | }} 21 | */ 22 | function getCallExpressionTokens(sourceCode, callExpression) { 23 | const openingParenthesisToken = sourceCode.getTokenAfter(callExpression.callee, isOpeningParenToken); 24 | const [ 25 | penultimateToken, 26 | closingParenthesisToken, 27 | ] = sourceCode.getLastTokens(callExpression, 2); 28 | const trailingCommaToken = isCommaToken(penultimateToken) ? penultimateToken : undefined; 29 | 30 | return { 31 | openingParenthesisToken, 32 | closingParenthesisToken, 33 | trailingCommaToken, 34 | }; 35 | } 36 | 37 | module.exports = getCallExpressionTokens; 38 | -------------------------------------------------------------------------------- /docs/rules/prefer-prototype-methods.md: -------------------------------------------------------------------------------- 1 | # Prefer borrowing methods from the prototype instead of the instance 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | When “borrowing” a method from `Array` or `Object`, it's clearer to get it from the prototype than from an instance. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const array = [].slice.apply(bar); 16 | ``` 17 | 18 | ```js 19 | const type = {}.toString.call(foo); 20 | ``` 21 | 22 | ```js 23 | Reflect.apply([].forEach, arrayLike, [callback]); 24 | ``` 25 | 26 | ```js 27 | const type = globalThis.toString.call(foo); 28 | ``` 29 | 30 | ## Pass 31 | 32 | ```js 33 | const array = Array.prototype.slice.apply(bar); 34 | ``` 35 | 36 | ```js 37 | const type = Object.prototype.toString.call(foo); 38 | ``` 39 | 40 | ```js 41 | Reflect.apply(Array.prototype.forEach, arrayLike, [callback]); 42 | ``` 43 | 44 | ```js 45 | const maxValue = Math.max.apply(Math, numbers); 46 | ``` 47 | -------------------------------------------------------------------------------- /test/snapshots/no-magic-array-flat-depth.mjs.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/no-magic-array-flat-depth.mjs` 2 | 3 | The actual snapshot is saved in `no-magic-array-flat-depth.mjs.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## invalid(1): array.flat(2) 8 | 9 | > Input 10 | 11 | `␊ 12 | 1 | array.flat(2)␊ 13 | ` 14 | 15 | > Error 1/1 16 | 17 | `␊ 18 | > 1 | array.flat(2)␊ 19 | | ^ Magic number as depth is not allowed.␊ 20 | ` 21 | 22 | ## invalid(2): array?.flat(2) 23 | 24 | > Input 25 | 26 | `␊ 27 | 1 | array?.flat(2)␊ 28 | ` 29 | 30 | > Error 1/1 31 | 32 | `␊ 33 | > 1 | array?.flat(2)␊ 34 | | ^ Magic number as depth is not allowed.␊ 35 | ` 36 | 37 | ## invalid(3): array.flat(99,) 38 | 39 | > Input 40 | 41 | `␊ 42 | 1 | array.flat(99,)␊ 43 | ` 44 | 45 | > Error 1/1 46 | 47 | `␊ 48 | > 1 | array.flat(99,)␊ 49 | | ^^ Magic number as depth is not allowed.␊ 50 | ` 51 | 52 | ## invalid(4): array.flat(0b10,) 53 | 54 | > Input 55 | 56 | `␊ 57 | 1 | array.flat(0b10,)␊ 58 | ` 59 | 60 | > Error 1/1 61 | 62 | `␊ 63 | > 1 | array.flat(0b10,)␊ 64 | | ^^^^ Magic number as depth is not allowed.␊ 65 | ` 66 | -------------------------------------------------------------------------------- /docs/rules/prefer-query-selector.md: -------------------------------------------------------------------------------- 1 | # Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` and `.getElementsByName()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | It's better to use the same method to query DOM elements. This helps keep consistency and it lends itself to future improvements (e.g. more specific selectors). 11 | 12 | ## Fail 13 | 14 | ```js 15 | document.getElementById('foo'); 16 | document.getElementsByClassName('foo bar'); 17 | document.getElementsByTagName('main'); 18 | document.getElementsByClassName(fn()); 19 | ``` 20 | 21 | ## Pass 22 | 23 | ```js 24 | document.querySelector('#foo'); 25 | document.querySelector('.bar'); 26 | document.querySelector('main #foo .bar'); 27 | document.querySelectorAll('.foo .bar'); 28 | document.querySelectorAll('li a'); 29 | document.querySelector('li').querySelectorAll('a'); 30 | ``` 31 | -------------------------------------------------------------------------------- /rules/prefer-string-trim-start-end.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isMethodCall} = require('./ast/index.js'); 3 | 4 | const MESSAGE_ID = 'prefer-string-trim-start-end'; 5 | const messages = { 6 | [MESSAGE_ID]: 'Prefer `String#{{replacement}}()` over `String#{{method}}()`.', 7 | }; 8 | 9 | /** @param {import('eslint').Rule.RuleContext} context */ 10 | const create = () => ({ 11 | CallExpression(callExpression) { 12 | if (!isMethodCall(callExpression, { 13 | methods: ['trimLeft', 'trimRight'], 14 | argumentsLength: 0, 15 | optionalCall: false, 16 | })) { 17 | return; 18 | } 19 | 20 | const node = callExpression.callee.property; 21 | const method = node.name; 22 | const replacement = method === 'trimLeft' ? 'trimStart' : 'trimEnd'; 23 | 24 | return { 25 | node, 26 | messageId: MESSAGE_ID, 27 | data: {method, replacement}, 28 | fix: fixer => fixer.replaceText(node, replacement), 29 | }; 30 | }, 31 | }); 32 | 33 | /** @type {import('eslint').Rule.RuleModule} */ 34 | module.exports = { 35 | create, 36 | meta: { 37 | type: 'suggestion', 38 | docs: { 39 | description: 'Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`.', 40 | recommended: true, 41 | }, 42 | fixable: 'code', 43 | messages, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /docs/rules/prefer-reflect-apply.md: -------------------------------------------------------------------------------- 1 | # Prefer `Reflect.apply()` over `Function#apply()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | [`Reflect.apply()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply) is arguably less verbose and easier to understand. In addition, when you accept arbitrary methods, it's not safe to assume `.apply()` exists or is not overridden. 11 | 12 | ## Fail 13 | 14 | ```js 15 | function foo() {} 16 | 17 | foo.apply(null, [42]); 18 | Function.prototype.apply.call(foo, null, [42]); 19 | foo.apply(this, [42]); 20 | Function.prototype.apply.call(foo, this, [42]); 21 | foo.apply(null, arguments); 22 | Function.prototype.apply.call(foo, null, arguments); 23 | foo.apply(this, arguments); 24 | Function.prototype.apply.call(foo, this, arguments); 25 | ``` 26 | 27 | ## Pass 28 | 29 | ```js 30 | function foo() {} 31 | 32 | Reflect.apply(foo, null, [42]); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/rules/no-document-cookie.md: -------------------------------------------------------------------------------- 1 | # Do not use `document.cookie` directly 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | It's not recommended to use [`document.cookie`](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie) directly as it's easy to get the string wrong. Instead, you should use the [Cookie Store API](https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API) or a [cookie library](https://www.npmjs.com/search?q=cookie). 9 | 10 | ## Fail 11 | 12 | ```js 13 | document.cookie = 14 | 'foo=bar' + 15 | '; Path=/' + 16 | '; Domain=example.com' + 17 | '; expires=Fri, 31 Dec 9999 23:59:59 GMT' + 18 | '; Secure'; 19 | ``` 20 | 21 | ```js 22 | document.cookie += '; foo=bar'; 23 | ``` 24 | 25 | ## Pass 26 | 27 | ```js 28 | await cookieStore.set({ 29 | name: 'foo', 30 | value: 'bar', 31 | expires: Date.now() + 24 * 60 * 60 * 1000, 32 | domain: 'example.com' 33 | }); 34 | ``` 35 | 36 | ```js 37 | const array = document.cookie.split('; '); 38 | ``` 39 | 40 | ```js 41 | import Cookies from 'js-cookie'; 42 | 43 | Cookies.set('foo', 'bar'); 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/rules/text-encoding-identifier-case.md: -------------------------------------------------------------------------------- 1 | # Enforce consistent case for text encoding identifiers 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | - Enforce `'utf8'` for [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoding. 11 | - Enforce `'ascii'` for [ASCII](https://en.wikipedia.org/wiki/ASCII) encoding. 12 | 13 | This rule only auto-fix encoding in `fs.readFile()` and `fs.readFileSync()`. 14 | 15 | ## Fail 16 | 17 | ```js 18 | await fs.readFile(file, 'UTF-8'); 19 | ``` 20 | 21 | ```js 22 | await fs.readFile(file, 'ASCII'); 23 | ``` 24 | 25 | ```js 26 | const string = buffer.toString('utf-8'); 27 | ``` 28 | 29 | ## Pass 30 | 31 | ```js 32 | await fs.readFile(file, 'utf8'); 33 | ``` 34 | 35 | ```js 36 | await fs.readFile(file, 'ascii'); 37 | ``` 38 | 39 | ```js 40 | const string = buffer.toString('utf8'); 41 | ``` 42 | -------------------------------------------------------------------------------- /rules/fix/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | // Utilities 5 | extendFixRange: require('./extend-fix-range.js'), 6 | removeParentheses: require('./remove-parentheses.js'), 7 | 8 | appendArgument: require('./append-argument.js'), 9 | removeArgument: require('./remove-argument.js'), 10 | replaceArgument: require('./replace-argument.js'), 11 | switchNewExpressionToCallExpression: require('./switch-new-expression-to-call-expression.js'), 12 | switchCallExpressionToNewExpression: require('./switch-call-expression-to-new-expression.js'), 13 | removeMemberExpressionProperty: require('./remove-member-expression-property.js'), 14 | removeMethodCall: require('./remove-method-call.js'), 15 | replaceTemplateElement: require('./replace-template-element.js'), 16 | replaceReferenceIdentifier: require('./replace-reference-identifier.js'), 17 | renameVariable: require('./rename-variable.js'), 18 | replaceNodeOrTokenAndSpacesBefore: require('./replace-node-or-token-and-spaces-before.js'), 19 | removeSpacesAfter: require('./remove-spaces-after.js'), 20 | fixSpaceAroundKeyword: require('./fix-space-around-keywords.js'), 21 | replaceStringLiteral: require('./replace-string-literal.js'), 22 | addParenthesizesToReturnOrThrowExpression: require('./add-parenthesizes-to-return-or-throw-expression.js'), 23 | }; 24 | -------------------------------------------------------------------------------- /scripts/internal-rules/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('node:path'); 4 | 5 | const pluginName = 'internal-rules'; 6 | const TEST_DIRECTORIES = [ 7 | path.join(__dirname, '../../test'), 8 | ]; 9 | const RULES_DIRECTORIES = [ 10 | path.join(__dirname, '../../rules'), 11 | ]; 12 | 13 | const rules = [ 14 | {id: 'fix-snapshot-test', directories: TEST_DIRECTORIES}, 15 | {id: 'prefer-negative-boolean-attribute', directories: RULES_DIRECTORIES}, 16 | {id: 'no-test-only', directories: TEST_DIRECTORIES}, 17 | ]; 18 | 19 | const isFileInsideDirectory = (filename, directory) => filename.startsWith(directory + path.sep); 20 | 21 | module.exports = { 22 | rules: Object.fromEntries( 23 | rules.map(({id, directories}) => { 24 | const rule = require(`./${id}.js`); 25 | return [ 26 | id, 27 | { 28 | ...rule, 29 | create(context) { 30 | const filename = context.physicalFilename; 31 | if (directories.every(directory => !isFileInsideDirectory(filename, directory))) { 32 | return {}; 33 | } 34 | 35 | return rule.create(context); 36 | }, 37 | }, 38 | ]; 39 | }), 40 | ), 41 | configs: { 42 | all: { 43 | plugins: [pluginName], 44 | rules: Object.fromEntries(rules.map(({id}) => [`${pluginName}/${id}`, 'error'])), 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /docs/rules/no-zero-fractions.md: -------------------------------------------------------------------------------- 1 | # Disallow number literals with zero fractions or dangling dots 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | There is no difference in JavaScript between, for example, `1`, `1.0` and `1.`, so prefer the former for consistency and brevity. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const foo = 1.0; 16 | ``` 17 | 18 | ```js 19 | const foo = -1.0; 20 | ``` 21 | 22 | ```js 23 | const foo = 123_456.000_000; 24 | ``` 25 | 26 | ```js 27 | const foo = 1.; 28 | ``` 29 | 30 | ```js 31 | const foo = 123.111000000; 32 | ``` 33 | 34 | ```js 35 | const foo = 123.00e20; 36 | ``` 37 | 38 | ## Pass 39 | 40 | ```js 41 | const foo = 1; 42 | ``` 43 | 44 | ```js 45 | const foo = -1; 46 | ``` 47 | 48 | ```js 49 | const foo = 123456; 50 | ``` 51 | 52 | ```js 53 | const foo = 1.1; 54 | ``` 55 | 56 | ```js 57 | const foo = -1.1; 58 | ``` 59 | 60 | ```js 61 | const foo = 123.456; 62 | ``` 63 | 64 | ```js 65 | const foo = 1e3; 66 | ``` 67 | -------------------------------------------------------------------------------- /rules/ast/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | isLiteral, 5 | isStringLiteral, 6 | isNumberLiteral, 7 | isBigIntLiteral, 8 | isNullLiteral, 9 | isRegexLiteral, 10 | } = require('./literal.js'); 11 | const { 12 | isNewExpression, 13 | isCallExpression, 14 | isCallOrNewExpression, 15 | } = require('./call-or-new-expression.js'); 16 | 17 | module.exports = { 18 | isLiteral, 19 | isStringLiteral, 20 | isNumberLiteral, 21 | isBigIntLiteral, 22 | isNullLiteral, 23 | isRegexLiteral, 24 | 25 | isArrowFunctionBody: require('./is-arrow-function-body.js'), 26 | isCallExpression, 27 | isCallOrNewExpression, 28 | isDirective: require('./is-directive.js'), 29 | isEmptyNode: require('./is-empty-node.js'), 30 | isExpressionStatement: require('./is-expression-statement.js'), 31 | isFunction: require('./is-function.js'), 32 | isMemberExpression: require('./is-member-expression.js'), 33 | isMethodCall: require('./is-method-call.js'), 34 | isNegativeOne: require('./is-negative-one.js'), 35 | isNewExpression, 36 | isReferenceIdentifier: require('./is-reference-identifier.js'), 37 | isStaticRequire: require('./is-static-require.js'), 38 | isTaggedTemplateLiteral: require('./is-tagged-template-literal.js'), 39 | isUndefined: require('./is-undefined.js'), 40 | 41 | functionTypes: require('./function-types.js'), 42 | }; 43 | -------------------------------------------------------------------------------- /docs/rules/prefer-string-slice.md: -------------------------------------------------------------------------------- 1 | # Prefer `String#slice()` over `String#substr()` and `String#substring()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | [`String#substr()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr) and [`String#substring()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring) are the two lesser known legacy ways to slice a string. It's better to use [`String#slice()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) as it's a more popular option with clearer behavior that has a consistent [`Array` counterpart](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice). 11 | 12 | ## Fail 13 | 14 | ```js 15 | foo.substr(start, length); 16 | foo.substring(indexStart, indexEnd); 17 | ``` 18 | 19 | ## Pass 20 | 21 | ```js 22 | foo.slice(beginIndex, endIndex); 23 | ``` 24 | -------------------------------------------------------------------------------- /rules/number-literal-case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {checkVueTemplate} = require('./utils/rule.js'); 3 | const {isNumberLiteral, isBigIntLiteral} = require('./ast/index.js'); 4 | 5 | const MESSAGE_ID = 'number-literal-case'; 6 | const messages = { 7 | [MESSAGE_ID]: 'Invalid number literal casing.', 8 | }; 9 | 10 | const fix = raw => { 11 | let fixed = raw.toLowerCase(); 12 | if (fixed.startsWith('0x')) { 13 | fixed = '0x' + fixed.slice(2).toUpperCase(); 14 | } 15 | 16 | return fixed; 17 | }; 18 | 19 | /** @param {import('eslint').Rule.RuleContext} context */ 20 | const create = () => ({ 21 | Literal(node) { 22 | const {raw} = node; 23 | 24 | let fixed = raw; 25 | if (isNumberLiteral(node)) { 26 | fixed = fix(raw); 27 | } else if (isBigIntLiteral(node)) { 28 | fixed = fix(raw.slice(0, -1)) + 'n'; 29 | } 30 | 31 | if (raw !== fixed) { 32 | return { 33 | node, 34 | messageId: MESSAGE_ID, 35 | fix: fixer => fixer.replaceText(node, fixed), 36 | }; 37 | } 38 | }, 39 | }); 40 | 41 | /** @type {import('eslint').Rule.RuleModule} */ 42 | module.exports = { 43 | create: checkVueTemplate(create), 44 | meta: { 45 | type: 'suggestion', 46 | docs: { 47 | description: 'Enforce proper case for numeric literals.', 48 | recommended: true, 49 | }, 50 | fixable: 'code', 51 | messages, 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /test/prefer-string-raw.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | String.raw`a = '\''`, 9 | // Cannot use `String.raw` 10 | String.raw`'a\\b'`, 11 | String.raw`import foo from "./foo\\bar.js";`, 12 | String.raw`export {foo} from "./foo\\bar.js";`, 13 | String.raw`export * from "./foo\\bar.js";`, 14 | String.raw`a = {'a\\b': ''}`, 15 | outdent` 16 | a = "\\\\a \\ 17 | b" 18 | `, 19 | String.raw`a = 'a\\b\u{51}c'`, 20 | 'a = "a\\\\b`"', 21 | // eslint-disable-next-line no-template-curly-in-string 22 | 'a = "a\\\\b${foo}"', 23 | { 24 | code: String.raw``, 25 | languageOptions: { 26 | parserOptions: { 27 | ecmaFeatures: { 28 | jsx: true, 29 | }, 30 | }, 31 | }, 32 | }, 33 | ], 34 | invalid: [ 35 | String.raw`a = 'a\\b'`, 36 | String.raw`a = {['a\\b']: b}`, 37 | String.raw`function a() {return'a\\b'}`, 38 | String.raw`const foo = "foo \\x46";`, 39 | ], 40 | }); 41 | 42 | test.typescript({ 43 | valid: [ 44 | outdent` 45 | enum Files { 46 | Foo = "C:\\\\path\\\\to\\\\foo.js", 47 | } 48 | `, 49 | outdent` 50 | enum Foo { 51 | "\\\\a\\\\b" = "baz", 52 | } 53 | `, 54 | ], 55 | invalid: [], 56 | }); 57 | -------------------------------------------------------------------------------- /docs/rules/relative-url-style.md: -------------------------------------------------------------------------------- 1 | # Enforce consistent relative URL style 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | When using a relative URL in [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL), the URL should either never or always use the `./` prefix consistently. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const url = new URL('./foo', base); 16 | ``` 17 | 18 | ## Pass 19 | 20 | ```js 21 | const url = new URL('foo', base); 22 | ``` 23 | 24 | ## Options 25 | 26 | Type: `string`\ 27 | Default: `'never'` 28 | 29 | - `'never'` (default) 30 | - Never use a `./` prefix. 31 | - `'always'` 32 | - Always add a `./` prefix to the relative URL when possible. 33 | 34 | ```js 35 | // eslint unicorn/relative-url-style: ["error", "always"] 36 | const url = new URL('foo', base); // Fail 37 | const url = new URL('./foo', base); // Pass 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/rules/no-await-expression-member.md: -------------------------------------------------------------------------------- 1 | # Disallow member access from await expression 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. 11 | 12 | This rule is fixable for simple member access. 13 | 14 | ## Fail 15 | 16 | ```js 17 | const foo = (await import('./foo.js')).default; 18 | ``` 19 | 20 | ```js 21 | const secondElement = (await getArray())[1]; 22 | ``` 23 | 24 | ```js 25 | const property = (await getObject()).property; 26 | ``` 27 | 28 | ```js 29 | const data = await (await fetch('/foo')).json(); 30 | ``` 31 | 32 | ## Pass 33 | 34 | ```js 35 | const {default: foo} = await import('./foo.js'); 36 | ``` 37 | 38 | ```js 39 | const [, secondElement] = await getArray(); 40 | ``` 41 | 42 | ```js 43 | const {property} = await getObject(); 44 | ``` 45 | 46 | ```js 47 | const response = await fetch('/foo'); 48 | const data = await response.json(); 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/rules/no-console-spaces.md: -------------------------------------------------------------------------------- 1 | # Do not use leading/trailing space between `console.log` parameters 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | The [`console.log()` method](https://developer.mozilla.org/en-US/docs/Web/API/Console/log) and similar methods joins the parameters with a space, so adding a leading/trailing space to a parameter, results in two spaces being added. 11 | 12 | ## Fail 13 | 14 | ```js 15 | console.log('abc ', 'def'); 16 | console.log('abc', ' def'); 17 | 18 | console.log("abc ", " def"); 19 | console.log(`abc `, ` def`); 20 | 21 | console.debug('abc ', 'def'); 22 | console.info('abc ', 'def'); 23 | console.warn('abc ', 'def'); 24 | console.error('abc ', 'def'); 25 | ``` 26 | 27 | ## Pass 28 | 29 | ```js 30 | console.log('abc'); 31 | console.log('abc', 'def'); 32 | 33 | console.log('abc '); 34 | console.log(' abc'); 35 | 36 | console.log('abc ', 'def'); 37 | console.log('abc\t', 'def'); 38 | console.log('abc\n', 'def'); 39 | 40 | console.log(` 41 | abc 42 | `); 43 | ``` 44 | -------------------------------------------------------------------------------- /rules/fix/replace-reference-identifier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isShorthandPropertyValue = require('../utils/is-shorthand-property-value.js'); 4 | const isShorthandPropertyAssignmentPatternLeft = require('../utils/is-shorthand-property-assignment-pattern-left.js'); 5 | const isShorthandImportLocal = require('../utils/is-shorthand-import-local.js'); 6 | const isShorthandExportLocal = require('../utils/is-shorthand-export-local.js'); 7 | 8 | function replaceReferenceIdentifier(identifier, replacement, fixer) { 9 | if ( 10 | isShorthandPropertyValue(identifier) 11 | || isShorthandPropertyAssignmentPatternLeft(identifier) 12 | ) { 13 | return fixer.replaceText(identifier, `${identifier.name}: ${replacement}`); 14 | } 15 | 16 | if (isShorthandImportLocal(identifier)) { 17 | return fixer.replaceText(identifier, `${identifier.name} as ${replacement}`); 18 | } 19 | 20 | if (isShorthandExportLocal(identifier)) { 21 | return fixer.replaceText(identifier, `${replacement} as ${identifier.name}`); 22 | } 23 | 24 | // `typeAnnotation` 25 | if (identifier.typeAnnotation) { 26 | return fixer.replaceTextRange( 27 | [identifier.range[0], identifier.typeAnnotation.range[0]], 28 | `${replacement}${identifier.optional ? '?' : ''}`, 29 | ); 30 | } 31 | 32 | return fixer.replaceText(identifier, replacement); 33 | } 34 | 35 | module.exports = replaceReferenceIdentifier; 36 | -------------------------------------------------------------------------------- /rules/no-abusive-eslint-disable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MESSAGE_ID = 'no-abusive-eslint-disable'; 4 | const messages = { 5 | [MESSAGE_ID]: 'Specify the rules you want to disable.', 6 | }; 7 | 8 | const disableRegex = /^eslint-disable(?:-next-line|-line)?(?$|(?:\s+(?:@(?:[\w-]+\/){1,2})?[\w-]+)?)/; 9 | 10 | /** @param {import('eslint').Rule.RuleContext} context */ 11 | const create = () => ({ 12 | * Program(node) { 13 | for (const comment of node.comments) { 14 | const value = comment.value.trim(); 15 | const result = disableRegex.exec(value); 16 | 17 | if ( 18 | result // It's a eslint-disable comment 19 | && !result.groups.ruleId // But it did not specify any rules 20 | ) { 21 | yield { 22 | // Can't set it at the given location as the warning 23 | // will be ignored due to the disable comment 24 | loc: { 25 | start: { 26 | ...comment.loc.start, 27 | column: -1, 28 | }, 29 | end: comment.loc.end, 30 | }, 31 | messageId: MESSAGE_ID, 32 | }; 33 | } 34 | } 35 | }, 36 | }); 37 | 38 | /** @type {import('eslint').Rule.RuleModule} */ 39 | module.exports = { 40 | create, 41 | meta: { 42 | type: 'suggestion', 43 | docs: { 44 | description: 'Enforce specifying rules to disable in `eslint-disable` comments.', 45 | recommended: true, 46 | }, 47 | messages, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /docs/rules/no-this-assignment.md: -------------------------------------------------------------------------------- 1 | # Disallow assigning `this` to a variable 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | `this` should be used directly. If you want a reference to `this` from a higher scope, consider using [arrow function expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) or [`Function#bind()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind). 9 | 10 | ## Fail 11 | 12 | ```js 13 | const foo = this; 14 | 15 | setTimeout(function () { 16 | foo.bar(); 17 | }, 1000); 18 | ``` 19 | 20 | ```js 21 | const foo = this; 22 | 23 | class Bar { 24 | method() { 25 | foo.baz(); 26 | } 27 | } 28 | 29 | new Bar().method(); 30 | ``` 31 | 32 | ## Pass 33 | 34 | ```js 35 | setTimeout(() => { 36 | this.bar(); 37 | }, 1000); 38 | ``` 39 | 40 | ```js 41 | setTimeout(function () { 42 | this.bar(); 43 | }.bind(this), 1000); 44 | ``` 45 | 46 | ```js 47 | class Bar { 48 | constructor(fooInstance) { 49 | this.fooInstance = fooInstance; 50 | } 51 | method() { 52 | this.fooInstance.baz(); 53 | } 54 | } 55 | 56 | new Bar(this).method(); 57 | ``` 58 | -------------------------------------------------------------------------------- /rules/no-object-as-default-parameter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isFunction} = require('./ast/index.js'); 3 | 4 | const MESSAGE_ID_IDENTIFIER = 'identifier'; 5 | const MESSAGE_ID_NON_IDENTIFIER = 'non-identifier'; 6 | const messages = { 7 | [MESSAGE_ID_IDENTIFIER]: 'Do not use an object literal as default for parameter `{{parameter}}`.', 8 | [MESSAGE_ID_NON_IDENTIFIER]: 'Do not use an object literal as default.', 9 | }; 10 | 11 | /** @param {import('eslint').Rule.RuleContext} context */ 12 | const create = () => ({ 13 | AssignmentPattern(node) { 14 | if (!( 15 | node.right.type === 'ObjectExpression' 16 | && node.right.properties.length > 0 17 | && isFunction(node.parent) 18 | && node.parent.params.includes(node) 19 | )) { 20 | return; 21 | } 22 | 23 | const {left, right} = node; 24 | 25 | if (left.type === 'Identifier') { 26 | return { 27 | node: left, 28 | messageId: MESSAGE_ID_IDENTIFIER, 29 | data: {parameter: left.name}, 30 | }; 31 | } 32 | 33 | return { 34 | node: right, 35 | messageId: MESSAGE_ID_NON_IDENTIFIER, 36 | }; 37 | }, 38 | }); 39 | 40 | /** @type {import('eslint').Rule.RuleModule} */ 41 | module.exports = { 42 | create, 43 | meta: { 44 | type: 'problem', 45 | docs: { 46 | description: 'Disallow the use of objects as default parameters.', 47 | recommended: true, 48 | }, 49 | messages, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /eslint.dogfooding.config.mjs: -------------------------------------------------------------------------------- 1 | /* Run all unicorn rules on codebase */ 2 | /* 3 | ! If you're making a new rule, you can ignore this before review. 4 | */ 5 | 6 | import eslintPluginUnicorn from './index.js'; 7 | 8 | const config = [ 9 | eslintPluginUnicorn.configs['flat/all'], 10 | { 11 | linterOptions: { 12 | reportUnusedDisableDirectives: false, 13 | }, 14 | // Fake rule to allow inline config to disable 15 | plugins: { 16 | n: { 17 | rules: {'no-unsupported-features/es-syntax': {}}, 18 | }, 19 | }, 20 | }, 21 | { 22 | ignores: [ 23 | 'coverage', 24 | 'test/integration/fixtures', 25 | 'test/integration/fixtures-local', 26 | 'rules/utils/lodash.js', 27 | ], 28 | }, 29 | { 30 | rules: { 31 | // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1109#issuecomment-782689255 32 | 'unicorn/consistent-destructuring': 'off', 33 | // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2341 34 | 'unicorn/escape-case': 'off', 35 | 'unicorn/no-hex-escape': 'off', 36 | // Buggy 37 | 'unicorn/custom-error-definition': 'off', 38 | 'unicorn/consistent-function-scoping': 'off', 39 | // Annoying 40 | 'unicorn/no-keyword-prefix': 'off', 41 | }, 42 | }, 43 | { 44 | files: [ 45 | '**/*.js', 46 | ], 47 | rules: { 48 | 'unicorn/prefer-module': 'off', 49 | }, 50 | }, 51 | ]; 52 | 53 | export default config; 54 | -------------------------------------------------------------------------------- /rules/shared/negative-index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const isSameReference = require('../utils/is-same-reference.js'); 3 | const {getParenthesizedRange} = require('../utils/parentheses.js'); 4 | const {isNumberLiteral} = require('../ast/index.js'); 5 | 6 | const isLengthMemberExpression = node => 7 | node.type === 'MemberExpression' 8 | && !node.computed 9 | && !node.optional 10 | && node.property.type === 'Identifier' 11 | && node.property.name === 'length'; 12 | const isLiteralPositiveNumber = node => 13 | isNumberLiteral(node) 14 | && node.value > 0; 15 | 16 | function getNegativeIndexLengthNode(node, objectNode) { 17 | if (!node) { 18 | return; 19 | } 20 | 21 | const {type, operator, left, right} = node; 22 | 23 | if (type !== 'BinaryExpression' || operator !== '-' || !isLiteralPositiveNumber(right)) { 24 | return; 25 | } 26 | 27 | if (isLengthMemberExpression(left) && isSameReference(left.object, objectNode)) { 28 | return left; 29 | } 30 | 31 | // Nested BinaryExpression 32 | return getNegativeIndexLengthNode(left, objectNode); 33 | } 34 | 35 | function removeLengthNode(node, fixer, sourceCode) { 36 | const [start, end] = getParenthesizedRange(node, sourceCode); 37 | return fixer.removeRange([ 38 | start, 39 | end + sourceCode.text.slice(end).match(/\S|$/).index, 40 | ]); 41 | } 42 | 43 | module.exports = { 44 | getNegativeIndexLengthNode, 45 | removeLengthNode, 46 | }; 47 | -------------------------------------------------------------------------------- /docs/rules/prefer-code-point.md: -------------------------------------------------------------------------------- 1 | # Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Unicode is better supported in [`String#codePointAt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) and [`String.fromCodePoint()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint). 11 | 12 | - [Difference between `String.fromCodePoint()` and `String.fromCharCode()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint#compared_to_fromcharcode) 13 | 14 | ## Fail 15 | 16 | ```js 17 | const unicorn = '🦄'.charCodeAt(0).toString(16); 18 | ``` 19 | 20 | ```js 21 | const unicorn = String.fromCharCode(0x1f984); 22 | ``` 23 | 24 | ## Pass 25 | 26 | ```js 27 | const unicorn = '🦄'.codePointAt(0).toString(16); 28 | ``` 29 | 30 | ```js 31 | const unicorn = String.fromCodePoint(0x1f984); 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/rules/no-useless-switch-case.md: -------------------------------------------------------------------------------- 1 | # Disallow useless case in switch statements 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | An empty case before the last default case is useless. 11 | 12 | ## Fail 13 | 14 | ```js 15 | switch (foo) { 16 | case 1: 17 | default: 18 | handleDefaultCase(); 19 | break; 20 | } 21 | ``` 22 | 23 | ## Pass 24 | 25 | ```js 26 | switch (foo) { 27 | case 1: 28 | case 2: 29 | handleCase1And2(); 30 | break; 31 | } 32 | ``` 33 | 34 | ```js 35 | switch (foo) { 36 | case 1: 37 | handleCase1(); 38 | break; 39 | default: 40 | handleDefaultCase(); 41 | break; 42 | } 43 | ``` 44 | 45 | ```js 46 | switch (foo) { 47 | case 1: 48 | handleCase1(); 49 | // Fallthrough 50 | default: 51 | handleDefaultCase(); 52 | break; 53 | } 54 | ``` 55 | 56 | ```js 57 | switch (foo) { 58 | // This is actually useless, but we only check cases where the last case is the `default` case 59 | case 1: 60 | default: 61 | handleDefaultCase(); 62 | break; 63 | case 2: 64 | handleCase2(); 65 | break; 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /rules/utils/is-node-matches.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Check if node matches object name or key path. 5 | 6 | @param {Node} node - The AST node to check. 7 | @param {string} nameOrPath - The object name or key path. 8 | @returns {boolean} 9 | */ 10 | function isNodeMatchesNameOrPath(node, nameOrPath) { 11 | const names = nameOrPath.trim().split('.'); 12 | for (let index = names.length - 1; index >= 0; index--) { 13 | const name = names[index]; 14 | if (!name) { 15 | return false; 16 | } 17 | 18 | if (index === 0) { 19 | return ( 20 | (node.type === 'Identifier' && node.name === name) 21 | || (name === 'this' && node.type === 'ThisExpression') 22 | ); 23 | } 24 | 25 | if ( 26 | node.type !== 'MemberExpression' 27 | || node.optional 28 | || node.computed 29 | || node.property.type !== 'Identifier' 30 | || node.property.name !== name 31 | ) { 32 | return false; 33 | } 34 | 35 | node = node.object; 36 | } 37 | } 38 | 39 | /** 40 | Check if node matches any object name or key path. 41 | 42 | @param {Node} node - The AST node to check. 43 | @param {string[]} nameOrPaths - The object name or key paths. 44 | @returns {boolean} 45 | */ 46 | function isNodeMatches(node, nameOrPaths) { 47 | return nameOrPaths.some(nameOrPath => isNodeMatchesNameOrPath(node, nameOrPath)); 48 | } 49 | 50 | module.exports = { 51 | isNodeMatchesNameOrPath, 52 | isNodeMatches, 53 | }; 54 | -------------------------------------------------------------------------------- /test/consistent-empty-array-spread.mjs: -------------------------------------------------------------------------------- 1 | import outdent from 'outdent'; 2 | import {getTester} from './utils/test.mjs'; 3 | 4 | const {test} = getTester(import.meta); 5 | 6 | test.snapshot({ 7 | valid: [ 8 | '[,,,]', 9 | '[...(test ? [] : [a, b])]', 10 | '[...(test ? [a, b] : [])]', 11 | '[...(test ? "" : "ab")]', 12 | '[...(test ? "ab" : "")]', 13 | '[...(test ? "" : unknown)]', 14 | '[...(test ? unknown : "")]', 15 | '[...(test ? [] : unknown)]', 16 | '[...(test ? unknown : [])]', 17 | '_ = {...(test ? "" : [a, b])}', 18 | '_ = {...(test ? [] : "ab")}', 19 | 'call(...(test ? "" : [a, b]))', 20 | 'call(...(test ? [] : "ab"))', 21 | '[...(test ? "ab" : [a, b])]', 22 | // Not checking 23 | 'const EMPTY_STRING = ""; [...(test ? EMPTY_STRING : [a, b])]', 24 | ], 25 | invalid: [ 26 | outdent` 27 | [ 28 | ...(test ? [] : "ab"), 29 | ...(test ? "ab" : []), 30 | ]; 31 | `, 32 | outdent` 33 | const STRING = "ab"; 34 | [ 35 | ...(test ? [] : STRING), 36 | ...(test ? STRING : []), 37 | ]; 38 | `, 39 | outdent` 40 | [ 41 | ...(test ? "" : [a, b]), 42 | ...(test ? [a, b] : ""), 43 | ]; 44 | `, 45 | outdent` 46 | const ARRAY = ["a", "b"]; 47 | [ 48 | /* hole */, 49 | ...(test ? "" : ARRAY), 50 | /* hole */, 51 | ...(test ? ARRAY : ""), 52 | /* hole */, 53 | ]; 54 | `, 55 | '[...(foo ? "" : [])]', 56 | ], 57 | }); 58 | -------------------------------------------------------------------------------- /docs/rules/prefer-blob-reading-methods.md: -------------------------------------------------------------------------------- 1 | # Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 6 | 7 | 8 | `FileReader` predates promises, and the newer [`Blob#arrayBuffer()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/arrayBuffer) and [`Blob#text()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/text) methods are much cleaner and easier to use. 9 | 10 | ## Fail 11 | 12 | ```js 13 | const arrayBuffer = await new Promise((resolve, reject) => { 14 | const fileReader = new FileReader(); 15 | fileReader.addEventListener('load', () => { 16 | resolve(fileReader.result); 17 | }); 18 | fileReader.addEventListener('error', () => { 19 | reject(fileReader.error); 20 | }); 21 | fileReader.readAsArrayBuffer(blob); 22 | }); 23 | ``` 24 | 25 | ```js 26 | fileReader.readAsText(blob); 27 | ``` 28 | 29 | ## Pass 30 | 31 | ```js 32 | const arrayBuffer = await blob.arrayBuffer(); 33 | ``` 34 | 35 | ```js 36 | const text = await blob.text(); 37 | ``` 38 | 39 | ```js 40 | fileReader.readAsText(blob, 'ascii'); 41 | ``` 42 | 43 | ```js 44 | fileReader.readAsDataURL(blob); 45 | ``` 46 | -------------------------------------------------------------------------------- /rules/no-empty-file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isEmptyNode, isDirective} = require('./ast/index.js'); 3 | 4 | const MESSAGE_ID = 'no-empty-file'; 5 | const messages = { 6 | [MESSAGE_ID]: 'Empty files are not allowed.', 7 | }; 8 | 9 | const isEmpty = node => isEmptyNode(node, isDirective); 10 | 11 | const isTripleSlashDirective = node => 12 | node.type === 'Line' && node.value.startsWith('/'); 13 | 14 | const hasTripeSlashDirectives = comments => 15 | comments.some(currentNode => isTripleSlashDirective(currentNode)); 16 | 17 | /** @param {import('eslint').Rule.RuleContext} context */ 18 | const create = context => { 19 | const filename = context.physicalFilename; 20 | 21 | if (!/\.(?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$/i.test(filename)) { 22 | return; 23 | } 24 | 25 | return { 26 | Program(node) { 27 | if (node.body.some(node => !isEmpty(node))) { 28 | return; 29 | } 30 | 31 | const {sourceCode} = context; 32 | const comments = sourceCode.getAllComments(); 33 | 34 | if (hasTripeSlashDirectives(comments)) { 35 | return; 36 | } 37 | 38 | return { 39 | node, 40 | messageId: MESSAGE_ID, 41 | }; 42 | }, 43 | }; 44 | }; 45 | 46 | /** @type {import('eslint').Rule.RuleModule} */ 47 | module.exports = { 48 | create, 49 | meta: { 50 | type: 'suggestion', 51 | docs: { 52 | description: 'Disallow empty files.', 53 | recommended: true, 54 | }, 55 | messages, 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /docs/rules/prefer-set-has.md: -------------------------------------------------------------------------------- 1 | # Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | [`Set#has()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) is faster than [`Array#includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). 11 | 12 | ## Fail 13 | 14 | ```js 15 | const array = [1, 2, 3]; 16 | const hasValue = value => array.includes(value); 17 | ``` 18 | 19 | ## Pass 20 | 21 | ```js 22 | const set = new Set([1, 2, 3]); 23 | const hasValue = value => set.has(value); 24 | ``` 25 | 26 | ```js 27 | // This array is not only checking existence. 28 | const array = [1, 2]; 29 | const hasValue = value => array.includes(value); 30 | array.push(3); 31 | ``` 32 | 33 | ```js 34 | // This array is only checked once. 35 | const array = [1, 2, 3]; 36 | const hasOne = array.includes(1); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/rules/prefer-string-trim-start-end.md: -------------------------------------------------------------------------------- 1 | # Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()` 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | [`String#trimLeft()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimLeft) and [`String#trimRight()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimRight) are aliases of [`String#trimStart()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart) and [`String#trimEnd()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd). This is to ensure consistency and use [direction](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Handling_different_text_directions)-independent wording. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const foo = bar.trimLeft(); 16 | const foo = bar.trimRight(); 17 | ``` 18 | 19 | ## Pass 20 | 21 | ```js 22 | const foo = bar.trimStart(); 23 | const foo = bar.trimEnd(); 24 | ``` 25 | -------------------------------------------------------------------------------- /rules/utils/should-add-parentheses-to-member-expression-object.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isNewExpressionWithParentheses = require('./is-new-expression-with-parentheses.js'); 4 | const {isDecimalIntegerNode} = require('./numeric.js'); 5 | 6 | /** 7 | Check if parentheses should to be added to a `node` when it's used as an `object` of `MemberExpression`. 8 | 9 | @param {Node} node - The AST node to check. 10 | @param {SourceCode} sourceCode - The source code object. 11 | @returns {boolean} 12 | */ 13 | function shouldAddParenthesesToMemberExpressionObject(node, sourceCode) { 14 | switch (node.type) { 15 | // This is not a full list. Some other nodes like `FunctionDeclaration` don't need parentheses, 16 | // but it's not possible to be in the place we are checking at this point. 17 | case 'Identifier': 18 | case 'MemberExpression': 19 | case 'CallExpression': 20 | case 'ChainExpression': 21 | case 'TemplateLiteral': 22 | case 'ThisExpression': 23 | case 'ArrayExpression': 24 | case 'FunctionExpression': { 25 | return false; 26 | } 27 | 28 | case 'NewExpression': { 29 | return !isNewExpressionWithParentheses(node, sourceCode); 30 | } 31 | 32 | case 'Literal': { 33 | /* c8 ignore next */ 34 | if (isDecimalIntegerNode(node)) { 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | default: { 42 | return true; 43 | } 44 | } 45 | } 46 | 47 | module.exports = shouldAddParenthesesToMemberExpressionObject; 48 | -------------------------------------------------------------------------------- /docs/rules/no-anonymous-default-export.md: -------------------------------------------------------------------------------- 1 | # Disallow anonymous functions and classes as the default export 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Naming default exports improves codebase searchability by ensuring consistent identifier use for a module's default export, both where it's declared and where it's imported. 11 | 12 | ## Fail 13 | 14 | ```js 15 | export default class {} 16 | ``` 17 | 18 | ```js 19 | export default function () {} 20 | ``` 21 | 22 | ```js 23 | export default () => {}; 24 | ``` 25 | 26 | ```js 27 | module.exports = class {}; 28 | ``` 29 | 30 | ```js 31 | module.exports = function () {}; 32 | ``` 33 | 34 | ```js 35 | module.exports = () => {}; 36 | ``` 37 | 38 | ## Pass 39 | 40 | ```js 41 | export default class Foo {} 42 | ``` 43 | 44 | ```js 45 | export default function foo () {} 46 | ``` 47 | 48 | ```js 49 | const foo = () => {}; 50 | export default foo; 51 | ``` 52 | 53 | ```js 54 | module.exports = class Foo {}; 55 | ``` 56 | 57 | ```js 58 | module.exports = function foo () {}; 59 | ``` 60 | 61 | ```js 62 | const foo = () => {}; 63 | module.exports = foo; 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/rules/no-single-promise-in-promise-methods.md: -------------------------------------------------------------------------------- 1 | # Disallow passing single-element arrays to `Promise` methods 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Passing a single-element array to `Promise.all()`, `Promise.any()`, or `Promise.race()` is likely a mistake. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const foo = await Promise.all([promise]); 16 | ``` 17 | 18 | ```js 19 | const foo = await Promise.any([promise]); 20 | ``` 21 | 22 | ```js 23 | const foo = await Promise.race([promise]); 24 | ``` 25 | 26 | ```js 27 | const promise = Promise.all([nonPromise]); 28 | ``` 29 | 30 | ## Pass 31 | 32 | ```js 33 | const foo = await promise; 34 | ``` 35 | 36 | ```js 37 | const promise = Promise.resolve(nonPromise); 38 | ``` 39 | 40 | ```js 41 | const foo = await Promise.all(promises); 42 | ``` 43 | 44 | ```js 45 | const foo = await Promise.any([promise, anotherPromise]); 46 | ``` 47 | 48 | ```js 49 | const [{value: foo, reason: error}] = await Promise.allSettled([promise]); 50 | ``` 51 | -------------------------------------------------------------------------------- /rules/no-magic-array-flat-depth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isMethodCall, isNumberLiteral} = require('./ast/index.js'); 3 | const {getCallExpressionTokens} = require('./utils/index.js'); 4 | 5 | const MESSAGE_ID = 'no-magic-array-flat-depth'; 6 | const messages = { 7 | [MESSAGE_ID]: 'Magic number as depth is not allowed.', 8 | }; 9 | 10 | /** @param {import('eslint').Rule.RuleContext} context */ 11 | const create = context => ({ 12 | CallExpression(callExpression) { 13 | if (!isMethodCall(callExpression, { 14 | method: 'flat', 15 | argumentsLength: 1, 16 | optionalCall: false, 17 | })) { 18 | return; 19 | } 20 | 21 | const [depth] = callExpression.arguments; 22 | 23 | if (!isNumberLiteral(depth) || depth.value === 1) { 24 | return; 25 | } 26 | 27 | const {sourceCode} = context; 28 | const { 29 | openingParenthesisToken, 30 | closingParenthesisToken, 31 | } = getCallExpressionTokens(sourceCode, callExpression); 32 | if (sourceCode.commentsExistBetween(openingParenthesisToken, closingParenthesisToken)) { 33 | return; 34 | } 35 | 36 | return { 37 | node: depth, 38 | messageId: MESSAGE_ID, 39 | }; 40 | }, 41 | }); 42 | 43 | /** @type {import('eslint').Rule.RuleModule} */ 44 | module.exports = { 45 | create, 46 | meta: { 47 | type: 'suggestion', 48 | docs: { 49 | description: 'Disallow a magic number as the `depth` argument in `Array#flat(…).`', 50 | recommended: true, 51 | }, 52 | messages, 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /docs/rules/better-regex.md: -------------------------------------------------------------------------------- 1 | # Improve regexes by making them shorter, consistent, and safer 2 | 3 | 🚫 This rule is _disabled_ in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | 10 | Note: This rule uses [`regexp-tree`](https://github.com/DmitrySoshnikov/regexp-tree) and [`clean-regexp`](https://github.com/samverschueren/clean-regexp) under the hood. 11 | 12 | ## Fail 13 | 14 | ```js 15 | const regex = /[0-9]/; 16 | const regex = /[^0-9]/; 17 | const regex = /[a-zA-Z0-9_]/; 18 | const regex = /[a-z0-9_]/i; 19 | const regex = /[^a-zA-Z0-9_]/; 20 | const regex = /[^a-z0-9_]/i; 21 | const regex = /[0-9]\.[a-zA-Z0-9_]\-[^0-9]/i; 22 | ``` 23 | 24 | ## Pass 25 | 26 | ```js 27 | const regex = /\d/; 28 | const regex = /\D/; 29 | const regex = /\w/; 30 | const regex = /\w/i; 31 | const regex = /\W/; 32 | const regex = /\W/i; 33 | const regex = /\d\.\w\-\D/i; 34 | ``` 35 | 36 | ## Options 37 | 38 | ### sortCharacterClasses 39 | 40 | Type: `boolean`\ 41 | Default: `true` 42 | 43 | Disables optimizations that affect the sorting of character classes. For example, preserves the order of the characters in `[AaQqTt]` rather than sorting it to `[AQTaqt]`. 44 | -------------------------------------------------------------------------------- /docs/rules/require-post-message-target-origin.md: -------------------------------------------------------------------------------- 1 | # Enforce using the `targetOrigin` argument with `window.postMessage()` 2 | 3 | 🚫 This rule is _disabled_ in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | When calling [`window.postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) without the `targetOrigin` argument, the message cannot be received by any window. 11 | 12 | This rule cannot distinguish between `window.postMessage()` and other calls like [`Worker#postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage), [`MessagePort#postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage), [`Client#postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Client/postMessage), and [`BroadcastChannel#postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel/postMessage). Use on your own risk. 13 | 14 | ## Fail 15 | 16 | ```js 17 | window.postMessage(message); 18 | ``` 19 | 20 | ## Pass 21 | 22 | ```js 23 | window.postMessage(message, 'https://example.com'); 24 | ``` 25 | 26 | ```js 27 | window.postMessage(message, '*'); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/rules/prefer-logical-operator-over-ternary.md: -------------------------------------------------------------------------------- 1 | # Prefer using a logical operator over a ternary 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). 4 | 5 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | 10 | Disallow ternary operators when simpler logical operator alternatives exist. 11 | 12 | Ideally, most reported cases have an equivalent [`Logical OR(||)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR) expression. The rule intentionally provides suggestions instead of auto-fixes, because in many cases, the [nullish coalescing operator (`??`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) should be preferred. 13 | 14 | ## Fail 15 | 16 | ```js 17 | foo ? foo : bar; 18 | ``` 19 | 20 | ```js 21 | foo.bar ? foo.bar : foo.baz 22 | ``` 23 | 24 | ```js 25 | foo?.bar ? foo.bar : baz 26 | ``` 27 | 28 | ```js 29 | !bar ? foo : bar; 30 | ``` 31 | 32 | ## Pass 33 | 34 | ```js 35 | foo ?? bar; 36 | ``` 37 | 38 | ```js 39 | foo || bar; 40 | ``` 41 | 42 | ```js 43 | foo ? bar : baz; 44 | ``` 45 | 46 | ```js 47 | foo.bar ?? foo.baz 48 | ``` 49 | 50 | ```js 51 | foo?.bar ?? baz 52 | ``` 53 | --------------------------------------------------------------------------------