├── .npmrc ├── .gitattributes ├── tests ├── lib │ ├── utils │ │ ├── type-tracker │ │ │ ├── fixture.ts │ │ │ ├── bigint.ts │ │ │ ├── regexp.ts │ │ │ ├── global.ts │ │ │ ├── boolean.ts │ │ │ ├── number.ts │ │ │ └── iterable.ts │ │ ├── unicode.ts │ │ └── refa.ts │ ├── meta.ts │ └── rules │ │ ├── __snapshots__ │ │ ├── no-empty-string-literal.ts.eslintsnap │ │ ├── prefer-named-capture-group.ts.eslintsnap │ │ ├── prefer-named-backreference.ts.eslintsnap │ │ ├── no-escape-backspace.ts.eslintsnap │ │ ├── no-empty-group.ts.eslintsnap │ │ ├── prefer-set-operation.ts.eslintsnap │ │ ├── no-non-standard-flag.ts.eslintsnap │ │ ├── confusing-quantifier.ts.eslintsnap │ │ ├── no-standalone-backslash.ts.eslintsnap │ │ ├── no-empty-capturing-group.ts.eslintsnap │ │ ├── no-invalid-regexp.ts.eslintsnap │ │ ├── no-useless-two-nums-quantifier.ts.eslintsnap │ │ ├── no-empty-character-class.ts.eslintsnap │ │ ├── prefer-regexp-exec.ts.eslintsnap │ │ ├── grapheme-string-literal.ts.eslintsnap │ │ ├── no-zero-quantifier.ts.eslintsnap │ │ ├── prefer-escape-replacement-dollar-char.ts.eslintsnap │ │ ├── sort-flags.ts.eslintsnap │ │ ├── prefer-plus-quantifier.ts.eslintsnap │ │ └── prefer-star-quantifier.ts.eslintsnap │ │ ├── no-zero-quantifier.ts │ │ ├── no-empty-group.ts │ │ ├── no-standalone-backslash.ts │ │ ├── no-escape-backspace.ts │ │ ├── prefer-named-capture-group.ts │ │ ├── no-useless-two-nums-quantifier.ts │ │ ├── no-invalid-regexp.ts │ │ ├── no-empty-string-literal.ts │ │ ├── prefer-set-operation.ts │ │ ├── no-non-standard-flag.ts │ │ ├── optimal-lookaround-quantifier.ts │ │ ├── confusing-quantifier.ts │ │ ├── no-empty-capturing-group.ts │ │ ├── no-octal.ts │ │ ├── prefer-named-backreference.ts │ │ ├── no-empty-alternative.ts │ │ ├── prefer-plus-quantifier.ts │ │ ├── prefer-star-quantifier.ts │ │ ├── no-useless-string-literal.ts │ │ ├── no-super-linear-backtracking.ts │ │ ├── prefer-predefined-assertion.ts │ │ ├── no-optional-assertion.ts │ │ ├── no-useless-set-operand.ts │ │ ├── no-empty-character-class.ts │ │ ├── no-potentially-useless-backreference.ts │ │ ├── no-contradiction-with-assertion.ts │ │ ├── no-trivially-nested-quantifier.ts │ │ ├── prefer-unicode-codepoint-escapes.ts │ │ ├── no-useless-lazy.ts │ │ ├── no-useless-range.ts │ │ ├── no-empty-lookarounds-assertion.ts │ │ ├── require-unicode-sets-regexp.ts │ │ ├── prefer-escape-replacement-dollar-char.ts │ │ ├── no-invisible-character.ts │ │ ├── no-misleading-capturing-group.ts │ │ ├── prefer-w.ts │ │ ├── control-character-escape.ts │ │ ├── prefer-regexp-exec.ts │ │ ├── no-control-character.ts │ │ ├── prefer-named-replacement.ts │ │ ├── prefer-question-quantifier.ts │ │ ├── no-useless-quantifier.ts │ │ ├── sort-flags.ts │ │ ├── no-obscure-range.ts │ │ ├── use-ignore-case.ts │ │ ├── prefer-quantifier.ts │ │ ├── no-super-linear-move.ts │ │ ├── strict.ts │ │ ├── no-extra-lookaround-assertions.ts │ │ ├── negation.ts │ │ ├── simplify-set-operations.ts │ │ ├── no-trivially-nested-assertion.ts │ │ └── hexadecimal-escape.ts └── fixtures │ └── integrations │ ├── eslint-plugin │ ├── test.js │ └── eslint.config.mjs │ └── eslint-plugin-legacy-config │ ├── test.js │ └── .eslintrc.js ├── .markdownlintignore ├── typings └── comment-parser │ └── index.d.ts ├── lib ├── utils │ ├── string-literal-parser │ │ ├── index.ts │ │ └── tokens.ts │ ├── type-tracker │ │ ├── index.ts │ │ ├── utils.ts │ │ └── type-data │ │ │ └── boolean.ts │ ├── regexp-ast │ │ ├── index.ts │ │ ├── ast.ts │ │ └── quantifier.ts │ ├── ast-utils │ │ └── index.ts │ ├── fix-simplify-quantifier.ts │ └── util.ts ├── configs │ ├── all.ts │ ├── recommended.ts │ ├── flat │ │ ├── all.ts │ │ └── recommended.ts │ └── rules │ │ └── all.ts ├── meta.ts ├── index.ts ├── rules │ ├── no-empty-capturing-group.ts │ ├── no-standalone-backslash.ts │ ├── no-empty-string-literal.ts │ ├── prefer-named-capture-group.ts │ ├── no-empty-group.ts │ ├── no-non-standard-flag.ts │ ├── prefer-named-backreference.ts │ ├── no-escape-backspace.ts │ └── prefer-regexp-exec.ts └── types.ts ├── .prettierrc.json ├── .nycrc.json ├── .markdownlint.json ├── docs ├── .vitepress │ ├── theme │ │ ├── components │ │ │ ├── state │ │ │ │ ├── index.js │ │ │ │ └── serialize.js │ │ │ └── components │ │ │ │ └── PgEditor.vue │ │ ├── Layout.vue │ │ └── index.ts │ └── stylelint.config.js ├── playground │ └── index.md └── rules │ ├── no-empty-group.md │ ├── no-non-standard-flag.md │ ├── prefer-w.md │ ├── no-useless-range.md │ ├── prefer-plus-quantifier.md │ ├── prefer-star-quantifier.md │ ├── no-empty-capturing-group.md │ ├── prefer-question-quantifier.md │ ├── use-ignore-case.md │ ├── no-trivially-nested-assertion.md │ ├── no-zero-quantifier.md │ ├── no-useless-two-nums-quantifier.md │ ├── no-escape-backspace.md │ ├── no-control-character.md │ ├── no-invisible-character.md │ ├── no-trivially-nested-quantifier.md │ ├── prefer-predefined-assertion.md │ ├── control-character-escape.md │ ├── prefer-escape-replacement-dollar-char.md │ ├── prefer-set-operation.md │ ├── prefer-named-backreference.md │ ├── no-octal.md │ ├── negation.md │ ├── no-useless-set-operand.md │ ├── prefer-regexp-test.md │ ├── prefer-unicode-codepoint-escapes.md │ ├── prefer-quantifier.md │ ├── no-useless-string-literal.md │ ├── no-empty-string-literal.md │ ├── no-empty-character-class.md │ ├── sort-alternatives.md │ ├── prefer-named-capture-group.md │ ├── no-standalone-backslash.md │ ├── no-dupe-characters-character-class.md │ ├── require-unicode-regexp.md │ └── sort-character-class-elements.md ├── tools ├── update.ts ├── lib │ ├── changesets-util.ts │ └── load-rules.ts ├── update-rules.ts └── update-rulesets.ts ├── .env-cmdrc ├── .github ├── ISSUE_TEMPLATE │ ├── other.md │ ├── feature_request.md │ ├── bug_report.md │ └── new_rule_request.md └── workflows │ ├── cron.yml │ ├── GHPages.yml │ └── Release.yml ├── tsconfig.build.json ├── renovate.json ├── .changeset ├── config.json └── README.md ├── .vscode ├── settings.json └── launch.json ├── tsconfig.json ├── LICENSE └── .devcontainer └── devcontainer.json /.npmrc: -------------------------------------------------------------------------------- 1 | force=true -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /tests/lib/utils/type-tracker/fixture.ts: -------------------------------------------------------------------------------- 1 | // fixture 2 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | LICENSE 3 | node_modules 4 | -------------------------------------------------------------------------------- /typings/comment-parser/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "../../node_modules/comment-parser" 2 | -------------------------------------------------------------------------------- /lib/utils/string-literal-parser/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./parser" 2 | export * from "./tokens" 3 | -------------------------------------------------------------------------------- /lib/configs/all.ts: -------------------------------------------------------------------------------- 1 | export { rules } from "./rules/all" 2 | 3 | export const plugins = ["regexp"] 4 | -------------------------------------------------------------------------------- /lib/utils/type-tracker/index.ts: -------------------------------------------------------------------------------- 1 | export { type TypeTracker, createTypeTracker } from "./tracker" 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "semi": false, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /tests/fixtures/integrations/eslint-plugin/test.js: -------------------------------------------------------------------------------- 1 | var reg = /[aa]/ 2 | var reg = /[a-zA-Z0-9_][0-9]/ 3 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": ["lib/**/*.ts"], 4 | "exclude": ["tests/**/*.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /lib/configs/recommended.ts: -------------------------------------------------------------------------------- 1 | export { rules } from "./rules/recommended" 2 | 3 | export const plugins = ["regexp"] 4 | -------------------------------------------------------------------------------- /tests/fixtures/integrations/eslint-plugin-legacy-config/test.js: -------------------------------------------------------------------------------- 1 | var reg = /[aa]/ 2 | var reg = /[a-zA-Z0-9_][0-9]/ 3 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "line-length": false, 3 | "no-inline-html": false, 4 | "single-title": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/state/index.js: -------------------------------------------------------------------------------- 1 | export * from "./deserialize.js" 2 | export * from "./serialize.js" 3 | -------------------------------------------------------------------------------- /tools/update.ts: -------------------------------------------------------------------------------- 1 | import "./update-rules" 2 | import "./update-rulesets" 3 | import "./update-docs" 4 | import "./update-readme" 5 | -------------------------------------------------------------------------------- /.env-cmdrc: -------------------------------------------------------------------------------- 1 | { 2 | "version": { 3 | "IN_VERSION_SCRIPT": "true" 4 | }, 5 | "version-ci": { 6 | "IN_VERSION_CI_SCRIPT": "true" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/configs/flat/all.ts: -------------------------------------------------------------------------------- 1 | import * as plugin from "../../index" 2 | export { rules } from "../rules/all" 3 | 4 | export const plugins = { regexp: plugin } 5 | -------------------------------------------------------------------------------- /lib/configs/flat/recommended.ts: -------------------------------------------------------------------------------- 1 | import * as plugin from "../../index" 2 | export { rules } from "../rules/recommended" 3 | 4 | export const plugins = { regexp: plugin } 5 | -------------------------------------------------------------------------------- /tests/fixtures/integrations/eslint-plugin/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import * as plugin from "../../../../dist/index.js" 2 | export default [plugin.configs["flat/recommended"]] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: An issue that doesn't fit into the other categories. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /docs/.vitepress/stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["stylelint-config-recommended-vue"], 3 | rules: { 4 | "no-descending-specificity": null, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /docs/playground/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "playground" 3 | --- 4 | 5 | # Playground 6 | 7 | 8 | 9 | The playground is [here](https://ota-meshi.github.io/eslint-plugin-regexp/playground/)!! 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/utils/regexp-ast/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common" 2 | export * from "./ast" 3 | export * from "./is-covered" 4 | export * from "./is-equals" 5 | export * from "./quantifier" 6 | export * from "./case-variation" 7 | export * from "./simplify-quantifier" 8 | -------------------------------------------------------------------------------- /lib/meta.ts: -------------------------------------------------------------------------------- 1 | // note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder 2 | // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- Get meta data 3 | export const { name, version } = require("../package.json") 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description** 11 | 15 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["tests/**/*", "tools/**/*", "typings/**/*", "docs/**/*"], 4 | "compilerOptions": { 5 | "removeComments": true /* Do not emit comments to output. */, 6 | "declaration": true /* Generates corresponding '.d.ts' file. */ 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/lib/meta.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | import * as plugin from "../../lib" 3 | import { version } from "../../package.json" 4 | const expectedMeta = { 5 | name: "eslint-plugin-regexp", 6 | version, 7 | } 8 | 9 | describe("Test for meta object", () => { 10 | it("A plugin should have a meta object.", () => { 11 | assert.deepStrictEqual(plugin.meta, expectedMeta) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /lib/configs/rules/all.ts: -------------------------------------------------------------------------------- 1 | import { rules as ruleLint } from "../../all-rules" 2 | import type { SeverityString } from "../../types" 3 | import { rules as recommendedRules } from "./recommended" 4 | 5 | const all: Record = {} 6 | for (const rule of ruleLint) { 7 | all[rule.meta.docs.ruleId] = "error" 8 | } 9 | export const rules = { 10 | ...all, 11 | ...recommendedRules, 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/integrations/eslint-plugin-legacy-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = { 4 | root: true, 5 | extends: [ 6 | // add more generic rulesets here, such as: 7 | // 'eslint:recommended', 8 | "plugin:regexp/recommended", 9 | ], 10 | rules: { 11 | // override/add rules settings here, such as: 12 | // 'regexp/rule-name': 'error' 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-empty-string-literal.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-empty-string-literal >> invalid 5 | Code: 6 | 1 | /[\q{}]/v 7 | | ^~~~ [1] 8 | 9 | [1] Unexpected empty string literal. 10 | --- 11 | 12 | 13 | Test: no-empty-string-literal >> invalid 14 | Code: 15 | 1 | /[\q{|}]/v 16 | | ^~~~~ [1] 17 | 18 | [1] Unexpected empty string literal. 19 | --- 20 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges", 5 | ":disableDependencyDashboard" 6 | ], 7 | "packageRules": [ 8 | { 9 | "updateTypes": ["minor", "patch", "pin", "digest"], 10 | "automerge": true 11 | }, 12 | { 13 | "depTypeList": ["devDependencies"], 14 | "automerge": true 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tools/lib/changesets-util.ts: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import getReleasePlan from "@changesets/get-release-plan" 3 | 4 | /** Get new version string from changesets */ 5 | export async function getNewVersion(): Promise { 6 | const releasePlan = await getReleasePlan(path.resolve(__dirname, "../..")) 7 | 8 | return releasePlan.releases.find( 9 | ({ name }) => name === "eslint-plugin-regexp", 10 | )!.newVersion 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils/string-literal-parser/tokens.ts: -------------------------------------------------------------------------------- 1 | export interface BaseToken { 2 | type: string 3 | value: string 4 | range: [number, number] 5 | } 6 | export type Token = CharacterToken | EscapeToken 7 | export interface CharacterToken extends BaseToken { 8 | type: "CharacterToken" 9 | } 10 | export interface EscapeToken extends BaseToken { 11 | type: "EscapeToken" 12 | kind: "special" | "eol" | "unicode" | "hex" | "octal" | "char" 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Information:** 11 | - ESLint version: 12 | - `eslint-plugin-regexp` version: 13 | 14 | **Description** 15 | 21 | -------------------------------------------------------------------------------- /tests/lib/rules/no-zero-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-zero-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-zero-quantifier", rule as any, { 12 | valid: [`/a{0,1}/`, `/a{0,}/`], 13 | invalid: [`/a{0}/`, `/a{0}/v`, `/a{0,0}/`, `/a{0,0}?b/`, `/(a){0}/`], 14 | }) 15 | -------------------------------------------------------------------------------- /tests/lib/rules/no-empty-group.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-empty-group" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-empty-group", rule as any, { 12 | valid: ["/(a)/", "/(a|)/", "/(?:a|)/", String.raw`/(?:a|[\q{}])/v`], 13 | invalid: ["/()/", "/(?:)/", "/(|)/", "/(?:|)/"], 14 | }) 15 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config/schema.json", 3 | "changelog": [ 4 | "@svitejs/changesets-changelog-github-compact", 5 | { 6 | "repo": "ota-meshi/eslint-plugin-regexp" 7 | } 8 | ], 9 | "commit": false, 10 | "linked": [], 11 | "access": "restricted", 12 | "baseBranch": "master", 13 | "updateInternalDependencies": "patch", 14 | "bumpVersionsWithWorkspaceProtocolOnly": true, 15 | "ignore": [] 16 | } 17 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/prefer-named-capture-group.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: prefer-named-capture-group >> invalid 5 | Code: 6 | 1 | /(foo)/ 7 | | ^~~~~ [1] 8 | 9 | [1] Capture group '(foo)' should be converted to a named or non-capturing group. 10 | --- 11 | 12 | 13 | Test: prefer-named-capture-group >> invalid 14 | Code: 15 | 1 | /(foo)/v 16 | | ^~~~~ [1] 17 | 18 | [1] Capture group '(foo)' should be converted to a named or non-capturing group. 19 | --- 20 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/prefer-named-backreference.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: prefer-named-backreference >> invalid 5 | Code: 6 | 1 | /(?a)\1/ 7 | | ^~ [1] 8 | 9 | Output: 10 | 1 | /(?a)\k/ 11 | 12 | [1] Unexpected unnamed backreference. 13 | --- 14 | 15 | 16 | Test: prefer-named-backreference >> invalid 17 | Code: 18 | 1 | /(?a)\1/v 19 | | ^~ [1] 20 | 21 | Output: 22 | 1 | /(?a)\k/v 23 | 24 | [1] Unexpected unnamed backreference. 25 | --- 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_rule_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New rule request 3 | about: Suggest a new rule. 4 | title: '' 5 | labels: enhancement, new rule 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Motivation** 11 | 12 | 13 | **Description** 14 | 15 | 16 | **Examples** 17 | 18 | 19 | 20 | ```js 21 | /* ✓ GOOD */ 22 | var foo = /regex/ 23 | 24 | /* ✗ BAD */ 25 | var foo = /regex/ 26 | ``` 27 | -------------------------------------------------------------------------------- /tests/lib/rules/no-standalone-backslash.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-standalone-backslash" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-standalone-backslash", rule as any, { 12 | valid: [String.raw`/\cX/`, String.raw`/[[\cA-\cZ]--\cX]/v`], 13 | invalid: [ 14 | String.raw`/\c/`, 15 | String.raw`/\c-/`, 16 | String.raw`/\c1/`, 17 | String.raw`/[\c]/`, 18 | ], 19 | }) 20 | -------------------------------------------------------------------------------- /lib/utils/ast-utils/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | getParent, 3 | findVariable, 4 | getStringIfConstant, 5 | getStaticValue, 6 | getScope, 7 | isKnownMethodCall, 8 | parseReplacements, 9 | } from "./utils" 10 | export type { KnownMethodCall, ReferenceElement } from "./utils" 11 | export { extractExpressionReferences } from "./extract-expression-references" 12 | export type { ExpressionReference } from "./extract-expression-references" 13 | export { extractPropertyReferences } from "./extract-property-references" 14 | export * from "./regex" 15 | export type { PropertyReference } from "./extract-property-references" 16 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-escape-backspace.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-escape-backspace >> invalid 5 | Code: 6 | 1 | /[\b]/ 7 | | ^~ [1] 8 | 9 | [1] Unexpected '[\b]'. Use '\u0008' instead. 10 | Suggestions: 11 | - Use '\u0008'. 12 | Output: 13 | 1 | /[\u0008]/ 14 | --- 15 | 16 | 17 | Test: no-escape-backspace >> invalid 18 | Code: 19 | 1 | /[\q{\b}]/v 20 | | ^~ [1] 21 | 22 | [1] Unexpected '[\b]'. Use '\u0008' instead. 23 | Suggestions: 24 | - Use '\u0008'. 25 | Output: 26 | 1 | /[\q{\u0008}]/v 27 | --- 28 | -------------------------------------------------------------------------------- /tests/lib/rules/no-escape-backspace.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-escape-backspace" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-escape-backspace", rule as any, { 12 | valid: [ 13 | String.raw`/\b/`, 14 | String.raw`/\u0008/`, 15 | String.raw`/\ch/`, 16 | String.raw`/\cH/`, 17 | String.raw`/[\q{\u0008}]/v`, 18 | ], 19 | invalid: [String.raw`/[\b]/`, String.raw`/[\q{\b}]/v`], 20 | }) 21 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-named-capture-group.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-named-capture-group" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-named-capture-group", rule as any, { 12 | valid: [ 13 | String.raw`/foo/`, 14 | String.raw`/b(?:a(?:r))/`, 15 | String.raw`/(?bar)/`, 16 | String.raw`/(?=a)(?<=b)/`, 17 | ], 18 | invalid: [String.raw`/(foo)/`, String.raw`/(foo)/v`], 19 | }) 20 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-empty-group.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-empty-group >> invalid 5 | Code: 6 | 1 | /()/ 7 | | ^~ [1] 8 | 9 | [1] Unexpected empty group. 10 | --- 11 | 12 | 13 | Test: no-empty-group >> invalid 14 | Code: 15 | 1 | /(?:)/ 16 | | ^~~~ [1] 17 | 18 | [1] Unexpected empty group. 19 | --- 20 | 21 | 22 | Test: no-empty-group >> invalid 23 | Code: 24 | 1 | /(|)/ 25 | | ^~~ [1] 26 | 27 | [1] Unexpected empty group. 28 | --- 29 | 30 | 31 | Test: no-empty-group >> invalid 32 | Code: 33 | 1 | /(?:|)/ 34 | | ^~~~~ [1] 35 | 36 | [1] Unexpected empty group. 37 | --- 38 | -------------------------------------------------------------------------------- /tests/lib/rules/no-useless-two-nums-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-useless-two-nums-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-useless-two-nums-quantifier", rule as any, { 12 | valid: ["/a{1,2}/", "/a{1,}/", "/a{1}/", "/a?/"], 13 | invalid: [ 14 | "/a{1,1}/", 15 | "/a{42,42}/", 16 | "/a{042,42}/", 17 | "/a{042,042}/", 18 | "/a{100,100}?/", 19 | "/a{100,100}?/v", 20 | ], 21 | }) 22 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import { rules as ruleList } from "./all-rules" 2 | import * as all from "./configs/all" 3 | import * as flatAll from "./configs/flat/all" 4 | import * as flatRecommended from "./configs/flat/recommended" 5 | import * as recommended from "./configs/recommended" 6 | import type { RuleModule } from "./types" 7 | export * as meta from "./meta" 8 | 9 | export const configs = { 10 | recommended, 11 | all, 12 | "flat/all": flatAll, 13 | "flat/recommended": flatRecommended, 14 | } 15 | export const rules = ruleList.reduce( 16 | (obj, r) => { 17 | obj[r.meta.docs.ruleName] = r 18 | return obj 19 | }, 20 | {} as { [key: string]: RuleModule }, 21 | ) 22 | -------------------------------------------------------------------------------- /tests/lib/rules/no-invalid-regexp.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-invalid-regexp" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-invalid-regexp", rule as any, { 12 | valid: [`/regexp/`, `RegExp("(" + ")")`], 13 | invalid: [ 14 | `RegExp("(")`, 15 | `RegExp("(" + "(")`, 16 | `RegExp("[a-Z] some valid stuff")`, 17 | 18 | "new RegExp(pattern, 'uu');", 19 | "new RegExp(pattern, 'uv');", 20 | "new RegExp('[A&&&]', 'v');", 21 | ], 22 | }) 23 | -------------------------------------------------------------------------------- /tests/lib/rules/no-empty-string-literal.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-empty-string-literal" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-empty-string-literal", rule as any, { 12 | valid: [ 13 | String.raw`/[\q{a}]/v`, 14 | String.raw`/[\q{abc}]/v`, 15 | String.raw`/[\q{a|}]/v`, 16 | String.raw`/[\q{abc|}]/v`, 17 | String.raw`/[\q{|a}]/v`, 18 | String.raw`/[\q{|abc}]/v`, 19 | ], 20 | invalid: [String.raw`/[\q{}]/v`, String.raw`/[\q{|}]/v`], 21 | }) 22 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-set-operation.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-set-operation" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-set-operation", rule as any, { 12 | valid: [ 13 | String.raw`/a\b/`, 14 | String.raw`/a\b/u`, 15 | String.raw`/a\b/v`, 16 | String.raw`/(?!a)\w/`, 17 | String.raw`/(?!a)\w/u`, 18 | ], 19 | invalid: [ 20 | String.raw`/(?!a)\w/v`, 21 | String.raw`/\w(?<=\d)/v`, 22 | String.raw`/(?!-)&/v`, 23 | ], 24 | }) 25 | -------------------------------------------------------------------------------- /tests/lib/rules/no-non-standard-flag.ts: -------------------------------------------------------------------------------- 1 | import * as tsParser from "@typescript-eslint/parser" 2 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 3 | import rule from "../../../lib/rules/no-non-standard-flag" 4 | 5 | const tester = new SnapshotRuleTester({ 6 | languageOptions: { 7 | parser: tsParser, 8 | ecmaVersion: "latest", 9 | sourceType: "module", 10 | }, 11 | }) 12 | 13 | tester.run("no-non-standard-flag", rule as any, { 14 | valid: [`/foo/gimsuy`, `/foo/v`], 15 | invalid: [ 16 | `/fo*o*/l`, 17 | `RegExp("foo", "l")`, 18 | 19 | // unknown pattern 20 | `RegExp(someVariable, "l")`, 21 | // invalid pattern 22 | `RegExp("(", "l")`, 23 | ], 24 | }) 25 | -------------------------------------------------------------------------------- /tests/lib/rules/optimal-lookaround-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/optimal-lookaround-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("optimal-lookaround-quantifier", rule as any, { 12 | valid: [String.raw`/(?=(a*))\w+\1/`, `/(?<=a{4})/`, `/(?=a(?:(a)|b)*)/`], 13 | invalid: [ 14 | `/(?=ba*)/`, 15 | `/(?=ba*)/v`, 16 | `/(?=(?:a|b|abc*))/`, 17 | `/(?=(?:a|b|abc+))/`, 18 | `/(?=(?:a|b|abc{4,9}))/`, 19 | `/(?<=[a-c]*)/`, 20 | `/(?<=(?:d|c)*ab)/`, 21 | ], 22 | }) 23 | -------------------------------------------------------------------------------- /tests/lib/rules/confusing-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/confusing-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("confusing-quantifier", rule as any, { 12 | valid: [ 13 | String.raw`/a+/`, 14 | String.raw`/a?/`, 15 | String.raw`/(a|b?)*/`, 16 | String.raw`/(a?){0,3}/`, 17 | String.raw`/(a|\b)+/`, 18 | String.raw`/[\q{a|b}]+/v`, 19 | ], 20 | invalid: [ 21 | String.raw`/(a?){5}/`, 22 | String.raw`/(?:a?b*|c+){4}/`, 23 | String.raw`/[\q{a|}]+/v`, 24 | ], 25 | }) 26 | -------------------------------------------------------------------------------- /tests/lib/rules/no-empty-capturing-group.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-empty-capturing-group" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-empty-capturing-group", rule as any, { 12 | valid: [ 13 | "/(a)/", 14 | String.raw`/a(\bb)/`, 15 | String.raw`/a(\b|b)/`, 16 | String.raw`/a([\q{a}])/v`, 17 | ], 18 | invalid: [ 19 | String.raw`/a(\b)/`, 20 | "/a($)/", 21 | "/(^)a/", 22 | "/()a/", 23 | String.raw`/(\b\b|(?:\B|$))a/`, 24 | String.raw`/a([\q{}])/v`, 25 | ], 26 | }) 27 | -------------------------------------------------------------------------------- /tests/lib/rules/no-octal.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-octal" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-octal", rule as any, { 12 | valid: [ 13 | String.raw`/\0/`, 14 | String.raw`/[\7]/`, 15 | String.raw`/[\1-\4]/`, 16 | String.raw`/[\q{\0}]/v`, 17 | ], 18 | invalid: [ 19 | String.raw`/\07/`, 20 | String.raw`/\077/`, 21 | String.raw`/[\077]/`, 22 | String.raw`/\0777/`, 23 | String.raw`/\7/`, 24 | String.raw`/\1\2/`, 25 | String.raw`/()\1\2/`, 26 | ], 27 | }) 28 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/prefer-set-operation.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: prefer-set-operation >> invalid 5 | Code: 6 | 1 | /(?!a)\w/v 7 | | ^~~~~ [1] 8 | 9 | Output: 10 | 1 | /[\w--a]/v 11 | 12 | [1] This lookaround can be combined with '\w' using a set operation. 13 | --- 14 | 15 | 16 | Test: prefer-set-operation >> invalid 17 | Code: 18 | 1 | /\w(?<=\d)/v 19 | | ^~~~~~~ [1] 20 | 21 | Output: 22 | 1 | /[\w&&\d]/v 23 | 24 | [1] This lookaround can be combined with '\w' using a set operation. 25 | --- 26 | 27 | 28 | Test: prefer-set-operation >> invalid 29 | Code: 30 | 1 | /(?!-)&/v 31 | | ^~~~~ [1] 32 | 33 | Output: 34 | 1 | /[\&--\-]/v 35 | 36 | [1] This lookaround can be combined with '&' using a set operation. 37 | --- 38 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-named-backreference.ts: -------------------------------------------------------------------------------- 1 | import { ESLint } from "eslint" 2 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 3 | import semver from "semver" 4 | import rule from "../../../lib/rules/prefer-named-backreference" 5 | 6 | const tester = new SnapshotRuleTester({ 7 | languageOptions: { 8 | ecmaVersion: "latest", 9 | sourceType: "module", 10 | }, 11 | }) 12 | 13 | tester.run("prefer-named-backreference", rule as any, { 14 | valid: [ 15 | `/(a)\\1/`, 16 | `/(?a)\\k/`, 17 | `/(a)\\1 (?a)\\k/`, 18 | // ES2025 19 | ...(semver.gte(ESLint.version, "9.6.0") 20 | ? [`/((?a)|(?b))\\1/`] 21 | : []), 22 | ], 23 | invalid: [`/(?a)\\1/`, `/(?a)\\1/v`], 24 | }) 25 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-non-standard-flag.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-non-standard-flag >> invalid 5 | Code: 6 | 1 | /fo*o*/l 7 | | ^ [1] 8 | 9 | [1] Unexpected non-standard flag 'l'. 10 | --- 11 | 12 | 13 | Test: no-non-standard-flag >> invalid 14 | Code: 15 | 1 | RegExp("foo", "l") 16 | | ^ [1] 17 | 18 | [1] Unexpected non-standard flag 'l'. 19 | --- 20 | 21 | 22 | Test: no-non-standard-flag >> invalid 23 | Code: 24 | 1 | RegExp(someVariable, "l") 25 | | ^ [1] 26 | 27 | [1] Unexpected non-standard flag 'l'. 28 | --- 29 | 30 | 31 | Test: no-non-standard-flag >> invalid 32 | Code: 33 | 1 | RegExp("(", "l") 34 | | ^ [1] 35 | 36 | [1] Unexpected non-standard flag 'l'. 37 | --- 38 | -------------------------------------------------------------------------------- /tests/lib/rules/no-empty-alternative.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-empty-alternative" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-empty-alternative", rule as any, { 12 | valid: [`/()|(?:)|(?=)/`, `/(?:)/`, `/a*|b+/`, String.raw`/[\q{a|b}]/v`], 13 | invalid: [ 14 | `/|||||/`, 15 | `/(a+|b+|)/`, 16 | String.raw`/(?:\|\|||\|)/`, 17 | String.raw`/(?a|b|)/`, 18 | String.raw`/(?:a|b|)f/`, 19 | String.raw`/(?:a|b|)+f/`, 20 | String.raw`/[\q{a|}]/v`, 21 | String.raw`/[\q{|a}]/v`, 22 | String.raw`/[\q{a||b}]/v`, 23 | ], 24 | }) 25 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-plus-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-plus-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-plus-quantifier", rule as any, { 12 | valid: ["/a+/", "/a+?/", "/(a+)/", "/(a+?)/", "/[a{1,}]/", "/a{1,2}/"], 13 | invalid: [ 14 | "/a{1,}/", 15 | "/a{1,}?/", 16 | "/(a){1,}/", 17 | "/(a){1,}/v", 18 | "/(a){1,}?/", 19 | ` 20 | const s = "a{1,}" 21 | new RegExp(s) 22 | `, 23 | ` 24 | const s = "a{1"+",}" 25 | new RegExp(s) 26 | `, 27 | ], 28 | }) 29 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-star-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-star-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-star-quantifier", rule as any, { 12 | valid: ["/a*/", "/a*?/", "/(a*)/", "/(a*?)/", "/[a{0,}]/", "/a{0,10}/"], 13 | invalid: [ 14 | "/a{0,}/", 15 | "/a{0,}?/", 16 | "/(a){0,}/", 17 | "/(a){0,}/v", 18 | "/(a){0,}?/", 19 | ` 20 | const s = "a{0,}" 21 | new RegExp(s) 22 | `, 23 | ` 24 | const s = "a{0"+",}" 25 | new RegExp(s) 26 | `, 27 | ], 28 | }) 29 | -------------------------------------------------------------------------------- /tools/lib/load-rules.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import path from "path" 3 | 4 | /** 5 | * Get the all rules 6 | * @returns {Array} The all rules 7 | */ 8 | function readRules() { 9 | const rulesLibRoot = path.resolve(__dirname, "../../lib/rules") 10 | const result = fs.readdirSync(rulesLibRoot) 11 | const rules = [] 12 | for (const name of result) { 13 | const ruleName = name.replace(/\.ts$/u, "") 14 | const ruleId = `regexp/${ruleName}` 15 | // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports -- ignore 16 | const rule = require(path.join(rulesLibRoot, name)).default 17 | 18 | rule.meta.docs.ruleName = ruleName 19 | rule.meta.docs.ruleId = ruleId 20 | 21 | rules.push(rule) 22 | } 23 | return rules 24 | } 25 | 26 | export const rules = readRules() 27 | -------------------------------------------------------------------------------- /tests/lib/utils/type-tracker/bigint.ts: -------------------------------------------------------------------------------- 1 | import type { TestCase } from "./test-utils" 2 | import { testTypeTrackerWithLinter } from "./test-utils" 3 | 4 | const TESTCASES: TestCase[] = [ 5 | { 6 | code: ` 7 | BigInt(0) 8 | `, 9 | type: "BigInt", 10 | }, 11 | { 12 | code: ` 13 | const a = [123n,234n] 14 | new Set(a) 15 | `, 16 | type: "Set", 17 | }, 18 | { 19 | code: ` 20 | for (const e of 123n) { 21 | e 22 | } 23 | `, 24 | type: [], 25 | }, 26 | { 27 | code: `123n()`, 28 | type: [], 29 | }, 30 | ] 31 | describe("type track for BigInt", () => { 32 | for (const testCase of TESTCASES) { 33 | it(testCase.code, () => { 34 | testTypeTrackerWithLinter(testCase) 35 | }) 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /tests/lib/utils/type-tracker/regexp.ts: -------------------------------------------------------------------------------- 1 | import type { TestCase } from "./test-utils" 2 | import { testTypeTrackerWithLinter } from "./test-utils" 3 | 4 | const TESTCASES: TestCase[] = [ 5 | { 6 | code: ` 7 | RegExp('a') 8 | `, 9 | type: "RegExp", 10 | }, 11 | { 12 | code: ` 13 | RegExp.lastMatch 14 | `, 15 | type: "Number", 16 | }, 17 | { 18 | code: ` 19 | for (const e of /a/) { 20 | e 21 | } 22 | `, 23 | type: [], 24 | }, 25 | { 26 | code: ` 27 | const a = /a/ 28 | a() 29 | `, 30 | type: [], 31 | }, 32 | ] 33 | describe("type track for RegExp", () => { 34 | for (const testCase of TESTCASES) { 35 | it(testCase.code, () => { 36 | testTypeTrackerWithLinter(testCase) 37 | }) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | "vue", 6 | "typescript", 7 | "json", 8 | "jsonc", 9 | "yaml" 10 | ], 11 | "typescript.validate.enable": true, 12 | "javascript.validate.enable": false, 13 | "vetur.validation.script": false, 14 | "vetur.validation.style": false, 15 | "css.validate": false, 16 | "typescript.tsdk": "./node_modules/typescript/lib", 17 | "editor.codeActionsOnSave": { 18 | "source.fixAll.eslint": "explicit", 19 | "source.fixAll.stylelint": "explicit" 20 | }, 21 | "stylelint.validate": [ 22 | "css", 23 | "html", 24 | "less", 25 | "postcss", 26 | "sass", 27 | "scss", 28 | "vue", 29 | "vue-html", 30 | "vue-postcss", 31 | "stylus" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /tests/lib/rules/no-useless-string-literal.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-useless-string-literal" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-useless-string-literal", rule as any, { 12 | valid: [String.raw`/[\q{abc}]/v`, String.raw`/[\q{ab|}]/v`], 13 | invalid: [ 14 | String.raw`/[\q{a}]/v`, 15 | String.raw`/[\q{a|bc}]/v`, 16 | String.raw`/[\q{ab|c}]/v`, 17 | String.raw`/[\q{ab|c|de}]/v`, 18 | String.raw`/[a\q{ab|\-}]/v`, 19 | String.raw`/[\q{ab|^}]/v`, 20 | String.raw`/[\q{ab|c}&&\q{ab}]/v`, 21 | String.raw`/[A&&\q{&}]/v`, 22 | String.raw`/[\q{&}&&A]/v`, 23 | String.raw`/[A&&\q{^|ab}]/v`, 24 | ], 25 | }) 26 | -------------------------------------------------------------------------------- /tests/lib/utils/type-tracker/global.ts: -------------------------------------------------------------------------------- 1 | import type { TestCase } from "./test-utils" 2 | import { testTypeTrackerWithLinter } from "./test-utils" 3 | 4 | const TESTCASES: TestCase[] = [ 5 | { 6 | code: ` 7 | const m = globalThis 8 | m 9 | `, 10 | type: "Global", 11 | }, 12 | { 13 | code: ` 14 | globalThis.BigInt(1) 15 | `, 16 | type: "BigInt", 17 | }, 18 | { 19 | code: ` 20 | globalThis(1) 21 | `, 22 | type: [], 23 | }, 24 | { 25 | code: ` 26 | for (const e of globalThis) { 27 | e 28 | } 29 | `, 30 | type: [], 31 | }, 32 | ] 33 | describe("type track for global", () => { 34 | for (const testCase of TESTCASES) { 35 | it(testCase.code, () => { 36 | testTypeTrackerWithLinter(testCase) 37 | }) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/components/PgEditor.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /tests/lib/rules/no-super-linear-backtracking.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-super-linear-backtracking" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-super-linear-backtracking", rule as any, { 12 | valid: [ 13 | String.raw`/regexp/`, 14 | String.raw`/a+b+a+b+/`, 15 | String.raw`/\w+\b[\w-]+/`, 16 | String.raw`/[\q{ab}]*[\q{ab}]*$/v`, // Limitation of scslre 17 | ], 18 | invalid: [ 19 | // self 20 | String.raw`/b(?:a+)+b/`, 21 | String.raw`/(?:ba+|a+b){2}/`, 22 | 23 | // trade 24 | String.raw`/\ba+a+$/`, 25 | String.raw`/\b\w+a\w+$/`, 26 | String.raw`/\b\w+a?b{4}\w+$/`, 27 | String.raw`/[\q{a}]*b?[\q{a}]+$/v`, 28 | ], 29 | }) 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "lib": ["es2023"], 7 | "allowJs": true, 8 | "checkJs": true, 9 | "outDir": "./dist", 10 | "strict": true, 11 | "noImplicitAny": true, 12 | 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "*": ["typings/*"] 20 | }, 21 | "esModuleInterop": true, 22 | "resolveJsonModule": true, 23 | 24 | "skipLibCheck": true 25 | }, 26 | "include": [ 27 | "lib/**/*", 28 | "tests/**/*", 29 | "tools/**/*", 30 | "typings/**/*", 31 | "docs/.vitepress/**/*.ts", 32 | "docs/.vitepress/**/*.mts" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "cwd": "${workspaceFolder}/tests", 13 | "args": [ 14 | "-r", 15 | "ts-node/register", 16 | "--timeout", 17 | "999999", 18 | "--colors", 19 | "'${workspaceFolder}/tests/**/*.ts'" 20 | ], 21 | "console": "integratedTerminal", 22 | "internalConsoleOptions": "neverOpen", 23 | "protocol": "inspector" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-predefined-assertion.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-predefined-assertion" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-predefined-assertion", rule as any, { 12 | valid: [String.raw`/a(?=\W)/`, String.raw`/a(?=\W)/v`], 13 | invalid: [ 14 | String.raw`/a(?=\w)/`, 15 | String.raw`/a(?!\w)/`, 16 | String.raw`/(?<=\w)a/`, 17 | String.raw`/(?> invalid 5 | Code: 6 | 1 | /(a?){5}/ 7 | | ^~~ [1] 8 | 9 | [1] This quantifier is confusing because its minimum is 5 but it can match the empty string. Maybe replace it with `{0,5}` to reflect that it can match the empty string? 10 | --- 11 | 12 | 13 | Test: confusing-quantifier >> invalid 14 | Code: 15 | 1 | /(?:a?b*|c+){4}/ 16 | | ^~~ [1] 17 | 18 | [1] This quantifier is confusing because its minimum is 4 but it can match the empty string. Maybe replace it with `{0,4}` to reflect that it can match the empty string? 19 | --- 20 | 21 | 22 | Test: confusing-quantifier >> invalid 23 | Code: 24 | 1 | /[\q{a|}]+/v 25 | | ^ [1] 26 | 27 | [1] This quantifier is confusing because its minimum is 1 but it can match the empty string. Maybe replace it with `*` to reflect that it can match the empty string? 28 | --- 29 | -------------------------------------------------------------------------------- /tests/lib/rules/no-optional-assertion.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-optional-assertion" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-optional-assertion", rule as any, { 12 | valid: [ 13 | String.raw`/fo(?:o\b)?/`, 14 | String.raw`/(?:a|(\b|-){2})?/`, 15 | String.raw`/(?:a|(?:\b|a)+)?/`, 16 | String.raw`/fo(?:o\b)/`, 17 | String.raw`/fo(?:o\b){1}/`, 18 | String.raw`/(?:(?=[\q{a}]))/v`, 19 | ], 20 | invalid: [ 21 | String.raw`/(?:\b|(?=a))?/`, 22 | String.raw`/(?:\b|a)?/`, 23 | String.raw`/(?:^|a)*/`, 24 | String.raw`/(?:((?:(\b|a)))|b)?/`, 25 | String.raw`/(?:((?:(\b|a)))|b)*/`, 26 | String.raw`/((\b)+){0,}/`, 27 | String.raw`/(?:(?=[\q{a}]))?/v`, 28 | ], 29 | }) 30 | -------------------------------------------------------------------------------- /tests/lib/rules/no-useless-set-operand.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-useless-set-operand" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-useless-set-operand", rule as any, { 12 | valid: [String.raw`/[\w--\d]/v`], 13 | invalid: [ 14 | String.raw`/[\w&&\d]/v`, 15 | String.raw`/[\w&&\s]/v`, 16 | String.raw`/[^\w&&\s]/v`, 17 | String.raw`/[\w&&[\d\s]]/v`, 18 | String.raw`/[\w&&[^\d\s]]/v`, 19 | String.raw`/[\w--\s]/v`, 20 | String.raw`/[\d--\w]/v`, 21 | String.raw`/[^\d--\w]/v`, 22 | String.raw`/[\w--[\d\s]]/v`, 23 | String.raw`/[\w--[^\d\s]]/v`, 24 | String.raw`/[\w--[a\q{aa|b}]]/v`, 25 | String.raw`/[\w--[a\q{aa}]]/v`, 26 | String.raw`/[\w--\q{a|aa}]/v`, 27 | ], 28 | }) 29 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/Layout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 39 | -------------------------------------------------------------------------------- /tests/lib/utils/type-tracker/boolean.ts: -------------------------------------------------------------------------------- 1 | import type { TestCase } from "./test-utils" 2 | import { testTypeTrackerWithLinter } from "./test-utils" 3 | 4 | const TESTCASES: TestCase[] = [ 5 | { 6 | code: ` 7 | Boolean(0) 8 | `, 9 | type: "Boolean", 10 | }, 11 | { 12 | code: ` 13 | const a = true 14 | a.valueOf() 15 | `, 16 | type: "Boolean", 17 | }, 18 | { 19 | code: ` 20 | Boolean.foo 21 | `, 22 | type: [], 23 | }, 24 | { 25 | code: ` 26 | for (const e of true) { 27 | e 28 | } 29 | `, 30 | type: [], 31 | }, 32 | { 33 | code: ` 34 | const a = true 35 | a() 36 | `, 37 | type: [], 38 | }, 39 | ] 40 | describe("type track for boolean", () => { 41 | for (const testCase of TESTCASES) { 42 | it(testCase.code, () => { 43 | testTypeTrackerWithLinter(testCase) 44 | }) 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-standalone-backslash.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-standalone-backslash >> invalid 5 | Code: 6 | 1 | /\c/ 7 | | ^ [1] 8 | 9 | [1] Unexpected standalone backslash (`\`). It looks like an escape sequence, but it's a single `\` character pattern. 10 | --- 11 | 12 | 13 | Test: no-standalone-backslash >> invalid 14 | Code: 15 | 1 | /\c-/ 16 | | ^ [1] 17 | 18 | [1] Unexpected standalone backslash (`\`). It looks like an escape sequence, but it's a single `\` character pattern. 19 | --- 20 | 21 | 22 | Test: no-standalone-backslash >> invalid 23 | Code: 24 | 1 | /\c1/ 25 | | ^ [1] 26 | 27 | [1] Unexpected standalone backslash (`\`). It looks like an escape sequence, but it's a single `\` character pattern. 28 | --- 29 | 30 | 31 | Test: no-standalone-backslash >> invalid 32 | Code: 33 | 1 | /[\c]/ 34 | | ^ [1] 35 | 36 | [1] Unexpected standalone backslash (`\`). It looks like an escape sequence, but it's a single `\` character pattern. 37 | --- 38 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-empty-capturing-group.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-empty-capturing-group >> invalid 5 | Code: 6 | 1 | /a(\b)/ 7 | | ^~~~ [1] 8 | 9 | [1] Unexpected capture empty. 10 | --- 11 | 12 | 13 | Test: no-empty-capturing-group >> invalid 14 | Code: 15 | 1 | /a($)/ 16 | | ^~~ [1] 17 | 18 | [1] Unexpected capture empty. 19 | --- 20 | 21 | 22 | Test: no-empty-capturing-group >> invalid 23 | Code: 24 | 1 | /(^)a/ 25 | | ^~~ [1] 26 | 27 | [1] Unexpected capture empty. 28 | --- 29 | 30 | 31 | Test: no-empty-capturing-group >> invalid 32 | Code: 33 | 1 | /()a/ 34 | | ^~ [1] 35 | 36 | [1] Unexpected capture empty. 37 | --- 38 | 39 | 40 | Test: no-empty-capturing-group >> invalid 41 | Code: 42 | 1 | /(\b\b|(?:\B|$))a/ 43 | | ^~~~~~~~~~~~~~~ [1] 44 | 45 | [1] Unexpected capture empty. 46 | --- 47 | 48 | 49 | Test: no-empty-capturing-group >> invalid 50 | Code: 51 | 1 | /a([\q{}])/v 52 | | ^~~~~~~~ [1] 53 | 54 | [1] Unexpected capture empty. 55 | --- 56 | -------------------------------------------------------------------------------- /tests/lib/rules/no-empty-character-class.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-empty-character-class" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-empty-character-class", rule as any, { 12 | valid: [ 13 | `/[a]/`, 14 | `/[a-z]/`, 15 | `/[a]?/`, 16 | `/[a]*/`, 17 | `/[[]/`, 18 | String.raw`/\[]/`, 19 | `/[^]/`, 20 | `/[()]/`, 21 | `/[ ]/`, 22 | String.raw`/[\s\S]/`, 23 | String.raw`/[\da-zA-Z_\W]/`, 24 | String.raw`/a[[[ab]&&b]]/v`, 25 | String.raw`/a[[ab]&&b]/v`, 26 | ], 27 | invalid: [ 28 | `/[]/`, 29 | `/abc[]/`, 30 | `/([])/`, 31 | `new RegExp("[]");`, 32 | String.raw`/[^\s\S]/`, 33 | String.raw`/[^\da-zA-Z_\W]/`, 34 | String.raw`/a[[a&&b]]/v`, 35 | String.raw`/a[a&&b]/v`, 36 | ], 37 | }) 38 | -------------------------------------------------------------------------------- /.github/workflows/cron.yml: -------------------------------------------------------------------------------- 1 | name: cron 2 | on: 3 | workflow_dispatch: null 4 | schedule: 5 | - cron: 0 0 * * 0 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | update-unicode-alias: 13 | name: update-unicode-alias 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v5 18 | - name: Install Node.js 19 | uses: actions/setup-node@v5 20 | - name: Install Packages 21 | run: npm install 22 | - name: Update 23 | run: npm run update:unicode-alias 24 | - name: Format 25 | run: npm run eslint-fix 26 | - uses: peter-evans/create-pull-request@v7 27 | with: 28 | commit-message: Updates unicode property alias resource with latest 29 | branch: update-unicode-alias 30 | branch-suffix: timestamp 31 | title: Updates unicode property alias resource with latest 32 | -------------------------------------------------------------------------------- /lib/utils/type-tracker/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Rule, Scope } from "eslint" 2 | import type * as ES from "estree" 3 | import * as astUtils from "../ast-utils" 4 | import * as eslintUtils from "@eslint-community/eslint-utils" 5 | 6 | /** 7 | * Find the variable of a given name. 8 | */ 9 | export function findVariable( 10 | context: Rule.RuleContext, 11 | node: ES.Identifier, 12 | ): Scope.Variable | null { 13 | return astUtils.findVariable(context, node) 14 | } 15 | /** 16 | * Get the property name from a MemberExpression node or a Property node. 17 | */ 18 | export function getPropertyName( 19 | context: Rule.RuleContext, 20 | node: ES.Property | ES.MemberExpression | ES.MethodDefinition, 21 | ): string | null { 22 | return eslintUtils.getPropertyName(node, astUtils.getScope(context, node)) 23 | } 24 | /** 25 | * Check whether a given node is parenthesized or not. 26 | */ 27 | export function isParenthesized( 28 | context: Rule.RuleContext, 29 | node: ES.Node, 30 | ): boolean { 31 | return eslintUtils.isParenthesized(node, context.sourceCode) 32 | } 33 | -------------------------------------------------------------------------------- /tools/update-rules.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import path from "path" 3 | // import eslint from "eslint" 4 | import { rules } from "./lib/load-rules" 5 | 6 | /** 7 | * Convert text to camelCase 8 | */ 9 | function camelCase(str: string) { 10 | return str.replace(/[-_](?\w)/gu, (_, char) => char.toUpperCase()) 11 | } 12 | 13 | const content = `import type { RuleModule } from "./types" 14 | ${rules 15 | .map( 16 | (rule) => 17 | `import ${camelCase(rule.meta.docs.ruleName)} from "./rules/${ 18 | rule.meta.docs.ruleName 19 | }"`, 20 | ) 21 | .join("\n")} 22 | 23 | export const rules: RuleModule[] = [ 24 | ${rules.map((rule) => camelCase(rule.meta.docs.ruleName)).join(",\n ")}, 25 | ] 26 | ` 27 | 28 | const filePath = path.resolve(__dirname, "../lib/all-rules.ts") 29 | 30 | // Update file. 31 | fs.writeFileSync(filePath, content) 32 | 33 | // Format files. 34 | // const linter = new eslint.CLIEngine({ fix: true }) 35 | // const report = linter.executeOnFiles([filePath]) 36 | // eslint.CLIEngine.outputFixes(report) 37 | -------------------------------------------------------------------------------- /tests/lib/rules/no-potentially-useless-backreference.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-potentially-useless-backreference" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-potentially-useless-backreference", rule as any, { 12 | valid: [ 13 | String.raw`/()\1/`, 14 | String.raw`/(a*)(?:a|\1)/`, 15 | String.raw`/(a)+\1/`, 16 | String.raw`/(?=(a))\1/`, 17 | String.raw`/([\q{a}])\1/v`, 18 | 19 | // done by regexp/no-useless-backreference 20 | String.raw`/(a+)b|\1/`, 21 | ], 22 | invalid: [ 23 | String.raw` 24 | var foo = /(a+)b\1/; 25 | 26 | var foo = /(a)?b\1/; 27 | var foo = /((a)|c)+b\2/;`, 28 | String.raw`/(a)?\1/`, 29 | String.raw`/(a)*\1/`, 30 | String.raw`/(?:(a)|b)\1/`, 31 | String.raw`/(?:(a)|b)+\1/`, 32 | String.raw`/(?:([\q{a}])|b)\1/v`, 33 | ], 34 | }) 35 | -------------------------------------------------------------------------------- /tests/lib/rules/no-contradiction-with-assertion.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-contradiction-with-assertion" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-contradiction-with-assertion", rule as any, { 12 | valid: [ 13 | // Ignore trivially accepting/rejecting assertions 14 | String.raw`/a\ba/`, 15 | String.raw`/(?!)a/`, 16 | String.raw`/(?=)a/`, 17 | String.raw`/$a/`, 18 | String.raw`/$a/v`, 19 | 20 | // Other valid regexes 21 | String.raw`/(^|[\s\S])\bfoo/`, 22 | String.raw`/(?:aa|a\b)-?a/`, 23 | String.raw`/(?:aa|a\b)-?a/v`, 24 | ], 25 | invalid: [ 26 | String.raw`/a\b-?a/`, 27 | String.raw`/a\b(a|-)/`, 28 | String.raw`/a\ba*-/`, 29 | String.raw`/a\b[a\q{foo|bar}]*-/v`, 30 | 31 | String.raw`/(^[\t ]*)#(?:comments-start|cs)[\s\S]*?^[ \t]*#(?:comments-end|ce)/m`, 32 | ], 33 | }) 34 | -------------------------------------------------------------------------------- /tests/lib/utils/unicode.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | import { toCharSetSource } from "../../../lib/utils/refa" 3 | import { 4 | isSpace, 5 | isInvisible, 6 | CP_NEL, 7 | CP_ZWSP, 8 | } from "../../../lib/utils/unicode" 9 | 10 | const SPACES = 11 | " \f\n\r\t\v\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff" 12 | 13 | describe("isSpace", () => { 14 | for (const c of SPACES) { 15 | it(`${toCharSetSource(c.codePointAt(0)!, {})} is space`, () => { 16 | assert.ok(isSpace(c.codePointAt(0)!)) 17 | }) 18 | } 19 | 20 | for (const c of [CP_NEL, CP_ZWSP]) { 21 | it(`${toCharSetSource(c, {})} is not space`, () => { 22 | assert.ok(!isSpace(c)) 23 | }) 24 | } 25 | }) 26 | 27 | describe("isInvisible", () => { 28 | const str = `${SPACES}\u0085\u200b\u200c\u200d\u200e\u200f\u2800` 29 | 30 | for (const c of str) { 31 | it(`${toCharSetSource(c.codePointAt(0)!, {})} is invisible`, () => { 32 | assert.ok(isInvisible(c.codePointAt(0)!)) 33 | }) 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Yosuke Ota 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/lib/rules/no-trivially-nested-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-trivially-nested-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-trivially-nested-quantifier", rule as any, { 12 | valid: [ 13 | `/(a?)+/`, 14 | `/(?:a{2})+/`, 15 | `/(?:a{3,4})+/`, 16 | `/(?:a+?)+/`, 17 | String.raw`/(?:[\q{a}])+/v`, 18 | ], 19 | invalid: [ 20 | String.raw`/(?:a?)+/`, 21 | String.raw`/(?:a{1,2})*/`, 22 | String.raw`/(?:a{1,2})+/`, 23 | String.raw`/(?:a{1,2}){3,4}/`, 24 | String.raw`/(?:a{2,}){4}/`, 25 | String.raw`/(?:a{4,}){5}/`, 26 | String.raw`/(?:a{3}){4}/`, 27 | String.raw`/(?:a+|b)*/`, 28 | String.raw`/(?:a?|b)*/`, 29 | String.raw`/(?:a{0,4}|b)*/`, 30 | String.raw`/(?:a{0,4}|b)+/`, 31 | String.raw`/(?:a{0,4}?|b)+?/`, 32 | String.raw`/(?:[\q{a}]+)+/v`, 33 | ], 34 | }) 35 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-unicode-codepoint-escapes.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-unicode-codepoint-escapes" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-unicode-codepoint-escapes", rule as any, { 12 | valid: [ 13 | `/regexp/u`, 14 | String.raw`/\ud83d\ude00/`, 15 | String.raw`/[\ud83d\ude00]/`, 16 | String.raw`/\u{1f600}/u`, 17 | String.raw`/😀/u`, 18 | String.raw`/\u{1f600}/v`, 19 | String.raw`/😀/v`, 20 | ], 21 | invalid: [ 22 | String.raw`/\ud83d\ude00/u`, 23 | String.raw`/[\ud83d\ude00]/u`, 24 | String.raw`/\uD83D\uDE00/u`, 25 | String.raw` 26 | const s = "\\ud83d\\ude00" 27 | new RegExp(s, 'u') 28 | `, 29 | String.raw` 30 | const s = "\\ud83d"+"\\ude00" 31 | new RegExp(s, 'u') 32 | `, 33 | String.raw`/\ud83d\ude00/v`, 34 | ], 35 | }) 36 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error -- Browser 2 | if (typeof window !== "undefined") { 3 | if (typeof require === "undefined") { 4 | // @ts-expect-error -- Browser 5 | ;(window as any).require = () => { 6 | const e = new Error("require is not defined") 7 | ;(e as any).code = "MODULE_NOT_FOUND" 8 | throw e 9 | } 10 | } 11 | } 12 | import type { Theme } from "vitepress" 13 | import DefaultTheme from "vitepress/theme" 14 | import Layout from "./Layout.vue" 15 | 16 | const theme: Theme = { 17 | ...DefaultTheme, 18 | Layout, 19 | async enhanceApp(ctx) { 20 | DefaultTheme.enhanceApp(ctx) 21 | 22 | const ESLintCodeBlock = await import( 23 | "./components/eslint-code-block.vue" 24 | ).then((m) => m.default ?? m) 25 | const PlaygroundBlock = await import( 26 | "./components/playground-block.vue" 27 | ).then((m) => m.default ?? m) 28 | ctx.app.component("eslint-code-block", ESLintCodeBlock) 29 | ctx.app.component("playground-block", PlaygroundBlock) 30 | }, 31 | } 32 | export default theme 33 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node 3 | { 4 | "name": "Node.js & TypeScript", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/typescript-node:3-22", 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [], 13 | 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | "postCreateCommand": "npm install", 16 | 17 | // Configure tool-specific properties. 18 | "customizations": { 19 | "vscode": { 20 | "extensions": ["dbaeumer.vscode-eslint", "Vue.volar"] 21 | } 22 | } 23 | 24 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 25 | // "remoteUser": "root" 26 | } 27 | -------------------------------------------------------------------------------- /tests/lib/utils/type-tracker/number.ts: -------------------------------------------------------------------------------- 1 | import type { TestCase } from "./test-utils" 2 | import { testTypeTrackerWithLinter } from "./test-utils" 3 | 4 | const TESTCASES: TestCase[] = [ 5 | { 6 | code: ` 7 | Number('0') 8 | `, 9 | type: "Number", 10 | }, 11 | { 12 | code: ` 13 | const a = 123 14 | a.valueOf() 15 | `, 16 | type: "Number", 17 | }, 18 | { 19 | code: ` 20 | const a = 123 21 | a.toFixed() 22 | `, 23 | type: "String", 24 | }, 25 | { 26 | code: ` 27 | Number.EPSILON 28 | `, 29 | type: "Number", 30 | }, 31 | { 32 | code: ` 33 | for (const e of 123) { 34 | e 35 | } 36 | `, 37 | type: [], 38 | }, 39 | { 40 | code: ` 41 | const a = 123 42 | a() 43 | `, 44 | type: [], 45 | }, 46 | ] 47 | describe("type track for number", () => { 48 | for (const testCase of TESTCASES) { 49 | it(testCase.code, () => { 50 | testTypeTrackerWithLinter(testCase) 51 | }) 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /tests/lib/rules/no-useless-lazy.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-useless-lazy" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-useless-lazy", rule as any, { 12 | valid: [ 13 | `/a*?/`, 14 | `/a+?/`, 15 | `/a{4,}?/`, 16 | `/a{2,4}?/`, 17 | `/a{2,2}/`, 18 | `/a{3}/`, 19 | `/a+?b*/`, 20 | `/[\\s\\S]+?bar/`, 21 | `/a??a?/`, 22 | ], 23 | invalid: [ 24 | `/a{1}?/`, 25 | `/a{1}?/v`, 26 | `/a{4}?/`, 27 | `/a{2,2}?/`, 28 | String.raw`const s = "\\d{1}?" 29 | new RegExp(s)`, 30 | String.raw`const s = "\\d"+"{1}?" 31 | new RegExp(s)`, 32 | 33 | `/a+?b+/`, 34 | String.raw`/[\q{aa|ab}]+?b+/v`, 35 | `/a*?b+/`, 36 | `/(?:a|cd)+?(?:b+|zzz)/`, 37 | 38 | String.raw`/\b\w+?(?=\W)/`, 39 | String.raw`/\b\w+?(?!\w)/`, 40 | String.raw`/\b\w+?\b/`, 41 | String.raw`/\b\w+?$/`, 42 | ], 43 | }) 44 | -------------------------------------------------------------------------------- /tests/lib/rules/no-useless-range.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-useless-range" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-useless-range", rule as any, { 12 | valid: [`/[a]/`, `/[ab]/`, `/[a-c]/`], 13 | invalid: [ 14 | `/[a-a]/`, 15 | `/[a-a]/v`, 16 | `/[a-b]/`, 17 | `/[a-b]/v`, 18 | `/[a-a-c-c]/`, 19 | `/[a-abc]/`, 20 | ` 21 | const s = "[a-a-c]" 22 | new RegExp(s)`, 23 | ` 24 | const s = "[a-"+"a]" 25 | new RegExp(s)`, 26 | ` 27 | /[,--b]/; 28 | /[a-a-z]/; 29 | /[a-a--z]/; 30 | /[\\c-d]/; 31 | /[\\x6-7]/; 32 | /[\\u002-3]/; 33 | /[A-\\u004-5]/; 34 | `, 35 | ` 36 | /[,-\\-b]/; 37 | /[c-d]/; 38 | /[x6-7]/; 39 | /[\\x 6-7]/; 40 | /[u002-3]/; 41 | /[\\u 002-3]/; 42 | `, 43 | ], 44 | }) 45 | -------------------------------------------------------------------------------- /tests/lib/rules/no-empty-lookarounds-assertion.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-empty-lookarounds-assertion" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-empty-lookarounds-assertion", rule as any, { 12 | valid: [ 13 | "/x(?=y)/", 14 | "/x(?!y)/", 15 | "/(?<=y)x/", 16 | "/(? 13 | 14 | > disallow empty group 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports empty groups. 19 | 20 | 21 | 22 | ```js 23 | /* eslint regexp/no-empty-group: "error" */ 24 | 25 | /* ✓ GOOD */ 26 | var foo = /(a)/; 27 | var foo = /(?:a)/; 28 | 29 | /* ✗ BAD */ 30 | // capturing group 31 | var foo = /()/; 32 | var foo = /(|)/; 33 | // non-capturing group 34 | var foo = /(?:)/; 35 | var foo = /(?:|)/; 36 | ``` 37 | 38 | 39 | 40 | ## :wrench: Options 41 | 42 | Nothing. 43 | 44 | ## :rocket: Version 45 | 46 | This rule was introduced in eslint-plugin-regexp v0.1.0 47 | 48 | ## :mag: Implementation 49 | 50 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-empty-group.ts) 51 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-empty-group.ts) 52 | -------------------------------------------------------------------------------- /tests/lib/utils/type-tracker/iterable.ts: -------------------------------------------------------------------------------- 1 | import type { TestCase } from "./test-utils" 2 | import { testTypeTrackerWithLinter } from "./test-utils" 3 | 4 | const TESTCASES: TestCase[] = [ 5 | { 6 | code: ` 7 | const m = new Map([[1,"a"],[2,"b"]]) 8 | const itr = m.keys() 9 | itr 10 | `, 11 | type: "Iterable", 12 | }, 13 | { 14 | code: ` 15 | const m = new Map([[1,"a"],[2,"b"]]) 16 | const itr = m.keys() 17 | itr.foo 18 | `, 19 | type: [], 20 | }, 21 | { 22 | code: ` 23 | const m = new Map([[1,"a"],[2,"b"]]) 24 | const itr = m.keys() 25 | itr() 26 | `, 27 | type: [], 28 | }, 29 | { 30 | code: ` 31 | const m = new Map([[1,"a"],[2,"b"]]) 32 | const itr1 = m.keys() 33 | const itr2 = m.keys() 34 | const m2 = new Map([[1,itr1],[2,itr2]]) 35 | m2.get(1) 36 | `, 37 | type: "Iterable", 38 | }, 39 | ] 40 | describe("type track for iterable", () => { 41 | for (const testCase of TESTCASES) { 42 | it(testCase.code, () => { 43 | testTypeTrackerWithLinter(testCase) 44 | }) 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /tests/lib/rules/require-unicode-sets-regexp.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/require-unicode-sets-regexp" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("require-unicode-sets-regexp", rule as any, { 12 | valid: [`/a/v`], 13 | invalid: [ 14 | `/a/`, 15 | `/a/u`, 16 | String.raw`/[\p{ASCII}]/iu`, 17 | `/[[]/u`, 18 | String.raw`/[^\P{Lowercase_Letter}]/giu`, 19 | String.raw`/[^\P{ASCII}]/iu`, 20 | String.raw`/[\P{ASCII}]/iu`, 21 | ...[ 22 | "&&", 23 | "!!", 24 | "##", 25 | "$$", 26 | "%%", 27 | "**", 28 | "++", 29 | ",,", 30 | "..", 31 | "::", 32 | ";;", 33 | "<<", 34 | "==", 35 | ">>", 36 | "??", 37 | "@@", 38 | "^^", 39 | "``", 40 | "~~", 41 | ].map((punctuator) => ({ 42 | code: String.raw`/[a${punctuator}b]/u`, 43 | })), 44 | String.raw`/[+--b]/u`, 45 | ], 46 | }) 47 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-escape-replacement-dollar-char.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-escape-replacement-dollar-char" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-escape-replacement-dollar-char", rule as any, { 12 | valid: [ 13 | `'€1,234'.replace(/€/, '$$'); // "$1,234"`, 14 | `'abc'.foo(/./, '$');`, 15 | `'abc'.replace(/./, $);`, 16 | `'abc'.replace(foo, '$');`, 17 | `foo.replace(/./, '$');`, 18 | `'abc'.replace(/./, '$&$&');`, 19 | `'abc'.replace(/./, "$\`$'");`, 20 | `'abc'.replace(/(.)/, '$1');`, 21 | `'abc'.replace(/(?.)/, '$');`, 22 | String.raw`'€1,234'.replace(/[\q{€}]/v, '$$'); // "$1,234"`, 23 | ], 24 | invalid: [ 25 | `'€1,234'.replace(/€/, '$'); // "$1,234"`, 26 | `'€1,234'.replace(/€/v, '$'); // "$1,234"`, 27 | `'€1,234'.replaceAll(/€/, '$'); // "$1,234"`, 28 | `'abc'.replace(/./, '$ $$ $');`, 29 | `'abc'.replace(/(?.)/, '$> invalid 5 | Code: 6 | 1 | RegExp("(") 7 | | ^ [1] 8 | 9 | [1] Invalid regular expression: /(/: Unterminated group 10 | --- 11 | 12 | 13 | Test: no-invalid-regexp >> invalid 14 | Code: 15 | 1 | RegExp("(" + "(") 16 | | ^ [1] 17 | 18 | [1] Invalid regular expression: /((/: Unterminated group 19 | --- 20 | 21 | 22 | Test: no-invalid-regexp >> invalid 23 | Code: 24 | 1 | RegExp("[a-Z] some valid stuff") 25 | | ^~ [1] 26 | 27 | [1] Invalid regular expression: /[a-Z] some valid stuff/: Range out of order in character class 28 | --- 29 | 30 | 31 | Test: no-invalid-regexp >> invalid 32 | Code: 33 | 1 | new RegExp(pattern, 'uu'); 34 | | ^~ [1] 35 | 36 | [1] Duplicate u flag. 37 | --- 38 | 39 | 40 | Test: no-invalid-regexp >> invalid 41 | Code: 42 | 1 | new RegExp(pattern, 'uv'); 43 | | ^~ [1] 44 | 45 | [1] Regex 'u' and 'v' flags cannot be used together. 46 | --- 47 | 48 | 49 | Test: no-invalid-regexp >> invalid 50 | Code: 51 | 1 | new RegExp('[A&&&]', 'v'); 52 | | ^~ [1] 53 | 54 | [1] Invalid regular expression: /[A&&&]/v: Invalid character in character class 55 | --- 56 | -------------------------------------------------------------------------------- /tests/lib/rules/no-invisible-character.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-invisible-character" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-invisible-character", rule as any, { 12 | valid: [ 13 | "/a/", 14 | "/ /", 15 | "/[a]/", 16 | "/[ ]/", 17 | String.raw`/\t/`, 18 | String.raw`new RegExp('\t')`, 19 | ` 20 | const a = '' + '\t'; 21 | new RegExp(a)`, 22 | "new RegExp(' ')", 23 | "new RegExp('a')", 24 | "new RegExp('[ ]')", 25 | String.raw`/[\q{\t}]/v`, 26 | ], 27 | invalid: [ 28 | "/\u00a0/", 29 | "/[\t]/", 30 | "/[\t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff\u0085\u200b]/", 31 | "/[\\t\u00a0\\u1680\u180e\\u2000\u2001\\u2002\u2003\\u2004\u2005\\u2006\u2007\\u2008\u2009\\u200a\u202f\\u205f\u3000\\ufeff\u0085\\u200b]/", 32 | "new RegExp('\t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff\u0085\u200b')", 33 | `/[\\q{\t}]/v`, 34 | ], 35 | }) 36 | -------------------------------------------------------------------------------- /tests/lib/rules/no-misleading-capturing-group.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-misleading-capturing-group" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-misleading-capturing-group", rule as any, { 12 | valid: [ 13 | String.raw`/a+a+/`, 14 | String.raw`/(a+a+)/`, 15 | String.raw`/(a+a+)b+/`, 16 | 17 | String.raw`/^(a*(?!a)).+/u`, 18 | String.raw`/(^~~?)(?!~)[\s\S]+(?=\1$)/m`, 19 | String.raw`/(^~~?(?!~))[\s\S]+(?=\1$)/m`, 20 | String.raw`/(^~(?:~|(?!~)))[\s\S]+(?=\1$)/m`, 21 | { 22 | code: String.raw`/^(a*).+/u`, 23 | options: [{ reportBacktrackingEnds: false }], 24 | }, 25 | ], 26 | invalid: [ 27 | String.raw`/\d+(\d*)/`, 28 | String.raw`/(?:!\d+|%\w+)(\d*)/`, 29 | 30 | // backtracking ends 31 | String.raw`/^(a*).+/u`, 32 | String.raw`/^([\t ]*).+/gmu`, 33 | String.raw`/('{2,5}).+?\1/`, 34 | String.raw`/^(---.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^---$)/m`, 35 | String.raw`/(^~~?)[\s\S]+(?=\1$)/m`, 36 | String.raw`/^([a\q{abc}]*).+/v`, 37 | ], 38 | }) 39 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-w.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-w" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-w", rule as any, { 12 | valid: [ 13 | String.raw`/\w/`, 14 | String.raw`/[\Da-zA-Z_#]/`, 15 | String.raw`/\w/v`, 16 | String.raw`/[\Da-zA-Z_#]/v`, 17 | ], 18 | invalid: [ 19 | "/[0-9a-zA-Z_]/", 20 | "/[0-9a-zA-Z_#]/", 21 | String.raw`/[\da-zA-Z_#]/`, 22 | String.raw`/[0-9a-z_[\s&&\p{ASCII}]]/iv`, 23 | "/[0-9a-z_]/i", 24 | "/[^0-9a-zA-Z_]/", 25 | "/[^0-9A-Z_]/i", 26 | ` 27 | const s = "[0-9A-Z_]" 28 | new RegExp(s, 'i') 29 | `, 30 | ` 31 | const s = "[0-9"+"A-Z_]" 32 | new RegExp(s, 'i') 33 | `, 34 | ` 35 | const s = "[0-9A-Z_c]" 36 | new RegExp(s, 'i') 37 | `, 38 | ` 39 | const s = "[0-9"+"A-Z_c]" 40 | new RegExp(s, 'i') 41 | `, 42 | ` 43 | const s = "[0-9A-Z_-]" 44 | new RegExp(s, 'i') 45 | `, 46 | ], 47 | }) 48 | -------------------------------------------------------------------------------- /docs/rules/no-non-standard-flag.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-non-standard-flag" 5 | description: "disallow non-standard flags" 6 | since: "v0.9.0" 7 | --- 8 | # regexp/no-non-standard-flag 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 13 | 14 | > disallow non-standard flags 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports non-standard flags. 19 | 20 | Some JavaScript runtime implementations allow special flags not defined in the ECMAScript standard. These flags are experimental and should not be used in production code. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-non-standard-flag: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /a*b*c/guy; 29 | 30 | /* ✗ BAD */ 31 | var foo = RegExp("(?:a|a)*b", "l"); 32 | ``` 33 | 34 | 35 | 36 | ## :wrench: Options 37 | 38 | Nothing. 39 | 40 | ## :rocket: Version 41 | 42 | This rule was introduced in eslint-plugin-regexp v0.9.0 43 | 44 | ## :mag: Implementation 45 | 46 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-non-standard-flag.ts) 47 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-non-standard-flag.ts) 48 | -------------------------------------------------------------------------------- /docs/rules/prefer-w.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-w" 5 | description: "enforce using `\\w`" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/prefer-w 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > enforce using `\w` 17 | 18 | ## :book: Rule Details 19 | 20 | This rule is aimed at using `\w` in regular expressions. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/prefer-w: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /\w/; 29 | var foo = /\W/; 30 | 31 | /* ✗ BAD */ 32 | var foo = /[0-9a-zA-Z_]/; 33 | var foo = /[^0-9a-zA-Z_]/; 34 | var foo = /[0-9a-z_]/i; 35 | var foo = /[0-9a-z_-]/i; 36 | ``` 37 | 38 | 39 | 40 | ## :wrench: Options 41 | 42 | Nothing. 43 | 44 | ## :rocket: Version 45 | 46 | This rule was introduced in eslint-plugin-regexp v0.1.0 47 | 48 | ## :mag: Implementation 49 | 50 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-w.ts) 51 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-w.ts) 52 | -------------------------------------------------------------------------------- /docs/rules/no-useless-range.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-useless-range" 5 | description: "disallow unnecessary character ranges" 6 | since: "v0.3.0" 7 | --- 8 | # regexp/no-useless-range 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow unnecessary character ranges 17 | 18 | ## :book: Rule Details 19 | 20 | This rule reports unnecessary character ranges. E.g. `[a-a]` 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-useless-range: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /[a]/ 29 | var foo = /[ab]/ 30 | 31 | /* ✗ BAD */ 32 | var foo = /[a-a]/ 33 | var foo = /[a-b]/ 34 | ``` 35 | 36 | 37 | 38 | ## :wrench: Options 39 | 40 | Nothing. 41 | 42 | ## :rocket: Version 43 | 44 | This rule was introduced in eslint-plugin-regexp v0.3.0 45 | 46 | ## :mag: Implementation 47 | 48 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-useless-range.ts) 49 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-useless-range.ts) 50 | -------------------------------------------------------------------------------- /docs/rules/prefer-plus-quantifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-plus-quantifier" 5 | description: "enforce using `+` quantifier" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/prefer-plus-quantifier 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > enforce using `+` quantifier 17 | 18 | ## :book: Rule Details 19 | 20 | This rule is aimed at using `+` quantifier instead of `{1,}` in regular expressions. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/prefer-plus-quantifier: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /a+/; 29 | 30 | /* ✗ BAD */ 31 | var foo = /a{1,}/; 32 | ``` 33 | 34 | 35 | 36 | ## :wrench: Options 37 | 38 | Nothing. 39 | 40 | ## :rocket: Version 41 | 42 | This rule was introduced in eslint-plugin-regexp v0.1.0 43 | 44 | ## :mag: Implementation 45 | 46 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-plus-quantifier.ts) 47 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-plus-quantifier.ts) 48 | -------------------------------------------------------------------------------- /docs/rules/prefer-star-quantifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-star-quantifier" 5 | description: "enforce using `*` quantifier" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/prefer-star-quantifier 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > enforce using `*` quantifier 17 | 18 | ## :book: Rule Details 19 | 20 | This rule is aimed at using `*` quantifier instead of `{0,}` in regular expressions. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/prefer-star-quantifier: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /a*/ 29 | 30 | /* ✗ BAD */ 31 | var foo = /a{0,}/; 32 | ``` 33 | 34 | 35 | 36 | ## :wrench: Options 37 | 38 | Nothing. 39 | 40 | ## :rocket: Version 41 | 42 | This rule was introduced in eslint-plugin-regexp v0.1.0 43 | 44 | ## :mag: Implementation 45 | 46 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-star-quantifier.ts) 47 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-star-quantifier.ts) 48 | -------------------------------------------------------------------------------- /tests/lib/rules/control-character-escape.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/control-character-escape" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("control-character-escape", rule as any, { 12 | valid: [ 13 | String.raw`/\0\t\n\v\f\r/`, 14 | String.raw`RegExp(/\0\t\n\v\f\r/, "i")`, 15 | String.raw`RegExp("\0\t\n\v\f\r", "i")`, 16 | String.raw`RegExp("\\0\\t\\n\\v\\f\\r", "i")`, 17 | String.raw`/\t/`, 18 | "new RegExp('\t')", 19 | String.raw`/[\q{\0\t\n\v\f\r}]/v`, 20 | ], 21 | invalid: [ 22 | String.raw`/\x00/`, 23 | String.raw`/\x0a/`, 24 | String.raw`/\cJ/`, 25 | String.raw`/\u{a}/u`, 26 | String.raw`RegExp("\\cJ")`, 27 | String.raw`RegExp("\\u{a}", "u")`, 28 | 29 | String.raw`/\u0009/`, 30 | "/\t/", 31 | String.raw` 32 | const s = "\\u0009" 33 | new RegExp(s) 34 | `, 35 | String.raw` 36 | const s = "\\u"+"0009" 37 | new RegExp(s) 38 | `, 39 | String.raw`RegExp("\t\r\n\0" + / /.source)`, 40 | String.raw`/[\q{\x00}]/v`, 41 | ], 42 | }) 43 | -------------------------------------------------------------------------------- /docs/rules/no-empty-capturing-group.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-empty-capturing-group" 5 | description: "disallow capturing group that captures empty." 6 | since: "v0.12.0" 7 | --- 8 | # regexp/no-empty-capturing-group 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 13 | 14 | > disallow capturing group that captures empty. 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports capturing group that captures assertions. 19 | 20 | 21 | 22 | ```js 23 | /* eslint regexp/no-empty-capturing-group: "error" */ 24 | 25 | /* ✓ GOOD */ 26 | var foo = /(a)/; 27 | var foo = /a(?:\b)/; 28 | var foo = /a(?:$)/; 29 | var foo = /(?:^)a/; 30 | var foo = /(?:^|b)a/; 31 | 32 | /* ✗ BAD */ 33 | var foo = /a(\b)/; 34 | var foo = /a($)/; 35 | var foo = /(^)a/; 36 | ``` 37 | 38 | 39 | 40 | ## :wrench: Options 41 | 42 | Nothing. 43 | 44 | ## :rocket: Version 45 | 46 | This rule was introduced in eslint-plugin-regexp v0.12.0 47 | 48 | ## :mag: Implementation 49 | 50 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-empty-capturing-group.ts) 51 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-empty-capturing-group.ts) 52 | -------------------------------------------------------------------------------- /docs/rules/prefer-question-quantifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-question-quantifier" 5 | description: "enforce using `?` quantifier" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/prefer-question-quantifier 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > enforce using `?` quantifier 17 | 18 | ## :book: Rule Details 19 | 20 | This rule is aimed at using `?` quantifier instead of `{0,1}` in regular expressions. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/prefer-question-quantifier: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /a?/; 29 | 30 | /* ✗ BAD */ 31 | var foo = /a{0,1}/; 32 | ``` 33 | 34 | 35 | 36 | ## :wrench: Options 37 | 38 | Nothing. 39 | 40 | ## :rocket: Version 41 | 42 | This rule was introduced in eslint-plugin-regexp v0.1.0 43 | 44 | ## :mag: Implementation 45 | 46 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-question-quantifier.ts) 47 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-question-quantifier.ts) 48 | -------------------------------------------------------------------------------- /.github/workflows/GHPages.yml: -------------------------------------------------------------------------------- 1 | name: GHPages 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | workflow_dispatch: null 7 | 8 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | # Allow one concurrent deployment 15 | concurrency: 16 | group: pages 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | deploy: 21 | environment: 22 | name: github-pages 23 | url: ${{ steps.deployment.outputs.page_url }} 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v5 28 | - uses: actions/setup-node@v5 29 | with: 30 | node-version: 22 31 | - name: Install Packages 32 | run: npm ci 33 | - name: Build docs 34 | run: |+ 35 | npm run docs:build 36 | - name: Setup Pages 37 | uses: actions/configure-pages@v5 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v4 40 | with: 41 | path: ./docs/.vitepress/dist/eslint-plugin-regexp 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 45 | -------------------------------------------------------------------------------- /tests/lib/utils/refa.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | import { toCharSetSource } from "../../../lib/utils/refa" 3 | 4 | describe("toCharSetSource", () => { 5 | for (const c of "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789") { 6 | const cp = c.codePointAt(0)! 7 | it(`0x${cp.toString(16)} to ${c}`, () => { 8 | assert.strictEqual(toCharSetSource(cp, {}), c) 9 | }) 10 | } 11 | it(`0x9 to \\t`, () => { 12 | assert.strictEqual(toCharSetSource(9, {}), String.raw`\t`) 13 | }) 14 | it(`0xA to \\n`, () => { 15 | assert.strictEqual(toCharSetSource(10, {}), String.raw`\n`) 16 | }) 17 | it(`0xC to \\f`, () => { 18 | assert.strictEqual(toCharSetSource(12, {}), String.raw`\f`) 19 | }) 20 | it(`0xD to \\n`, () => { 21 | assert.strictEqual(toCharSetSource(13, {}), String.raw`\r`) 22 | }) 23 | }) 24 | 25 | describe("toCharSetSource with invisible chars", () => { 26 | const str = 27 | "\v\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff" + 28 | "\u0085\u200b\u200c\u200d\u200e\u200f\u2800" 29 | 30 | for (const c of str) { 31 | const cp = c.codePointAt(0)! 32 | it(`0x${cp.toString(16)}`, () => { 33 | assert.notStrictEqual(toCharSetSource(cp, {}), c) 34 | }) 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-useless-two-nums-quantifier.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-useless-two-nums-quantifier >> invalid 5 | Code: 6 | 1 | /a{1,1}/ 7 | | ^~~~~ [1] 8 | 9 | Output: 10 | 1 | /a{1}/ 11 | 12 | [1] Unexpected quantifier '{1,1}'. 13 | --- 14 | 15 | 16 | Test: no-useless-two-nums-quantifier >> invalid 17 | Code: 18 | 1 | /a{42,42}/ 19 | | ^~~~~~~ [1] 20 | 21 | Output: 22 | 1 | /a{42}/ 23 | 24 | [1] Unexpected quantifier '{42,42}'. 25 | --- 26 | 27 | 28 | Test: no-useless-two-nums-quantifier >> invalid 29 | Code: 30 | 1 | /a{042,42}/ 31 | | ^~~~~~~~ [1] 32 | 33 | Output: 34 | 1 | /a{42}/ 35 | 36 | [1] Unexpected quantifier '{042,42}'. 37 | --- 38 | 39 | 40 | Test: no-useless-two-nums-quantifier >> invalid 41 | Code: 42 | 1 | /a{042,042}/ 43 | | ^~~~~~~~~ [1] 44 | 45 | Output: 46 | 1 | /a{42}/ 47 | 48 | [1] Unexpected quantifier '{042,042}'. 49 | --- 50 | 51 | 52 | Test: no-useless-two-nums-quantifier >> invalid 53 | Code: 54 | 1 | /a{100,100}?/ 55 | | ^~~~~~~~~ [1] 56 | 57 | Output: 58 | 1 | /a{100}?/ 59 | 60 | [1] Unexpected quantifier '{100,100}'. 61 | --- 62 | 63 | 64 | Test: no-useless-two-nums-quantifier >> invalid 65 | Code: 66 | 1 | /a{100,100}?/v 67 | | ^~~~~~~~~ [1] 68 | 69 | Output: 70 | 1 | /a{100}?/v 71 | 72 | [1] Unexpected quantifier '{100,100}'. 73 | --- 74 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-regexp-exec.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-regexp-exec" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-regexp-exec", rule as any, { 12 | valid: [ 13 | ` 14 | /thing/.exec('something'); 15 | 16 | 'some things are just things'.match(/thing/g); 17 | 18 | const text = 'something'; 19 | const search = /thing/; 20 | search.exec(text); 21 | `, 22 | ` 23 | /thin[[g]]/v.exec('something'); 24 | `, 25 | ], 26 | invalid: [ 27 | ` 28 | 'something'.match(/thing/); 29 | 30 | 'some things are just things'.match(/thing/); 31 | 32 | const text = 'something'; 33 | const search = /thing/; 34 | text.match(search); 35 | `, 36 | ` 37 | const fn = (a) => a + '' 38 | fn(1).match(search); 39 | `, 40 | ` 41 | const v = a + b 42 | v.match(search); 43 | 44 | const n = 1 + 2 45 | n.match(search); // ignore 46 | `, 47 | ` 48 | 'something'.match(/thin[[g]]/v); 49 | `, 50 | ], 51 | }) 52 | -------------------------------------------------------------------------------- /tests/lib/rules/no-control-character.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-control-character" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-control-character", rule as any, { 12 | valid: [ 13 | String.raw`/x1f/`, 14 | String.raw`/\\x1f/`, 15 | String.raw`new RegExp('x1f')`, 16 | String.raw`RegExp('x1f')`, 17 | String.raw`new RegExp('[')`, 18 | String.raw`RegExp('[')`, 19 | String.raw`new (function foo(){})('\x1f')`, 20 | String.raw`new RegExp('\n')`, 21 | String.raw`new RegExp('\\n')`, 22 | ], 23 | invalid: [ 24 | String.raw`/\x1f/`, 25 | String.raw`/\\\x1f\\x1e/`, 26 | String.raw`/\\\x1fFOO\\x00/`, 27 | String.raw`/FOO\\\x1fFOO\\x1f/`, 28 | String.raw`new RegExp('\x1f\x1e')`, 29 | String.raw`new RegExp('\x1fFOO\x00')`, 30 | String.raw`new RegExp('FOO\x1fFOO\x1f')`, 31 | String.raw`RegExp('\x1f')`, 32 | String.raw`RegExp('\\x1f')`, 33 | String.raw`RegExp('\\\x1f')`, 34 | String.raw`RegExp('\x0a')`, 35 | String.raw`RegExp('\\x0a')`, 36 | String.raw`RegExp('\\\x0a')`, 37 | String.raw`/[\q{\x1f}]/v`, 38 | ], 39 | }) 40 | -------------------------------------------------------------------------------- /docs/rules/use-ignore-case.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/use-ignore-case" 5 | description: "use the `i` flag if it simplifies the pattern" 6 | since: "v1.4.0" 7 | --- 8 | # regexp/use-ignore-case 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > use the `i` flag if it simplifies the pattern 17 | 18 | ## :book: Rule Details 19 | 20 | This rule reports regular expressions that can be simplified by adding the `i` flag. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/use-ignore-case: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /\w\d+a/; 29 | var foo = /\b0x[a-fA-F0-9]+\b/; 30 | 31 | /* ✗ BAD */ 32 | var foo = /[a-zA-Z]/; 33 | var foo = /\b0[xX][a-fA-F0-9]+\b/; 34 | ``` 35 | 36 | 37 | 38 | ## :wrench: Options 39 | 40 | Nothing. 41 | 42 | ## :rocket: Version 43 | 44 | This rule was introduced in eslint-plugin-regexp v1.4.0 45 | 46 | ## :mag: Implementation 47 | 48 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/use-ignore-case.ts) 49 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/use-ignore-case.ts) 50 | -------------------------------------------------------------------------------- /docs/rules/no-trivially-nested-assertion.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-trivially-nested-assertion" 5 | description: "disallow trivially nested assertions" 6 | since: "v0.9.0" 7 | --- 8 | # regexp/no-trivially-nested-assertion 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow trivially nested assertions 17 | 18 | ## :book: Rule Details 19 | 20 | Lookaround assertions that only contain another assertion can be simplified. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-trivially-nested-assertion: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /a(?=b)/; 29 | var foo = /a(?!$)/; 30 | 31 | /* ✗ BAD */ 32 | var foo = /a(?=$)/; 33 | var foo = /a(?=(?!a))/; 34 | ``` 35 | 36 | 37 | 38 | ## :wrench: Options 39 | 40 | Nothing. 41 | 42 | ## :rocket: Version 43 | 44 | This rule was introduced in eslint-plugin-regexp v0.9.0 45 | 46 | ## :mag: Implementation 47 | 48 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-trivially-nested-assertion.ts) 49 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-trivially-nested-assertion.ts) 50 | -------------------------------------------------------------------------------- /lib/rules/no-empty-capturing-group.ts: -------------------------------------------------------------------------------- 1 | import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" 2 | import { isZeroLength } from "regexp-ast-analysis" 3 | import type { RegExpContext } from "../utils" 4 | import { createRule, defineRegexpVisitor } from "../utils" 5 | 6 | export default createRule("no-empty-capturing-group", { 7 | meta: { 8 | docs: { 9 | description: "disallow capturing group that captures empty.", 10 | category: "Possible Errors", 11 | recommended: true, 12 | }, 13 | schema: [], 14 | messages: { 15 | unexpected: "Unexpected capture empty.", 16 | }, 17 | type: "suggestion", 18 | }, 19 | create(context) { 20 | function createVisitor({ 21 | node, 22 | flags, 23 | getRegexpLocation, 24 | }: RegExpContext): RegExpVisitor.Handlers { 25 | return { 26 | onCapturingGroupEnter(cgNode) { 27 | if (isZeroLength(cgNode, flags)) { 28 | context.report({ 29 | node, 30 | loc: getRegexpLocation(cgNode), 31 | messageId: "unexpected", 32 | }) 33 | } 34 | }, 35 | } 36 | } 37 | 38 | return defineRegexpVisitor(context, { 39 | createVisitor, 40 | }) 41 | }, 42 | }) 43 | -------------------------------------------------------------------------------- /lib/utils/regexp-ast/ast.ts: -------------------------------------------------------------------------------- 1 | import { parseRegExpLiteral, RegExpParser } from "@eslint-community/regexpp" 2 | import type { RegExpLiteral, Pattern } from "@eslint-community/regexpp/ast" 3 | import type { Rule } from "eslint" 4 | import type { Expression } from "estree" 5 | import { getStaticValue } from "../ast-utils" 6 | 7 | const parser = new RegExpParser() 8 | /** 9 | * Get Reg Exp node from given expression node 10 | */ 11 | export function getRegExpNodeFromExpression( 12 | node: Expression, 13 | context: Rule.RuleContext, 14 | ): RegExpLiteral | Pattern | null { 15 | if (node.type === "Literal") { 16 | if ("regex" in node && node.regex) { 17 | try { 18 | return parser.parsePattern( 19 | node.regex.pattern, 20 | 0, 21 | node.regex.pattern.length, 22 | { 23 | unicode: node.regex.flags.includes("u"), 24 | unicodeSets: node.regex.flags.includes("v"), 25 | }, 26 | ) 27 | } catch { 28 | return null 29 | } 30 | } 31 | return null 32 | } 33 | const evaluated = getStaticValue(context, node) 34 | if (!evaluated || !(evaluated.value instanceof RegExp)) { 35 | return null 36 | } 37 | try { 38 | return parseRegExpLiteral(evaluated.value) 39 | } catch { 40 | return null 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-named-replacement.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-named-replacement" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-named-replacement", rule as any, { 12 | valid: [ 13 | `"str".replace(/regexp/, "foo")`, 14 | `"str".replace(/a(b)c/, "_$1_")`, 15 | `"str".replaceAll(/a(b)c/, "_$1_")`, 16 | `"str".replace(/a(?b)c/, "_$_")`, 17 | `"str".replaceAll(/a(?b)c/, "_$_")`, 18 | `"str".replace(/a(?b)c/, "_$0_")`, 19 | `"str".replace(/(a)(?b)c/, "_$1_")`, 20 | `"str".replace(/a(b)c/, "_$2_")`, 21 | `unknown.replace(/a(?b)c/, "_$1_")`, 22 | `unknown.replaceAll(/a(?b)c/, "_$1_")`, 23 | ], 24 | invalid: [ 25 | `"str".replace(/a(?b)c/, "_$1_")`, 26 | `"str".replace(/a(?b)c/v, "_$1_")`, 27 | `"str".replaceAll(/a(?b)c/, "_$1_")`, 28 | `"str".replace(/(a)(?b)c/, "_$1$2_")`, 29 | { 30 | code: `unknown.replace(/a(?b)c/, "_$1_")`, 31 | options: [{ strictTypes: false }], 32 | }, 33 | { 34 | code: `unknown.replaceAll(/a(?b)c/, "_$1_")`, 35 | options: [{ strictTypes: false }], 36 | }, 37 | ], 38 | }) 39 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-question-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-question-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-question-quantifier", rule as any, { 12 | valid: [ 13 | "/a?/", 14 | "/a??/", 15 | "/(a?)/", 16 | "/(a??)/", 17 | "/[a{0,1}]/", 18 | "/a{0,}/", 19 | "/(?:a|b)/", 20 | "/a(?:|a)/", 21 | "/(?:abc||def)/", 22 | "/(?:)/", 23 | "/(?:||)/", 24 | "/(?:abc|def|)+/", 25 | "/(?:abc|def|)??/", 26 | ], 27 | invalid: [ 28 | "/a{0,1}/", 29 | "/a{0,1}?/", 30 | "/(a){0,1}/", 31 | "/(a){0,1}/v", 32 | "/(a){0,1}?/", 33 | "/(?:abc|)/", 34 | "/(?:abc|def|)/", 35 | "/(?:abc||def|)/", 36 | "/(?:abc|def||)/", 37 | "/(?:abc|def|)?/", 38 | ` 39 | const s = "a{0,1}" 40 | new RegExp(s) 41 | `, 42 | ` 43 | const s = "a{0,"+"1}" 44 | new RegExp(s) 45 | `, 46 | ` 47 | const s = "(?:abc|def|)" 48 | new RegExp(s) 49 | `, 50 | ` 51 | const s = "(?:abc|"+"def|)" 52 | new RegExp(s) 53 | `, 54 | ], 55 | }) 56 | -------------------------------------------------------------------------------- /tests/lib/rules/no-useless-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-useless-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-useless-quantifier", rule as any, { 12 | valid: [ 13 | String.raw`/a*/`, 14 | String.raw`/(?:a)?/`, 15 | String.raw`/(?:a|b?)??/`, 16 | String.raw`/(?:\b|a)?/`, 17 | String.raw`/(?:\b)*/`, 18 | String.raw`/(?:\b|(?!a))*/`, 19 | String.raw`/(?:\b|(?!))*/`, 20 | String.raw`/#[\da-z]+|#(?:-|([+/\\*~<>=@%|&?!])\1?)|#(?=\()/`, 21 | ], 22 | invalid: [ 23 | // trivial 24 | String.raw`/a{1}/`, 25 | String.raw`/a{1,1}?/`, 26 | 27 | // empty quantified element 28 | String.raw`/(?:)+/`, 29 | String.raw`/(?:[\q{}])+/v`, 30 | String.raw`/(?:|(?:)){5,9}/`, 31 | String.raw`/(?:|()()())*/`, 32 | 33 | // unnecessary optional quantifier (?) because the quantified element is potentially empty 34 | String.raw`/(?:a+b*|c*)?/`, 35 | String.raw`/(?:a|b?c?d?e?f?)?/`, 36 | 37 | // quantified elements which do not consume characters 38 | String.raw`/(?:\b)+/`, 39 | String.raw`/(?:\b){5,100}/`, 40 | String.raw`/(?:\b|(?!a))+/`, 41 | String.raw`/(?:\b|(?!)){6}/`, 42 | ], 43 | }) 44 | -------------------------------------------------------------------------------- /docs/rules/no-zero-quantifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-zero-quantifier" 5 | description: "disallow quantifiers with a maximum of zero" 6 | since: "v0.10.0" 7 | --- 8 | # regexp/no-zero-quantifier 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 13 | 14 | 15 | 16 | > disallow quantifiers with a maximum of zero 17 | 18 | ## :book: Rule Details 19 | 20 | This rule reports quantifiers with a maximum of zero. These quantifiers trivially do not affect the pattern is any way and can be removed. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-zero-quantifier: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /a?/; 29 | var foo = /a{0,}/; 30 | var foo = /a{0,1}/; 31 | 32 | /* ✗ BAD */ 33 | var foo = /a{0}/; 34 | var foo = /a{0,0}?/; 35 | var foo = /(a){0}/; 36 | ``` 37 | 38 | 39 | 40 | ## :wrench: Options 41 | 42 | Nothing. 43 | 44 | ## :rocket: Version 45 | 46 | This rule was introduced in eslint-plugin-regexp v0.10.0 47 | 48 | ## :mag: Implementation 49 | 50 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-zero-quantifier.ts) 51 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-zero-quantifier.ts) 52 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/state/serialize.js: -------------------------------------------------------------------------------- 1 | import pako from "pako" 2 | 3 | /** 4 | * Get only enabled rules to make the serialized data smaller. 5 | * @param {object} allRules The rule settings. 6 | * @returns {object} The rule settings for the enabled rules. 7 | */ 8 | function getEnabledRules(allRules) { 9 | return Object.keys(allRules).reduce((map, id) => { 10 | if (allRules[id] === "error") { 11 | map[id] = 2 12 | } 13 | return map 14 | }, {}) 15 | } 16 | 17 | /** 18 | * Serialize a given state as a base64 string. 19 | * @param {State} state The state to serialize. 20 | * @returns {string} The serialized string. 21 | */ 22 | export function serializeState(state) { 23 | const saveData = { 24 | code: state.code, 25 | rules: state.rules ? getEnabledRules(state.rules) : undefined, 26 | } 27 | const jsonString = JSON.stringify(saveData) 28 | const uint8Arr = new TextEncoder().encode(jsonString) 29 | const compressedString = String.fromCharCode(...pako.deflate(uint8Arr)) 30 | const base64 = 31 | (typeof window !== "undefined" && window.btoa(compressedString)) || 32 | compressedString 33 | 34 | //eslint-disable-next-line no-console -- demo 35 | console.log( 36 | `The compress rate of serialized string: ${( 37 | (100 * base64.length) / 38 | jsonString.length 39 | ).toFixed(1)}% (${jsonString.length}B → ${base64.length}B)`, 40 | ) 41 | 42 | return base64 43 | } 44 | -------------------------------------------------------------------------------- /docs/rules/no-useless-two-nums-quantifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-useless-two-nums-quantifier" 5 | description: "disallow unnecessary `{n,m}` quantifier" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/no-useless-two-nums-quantifier 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow unnecessary `{n,m}` quantifier 17 | 18 | ## :book: Rule Details 19 | 20 | This rule reports unnecessary `{n,m}` quantifiers. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-useless-two-nums-quantifier: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /a{0,1}/; 29 | var foo = /a{1,5}/; 30 | var foo = /a{1,}/; 31 | var foo = /a{2}/; 32 | 33 | /* ✗ BAD */ 34 | var foo = /a{0,0}/; 35 | var foo = /a{1,1}/; 36 | var foo = /a{2,2}/; 37 | ``` 38 | 39 | 40 | 41 | ## :wrench: Options 42 | 43 | Nothing. 44 | 45 | ## :rocket: Version 46 | 47 | This rule was introduced in eslint-plugin-regexp v0.1.0 48 | 49 | ## :mag: Implementation 50 | 51 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-useless-two-nums-quantifier.ts) 52 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-useless-two-nums-quantifier.ts) 53 | -------------------------------------------------------------------------------- /tests/lib/rules/sort-flags.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/sort-flags" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("sort-flags", rule as any, { 12 | valid: [ 13 | String.raw`/\w/i`, 14 | String.raw`/\w/im`, 15 | String.raw`/\w/gi`, 16 | String.raw`/\w/gimsuy`, 17 | String.raw`/\w/gimsvy`, 18 | String.raw`new RegExp("\\w", "i")`, 19 | String.raw`new RegExp("\\w", "gi")`, 20 | String.raw`new RegExp("\\w", "gimsuy")`, 21 | String.raw`new RegExp("\\w", "dgimsuy")`, 22 | String.raw`new RegExp("\\w", "dgimsvy")`, 23 | 24 | // ignore 25 | String.raw` 26 | const flags = "yusimg" 27 | new RegExp("\\w", flags) 28 | new RegExp("\\w", flags) 29 | `, 30 | ], 31 | invalid: [ 32 | String.raw`/\w/yusimg`, 33 | String.raw`/\w/yvsimg`, 34 | String.raw`new RegExp("\\w", "yusimg")`, 35 | String.raw`new RegExp("\\w", "yusimgd")`, 36 | 37 | // sort flags even on invalid patterns 38 | String.raw`new RegExp("\\w)", "ui")`, 39 | // sort flags even on unknown 40 | String.raw`RegExp('a' + b, 'us');`, 41 | // sort flags even on non-owned pattern 42 | String.raw`var a = "foo"; RegExp(foo, 'us'); RegExp(foo, 'u');`, 43 | ], 44 | }) 45 | -------------------------------------------------------------------------------- /docs/rules/no-escape-backspace.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-escape-backspace" 5 | description: "disallow escape backspace (`[\\b]`)" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/no-escape-backspace 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 13 | 14 | 15 | 16 | > disallow escape backspace (`[\b]`) 17 | 18 | ## :book: Rule Details 19 | 20 | This rule reports `[\b]`.\ 21 | The word boundaries (`\b`) and the escape backspace (`[\b]`) are indistinguishable at a glance. This rule does not allow backspace (`[\b]`). Use unicode escapes (`\u0008`) instead. 22 | 23 | 24 | 25 | ```js 26 | /* eslint regexp/no-escape-backspace: "error" */ 27 | 28 | /* ✓ GOOD */ 29 | var foo = /\b/; 30 | var foo = /\u0008/; 31 | var foo = /\cH/; 32 | var foo = /\x08/; 33 | 34 | /* ✗ BAD */ 35 | var foo = /[\b]/; 36 | ``` 37 | 38 | 39 | 40 | ## :wrench: Options 41 | 42 | Nothing. 43 | 44 | ## :rocket: Version 45 | 46 | This rule was introduced in eslint-plugin-regexp v0.1.0 47 | 48 | ## :mag: Implementation 49 | 50 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-escape-backspace.ts) 51 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-escape-backspace.ts) 52 | -------------------------------------------------------------------------------- /lib/utils/fix-simplify-quantifier.ts: -------------------------------------------------------------------------------- 1 | import type { Quantifier } from "@eslint-community/regexpp/ast" 2 | import type { Rule } from "eslint" 3 | import { getClosestAncestor } from "regexp-ast-analysis" 4 | import { quantToString } from "./regexp-ast" 5 | import type { CanSimplify } from "./regexp-ast" 6 | import type { RegExpContext } from "." 7 | 8 | /** 9 | * Returns a fixer to simplify the given quantifier. 10 | */ 11 | export function fixSimplifyQuantifier( 12 | quantifier: Quantifier, 13 | result: CanSimplify, 14 | { fixReplaceNode }: RegExpContext, 15 | ): [replacement: string, fix: (fixer: Rule.RuleFixer) => Rule.Fix | null] { 16 | const ancestor = getClosestAncestor(quantifier, ...result.dependencies) 17 | 18 | let replacement: string 19 | if (quantifier.min === 0) { 20 | replacement = "" 21 | } else if (quantifier.min === 1) { 22 | replacement = quantifier.element.raw 23 | } else { 24 | replacement = 25 | quantifier.element.raw + 26 | quantToString({ 27 | min: quantifier.min, 28 | max: quantifier.min, 29 | greedy: true, 30 | }) 31 | } 32 | 33 | return [ 34 | replacement, 35 | fixReplaceNode(ancestor, () => { 36 | return ( 37 | ancestor.raw.slice(0, quantifier.start - ancestor.start) + 38 | replacement + 39 | ancestor.raw.slice(quantifier.end - ancestor.start) 40 | ) 41 | }), 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | issues: write 11 | pull-requests: write 12 | 13 | jobs: 14 | release: 15 | name: Release 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout Repo 19 | uses: actions/checkout@v5 20 | with: 21 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 22 | fetch-depth: 0 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v5 25 | - name: Install Dependencies 26 | run: npm ci 27 | - name: Create Release Pull Request or Publish to npm 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | # this expects you to have a npm script called version that runs some logic and then calls `changeset version`. 32 | version: npm run version:ci 33 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 34 | publish: npm run release 35 | commit: "chore: release eslint-plugin-regexp" 36 | title: "chore: release eslint-plugin-regexp" 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | -------------------------------------------------------------------------------- /docs/rules/no-control-character.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-control-character" 5 | description: "disallow control characters" 6 | since: "v1.2.0" 7 | --- 8 | # regexp/no-control-character 9 | 10 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 11 | 12 | 13 | 14 | > disallow control characters 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports control characters. 19 | 20 | This rule is inspired by the [no-control-regex] rule. The positions of reports are improved over the core rule and suggestions are provided in some cases. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-control-character: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /\n/; 29 | var foo = RegExp("\n"); 30 | 31 | /* ✗ BAD */ 32 | var foo = /\x1f/; 33 | var foo = /\x0a/; 34 | var foo = RegExp('\x0a'); 35 | ``` 36 | 37 | 38 | 39 | ## :wrench: Options 40 | 41 | Nothing. 42 | 43 | ## :books: Further reading 44 | 45 | - [no-control-regex] 46 | 47 | [no-control-regex]: https://eslint.org/docs/rules/no-control-regex 48 | 49 | ## :rocket: Version 50 | 51 | This rule was introduced in eslint-plugin-regexp v1.2.0 52 | 53 | ## :mag: Implementation 54 | 55 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-control-character.ts) 56 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-control-character.ts) 57 | -------------------------------------------------------------------------------- /lib/rules/no-standalone-backslash.ts: -------------------------------------------------------------------------------- 1 | import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" 2 | import type { RegExpContext } from "../utils" 3 | import { createRule, defineRegexpVisitor, CP_BACK_SLASH } from "../utils" 4 | 5 | export default createRule("no-standalone-backslash", { 6 | meta: { 7 | docs: { 8 | description: "disallow standalone backslashes (`\\`)", 9 | category: "Best Practices", 10 | recommended: false, // `regexp/strict` rule meets this rule. 11 | }, 12 | schema: [], 13 | messages: { 14 | unexpected: 15 | "Unexpected standalone backslash (`\\`). It looks like an escape sequence, but it's a single `\\` character pattern.", 16 | }, 17 | type: "suggestion", // "problem", 18 | }, 19 | create(context) { 20 | function createVisitor({ 21 | node, 22 | getRegexpLocation, 23 | }: RegExpContext): RegExpVisitor.Handlers { 24 | return { 25 | onCharacterEnter(cNode) { 26 | if (cNode.value === CP_BACK_SLASH && cNode.raw === "\\") { 27 | context.report({ 28 | node, 29 | loc: getRegexpLocation(cNode), 30 | messageId: "unexpected", 31 | }) 32 | } 33 | }, 34 | } 35 | } 36 | 37 | return defineRegexpVisitor(context, { 38 | createVisitor, 39 | }) 40 | }, 41 | }) 42 | -------------------------------------------------------------------------------- /tools/update-rulesets.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import path from "path" 3 | // import eslint from "eslint" 4 | import { rules } from "./lib/load-rules" 5 | 6 | const coreRules = [ 7 | // Possible Errors 8 | "no-control-regex", 9 | "no-misleading-character-class", 10 | "no-regex-spaces", 11 | // Best Practices 12 | // "prefer-named-capture-group", // modern 13 | "prefer-regex-literals", 14 | // "require-unicode-regexp", // modern 15 | ] 16 | 17 | const content = `import type { SeverityString } from "../../types" 18 | 19 | export const rules: Record = { 20 | // ESLint core rules 21 | ${coreRules.map((ruleName) => `"${ruleName}": "error"`).join(",\n ")}, 22 | // The ESLint rule will report fewer cases than our rule 23 | "no-invalid-regexp": "off", 24 | "no-useless-backreference": "off", 25 | "no-empty-character-class": "off", 26 | 27 | // eslint-plugin-regexp rules 28 | ${rules 29 | .filter((rule) => rule.meta.docs.recommended && !rule.meta.deprecated) 30 | .map((rule) => { 31 | const conf = rule.meta.docs.default || "error" 32 | return `"${rule.meta.docs.ruleId}": "${conf}"` 33 | }) 34 | .join(",\n ")}, 35 | } 36 | ` 37 | 38 | const filePath = path.resolve(__dirname, "../lib/configs/rules/recommended.ts") 39 | 40 | // Update file. 41 | fs.writeFileSync(filePath, content) 42 | 43 | // Format files. 44 | // const linter = new eslint.CLIEngine({ fix: true }) 45 | // const report = linter.executeOnFiles([filePath]) 46 | // eslint.CLIEngine.outputFixes(report) 47 | -------------------------------------------------------------------------------- /lib/rules/no-empty-string-literal.ts: -------------------------------------------------------------------------------- 1 | import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" 2 | import type { RegExpContext } from "../utils" 3 | import { createRule, defineRegexpVisitor } from "../utils" 4 | 5 | export default createRule("no-empty-string-literal", { 6 | meta: { 7 | docs: { 8 | description: "disallow empty string literals in character classes", 9 | category: "Best Practices", 10 | recommended: true, 11 | }, 12 | schema: [], 13 | messages: { 14 | unexpected: "Unexpected empty string literal.", 15 | }, 16 | type: "suggestion", // "problem", 17 | }, 18 | create(context) { 19 | function createVisitor( 20 | regexpContext: RegExpContext, 21 | ): RegExpVisitor.Handlers { 22 | const { node, getRegexpLocation } = regexpContext 23 | 24 | return { 25 | onClassStringDisjunctionEnter(csdNode) { 26 | if ( 27 | csdNode.alternatives.every( 28 | (alt) => alt.elements.length === 0, 29 | ) 30 | ) { 31 | context.report({ 32 | node, 33 | loc: getRegexpLocation(csdNode), 34 | messageId: "unexpected", 35 | }) 36 | } 37 | }, 38 | } 39 | } 40 | 41 | return defineRegexpVisitor(context, { 42 | createVisitor, 43 | }) 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /tests/lib/rules/no-obscure-range.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-obscure-range" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-obscure-range", rule as any, { 12 | valid: [ 13 | String.raw`/[\d\0-\x1f\cA-\cZ\2-\5\012-\123\x10-\uffff a-z a-f]/`, 14 | { 15 | code: "/[а-я А-Я]/", 16 | options: [{ allowed: ["alphanumeric", "а-я", "А-Я"] }], 17 | }, 18 | { 19 | code: "/[а-я А-Я]/", 20 | settings: { 21 | regexp: { 22 | allowedCharacterRanges: ["alphanumeric", "а-я", "А-Я"], 23 | }, 24 | }, 25 | }, 26 | "/[[0-9]--[6-8]]/v", 27 | ], 28 | invalid: [ 29 | { 30 | // rule options override settings 31 | code: "/[а-я А-Я]/", 32 | options: [{ allowed: "alphanumeric" }], 33 | settings: { 34 | regexp: { 35 | allowedCharacterRanges: ["alphanumeric", "а-я", "А-Я"], 36 | }, 37 | }, 38 | }, 39 | 40 | String.raw`/[\1-\x13]/`, 41 | String.raw`/[\x20-\113]/`, 42 | 43 | String.raw`/[\n-\r]/`, 44 | 45 | String.raw`/[\cA-Z]/`, 46 | 47 | "/[A-z]/", 48 | "/[0-A]/", 49 | "/[Z-a]/", 50 | String.raw`/[A-\x43]/`, 51 | 52 | "/[*+-/]/", 53 | String.raw`/[[ -\/]--+]/v`, 54 | ], 55 | }) 56 | -------------------------------------------------------------------------------- /docs/rules/no-invisible-character.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-invisible-character" 5 | description: "disallow invisible raw character" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/no-invisible-character 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow invisible raw character 17 | 18 | ## :book: Rule Details 19 | 20 | This rule disallows using invisible characters other than SPACE (`U+0020`) without using escapes. 21 | 22 | 23 | 24 | 25 | 26 | ```js 27 | /* eslint regexp/no-invisible-character: "error" */ 28 | 29 | /* ✓ GOOD */ 30 | var foo = /\t/; 31 | var foo = /\v/; 32 | var foo = /\f/; 33 | var foo = /\u3000/; 34 | var foo = / /; // SPACE (`U+0020`) 35 | 36 | /* ✗ BAD */ 37 | var foo = / /; 38 | var foo = / /; 39 | var foo = / /; 40 | var foo = / /; 41 | ``` 42 | 43 | 44 | 45 | 46 | 47 | ## :wrench: Options 48 | 49 | Nothing. 50 | 51 | ## :rocket: Version 52 | 53 | This rule was introduced in eslint-plugin-regexp v0.1.0 54 | 55 | ## :mag: Implementation 56 | 57 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-invisible-character.ts) 58 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-invisible-character.ts) 59 | -------------------------------------------------------------------------------- /lib/utils/regexp-ast/quantifier.ts: -------------------------------------------------------------------------------- 1 | import type { Quantifier } from "@eslint-community/regexpp/ast" 2 | 3 | /** 4 | * Get the offsets of the given quantifier 5 | */ 6 | export function getQuantifierOffsets(qNode: Quantifier): [number, number] { 7 | const startOffset = qNode.element.end - qNode.start 8 | const endOffset = qNode.raw.length - (qNode.greedy ? 0 : 1) 9 | return [startOffset, endOffset] 10 | } 11 | 12 | export interface Quant { 13 | min: number 14 | max: number 15 | greedy?: boolean 16 | } 17 | 18 | /** 19 | * Returns the string representation of the given quantifier. 20 | */ 21 | export function quantToString(quant: Readonly): string { 22 | if ( 23 | quant.max < quant.min || 24 | quant.min < 0 || 25 | !Number.isInteger(quant.min) || 26 | !(Number.isInteger(quant.max) || quant.max === Infinity) 27 | ) { 28 | throw new Error( 29 | `Invalid quantifier { min: ${quant.min}, max: ${quant.max} }`, 30 | ) 31 | } 32 | 33 | let value 34 | if (quant.min === 0 && quant.max === 1) { 35 | value = "?" 36 | } else if (quant.min === 0 && quant.max === Infinity) { 37 | value = "*" 38 | } else if (quant.min === 1 && quant.max === Infinity) { 39 | value = "+" 40 | } else if (quant.min === quant.max) { 41 | value = `{${quant.min}}` 42 | } else if (quant.max === Infinity) { 43 | value = `{${quant.min},}` 44 | } else { 45 | value = `{${quant.min},${quant.max}}` 46 | } 47 | 48 | if (quant.greedy === false) { 49 | return `${value}?` 50 | } 51 | return value 52 | } 53 | -------------------------------------------------------------------------------- /docs/rules/no-trivially-nested-quantifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-trivially-nested-quantifier" 5 | description: "disallow nested quantifiers that can be rewritten as one quantifier" 6 | since: "v0.9.0" 7 | --- 8 | # regexp/no-trivially-nested-quantifier 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow nested quantifiers that can be rewritten as one quantifier 17 | 18 | ## :book: Rule Details 19 | 20 | In some cases, nested quantifiers can be rewritten as one quantifier (e.g. `(?:a{1,2}){3}` -> `a{3,6}`). 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-trivially-nested-quantifier: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /(a{1,2})+/; // the rule won't touch capturing groups 29 | var foo = /(?:a{2})+/; 30 | 31 | /* ✗ BAD */ 32 | var foo = /(?:a{1,2})+/; 33 | var foo = /(?:a{1,2}){3,4}/; 34 | var foo = /(?:a{4,}){5}/; 35 | ``` 36 | 37 | 38 | 39 | ## :wrench: Options 40 | 41 | Nothing. 42 | 43 | ## :rocket: Version 44 | 45 | This rule was introduced in eslint-plugin-regexp v0.9.0 46 | 47 | ## :mag: Implementation 48 | 49 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-trivially-nested-quantifier.ts) 50 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-trivially-nested-quantifier.ts) 51 | -------------------------------------------------------------------------------- /docs/rules/prefer-predefined-assertion.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-predefined-assertion" 5 | description: "prefer predefined assertion over equivalent lookarounds" 6 | since: "v0.10.0" 7 | --- 8 | # regexp/prefer-predefined-assertion 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > prefer predefined assertion over equivalent lookarounds 17 | 18 | ## :book: Rule Details 19 | 20 | All predefined assertions (`\b`, `\B`, `^`, and `$`) can be expressed as lookaheads and lookbehinds. E.g. `/a$/` is the same as `/a(?![^])/`. 21 | 22 | In most cases, it's better to use the predefined assertions because they are better known. 23 | 24 | 25 | 26 | ```js 27 | /* eslint regexp/prefer-predefined-assertion: "error" */ 28 | 29 | /* ✓ GOOD */ 30 | var foo = /a(?=\W)/; 31 | 32 | /* ✗ BAD */ 33 | var foo = /a(?![^])/; 34 | var foo = /a(?!\w)/; 35 | var foo = /a+(?!\w)(?:\s|bc+)+/; 36 | ``` 37 | 38 | 39 | 40 | ## :wrench: Options 41 | 42 | Nothing. 43 | 44 | ## :rocket: Version 45 | 46 | This rule was introduced in eslint-plugin-regexp v0.10.0 47 | 48 | ## :mag: Implementation 49 | 50 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-predefined-assertion.ts) 51 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-predefined-assertion.ts) 52 | -------------------------------------------------------------------------------- /lib/utils/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Throws if the function is called. This is useful for ensuring that a switch statement is exhaustive. 3 | */ 4 | export function assertNever(value: never): never { 5 | throw new Error(`Invalid value: ${value}`) 6 | } 7 | 8 | /** 9 | * Returns a cached version of the given function for lazy evaluation. 10 | * 11 | * For the cached function to behave correctly, the given function must be pure. 12 | */ 13 | export function lazy | null>( 14 | fn: () => R, 15 | ): () => R { 16 | let cached: R | undefined 17 | return () => { 18 | if (cached === undefined) { 19 | cached = fn() 20 | } 21 | return cached 22 | } 23 | } 24 | 25 | /** 26 | * Returns a cached version of the given function. A `WeakMap` is used internally. 27 | * 28 | * For the cached function to behave correctly, the given function must be pure. 29 | */ 30 | export function cachedFn( 31 | fn: (key: K) => R, 32 | ): (key: K) => R { 33 | const cache = new WeakMap() 34 | return (key) => { 35 | let cached = cache.get(key) 36 | if (cached === undefined) { 37 | cached = fn(key) 38 | cache.set(key, cached) 39 | } 40 | return cached 41 | } 42 | } 43 | 44 | /** 45 | * Returns all code points of the given string. 46 | */ 47 | export function toCodePoints(s: string): number[] { 48 | return [...s].map((c) => c.codePointAt(0)!) 49 | } 50 | 51 | /** 52 | * Returns an array of the given iterable in reverse order. 53 | */ 54 | export function reversed(iter: Iterable): T[] { 55 | return [...iter].reverse() 56 | } 57 | -------------------------------------------------------------------------------- /lib/rules/prefer-named-capture-group.ts: -------------------------------------------------------------------------------- 1 | import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" 2 | import type { RegExpContext } from "../utils" 3 | import { createRule, defineRegexpVisitor } from "../utils" 4 | import { mention } from "../utils/mention" 5 | 6 | export default createRule("prefer-named-capture-group", { 7 | meta: { 8 | docs: { 9 | description: "enforce using named capture groups", 10 | category: "Stylistic Issues", 11 | recommended: false, 12 | }, 13 | schema: [], 14 | messages: { 15 | required: 16 | "Capture group {{group}} should be converted to a named or non-capturing group.", 17 | }, 18 | type: "suggestion", 19 | }, 20 | create(context) { 21 | function createVisitor( 22 | regexpContext: RegExpContext, 23 | ): RegExpVisitor.Handlers { 24 | const { node, getRegexpLocation } = regexpContext 25 | 26 | return { 27 | onCapturingGroupEnter(cgNode) { 28 | if (cgNode.name === null) { 29 | context.report({ 30 | node, 31 | loc: getRegexpLocation(cgNode), 32 | messageId: "required", 33 | data: { 34 | group: mention(cgNode), 35 | }, 36 | }) 37 | } 38 | }, 39 | } 40 | } 41 | 42 | return defineRegexpVisitor(context, { 43 | createVisitor, 44 | }) 45 | }, 46 | }) 47 | -------------------------------------------------------------------------------- /docs/rules/control-character-escape.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/control-character-escape" 5 | description: "enforce consistent escaping of control characters" 6 | since: "v0.9.0" 7 | --- 8 | # regexp/control-character-escape 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > enforce consistent escaping of control characters 17 | 18 | ## :book: Rule Details 19 | 20 | This rule reports control characters that were not escaped using a control escape (`\0`, `\t`, `\n`, `\v`, `\f`, `\r`). 21 | 22 | 23 | 24 | 25 | 26 | ```js 27 | /* eslint regexp/control-character-escape: "error" */ 28 | 29 | /* ✓ GOOD */ 30 | var foo = /[\n\r]/; 31 | var foo = /\t/; 32 | var foo = RegExp("\t+\n"); 33 | 34 | /* ✗ BAD */ 35 | var foo = / /; 36 | var foo = /\u0009/; 37 | var foo = /\u{a}/u; 38 | var foo = RegExp("\\u000a"); 39 | ``` 40 | 41 | 42 | 43 | 44 | 45 | ## :wrench: Options 46 | 47 | Nothing. 48 | 49 | ## :rocket: Version 50 | 51 | This rule was introduced in eslint-plugin-regexp v0.9.0 52 | 53 | ## :mag: Implementation 54 | 55 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/control-character-escape.ts) 56 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/control-character-escape.ts) 57 | -------------------------------------------------------------------------------- /tests/lib/rules/use-ignore-case.ts: -------------------------------------------------------------------------------- 1 | import { ESLint } from "eslint" 2 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 3 | import semver from "semver" 4 | import rule from "../../../lib/rules/use-ignore-case" 5 | 6 | const tester = new SnapshotRuleTester({ 7 | languageOptions: { 8 | ecmaVersion: "latest", 9 | sourceType: "module", 10 | }, 11 | }) 12 | 13 | tester.run("use-ignore-case", rule as any, { 14 | valid: [ 15 | String.raw`/regexp/`, 16 | String.raw`/[aA]/i`, 17 | String.raw`/[aA]a/`, 18 | String.raw`/[aAb]/`, 19 | String.raw`/[aaaa]/`, 20 | 21 | String.raw`/regexp/u`, 22 | String.raw`/[aA]/iu`, 23 | String.raw`/[aA]a/u`, 24 | String.raw`/[aAb]/u`, 25 | String.raw`/[aaaa]/u`, 26 | String.raw`/\b[aA]/u`, 27 | String.raw`/[a-zA-Z]/u`, 28 | 29 | String.raw`/regexp/v`, 30 | String.raw`/[aA]/iv`, 31 | String.raw`/[aA]a/v`, 32 | String.raw`/[aAb]/v`, 33 | String.raw`/[aaaa]/v`, 34 | String.raw`/\b[aA]/v`, 35 | String.raw`/[a-zA-Z]/v`, 36 | 37 | // partial pattern 38 | String.raw`/[a-zA-Z]/.source`, 39 | ], 40 | invalid: [ 41 | String.raw`/[a-zA-Z]/`, 42 | String.raw`/[aA][aA][aA][aA][aA]/`, 43 | String.raw`/[aA]/u`, 44 | String.raw`/[aA]/v`, 45 | String.raw`/\b0[xX][a-fA-F0-9]+\b/`, 46 | String.raw`RegExp("[a-zA-Z]")`, 47 | String.raw`/[\q{a|A}]/v`, 48 | // ES2025 49 | ...(semver.gte(ESLint.version, "9.6.0") 50 | ? [String.raw`/(?:(?[aA])|(?[bB]))\k/`] 51 | : []), 52 | ], 53 | }) 54 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-quantifier.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/prefer-quantifier" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("prefer-quantifier", rule as any, { 12 | valid: [ 13 | `/regexp/`, 14 | `/\\d_\\d/`, 15 | `/Google/`, 16 | `/2000/`, 17 | `/ 11 | 12 | > enforces escape of replacement `$` character (`$$`). 13 | 14 | ## :book: Rule Details 15 | 16 | This rule aims to enforce correct dollar sign escapes (`$$`) when using `$` in replacement pattern of string replacements. 17 | 18 | 19 | 20 | ```js 21 | /* eslint regexp/prefer-escape-replacement-dollar-char: "error" */ 22 | 23 | /* ✓ GOOD */ 24 | '€1,234'.replace(/€/, '$$'); // "$1,234" 25 | 26 | 27 | /* ✗ BAD */ 28 | '€1,234'.replace(/€/, '$'); // "$1,234" 29 | ``` 30 | 31 | 32 | 33 | ## :wrench: Options 34 | 35 | Nothing. 36 | 37 | ## :couple: Related rules 38 | 39 | - [regexp/no-useless-dollar-replacements](./no-useless-dollar-replacements.md) 40 | 41 | ## :books: Further reading 42 | 43 | - [MDN Web Docs - String.prototype.replace() > Specifying a string as a parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter) 44 | 45 | ## :rocket: Version 46 | 47 | This rule was introduced in eslint-plugin-regexp v0.6.0 48 | 49 | ## :mag: Implementation 50 | 51 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-escape-replacement-dollar-char.ts) 52 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-escape-replacement-dollar-char.ts) 53 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-empty-character-class.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-empty-character-class >> invalid 5 | Code: 6 | 1 | /[]/ 7 | | ^~ [1] 8 | 9 | [1] This character class matches no characters because it is empty. 10 | --- 11 | 12 | 13 | Test: no-empty-character-class >> invalid 14 | Code: 15 | 1 | /abc[]/ 16 | | ^~ [1] 17 | 18 | [1] This character class matches no characters because it is empty. 19 | --- 20 | 21 | 22 | Test: no-empty-character-class >> invalid 23 | Code: 24 | 1 | /([])/ 25 | | ^~ [1] 26 | 27 | [1] This character class matches no characters because it is empty. 28 | --- 29 | 30 | 31 | Test: no-empty-character-class >> invalid 32 | Code: 33 | 1 | new RegExp("[]"); 34 | | ^~ [1] 35 | 36 | [1] This character class matches no characters because it is empty. 37 | --- 38 | 39 | 40 | Test: no-empty-character-class >> invalid 41 | Code: 42 | 1 | /[^\s\S]/ 43 | | ^~~~~~~ [1] 44 | 45 | [1] This character class cannot match any characters. 46 | --- 47 | 48 | 49 | Test: no-empty-character-class >> invalid 50 | Code: 51 | 1 | /[^\da-zA-Z_\W]/ 52 | | ^~~~~~~~~~~~~~ [1] 53 | 54 | [1] This character class cannot match any characters. 55 | --- 56 | 57 | 58 | Test: no-empty-character-class >> invalid 59 | Code: 60 | 1 | /a[[a&&b]]/v 61 | | ^~~~~~~~ [1] 62 | | ^~~~~~ [2] 63 | 64 | [1] This character class cannot match any characters. 65 | [2] This character class cannot match any characters. 66 | --- 67 | 68 | 69 | Test: no-empty-character-class >> invalid 70 | Code: 71 | 1 | /a[a&&b]/v 72 | | ^~~~~~ [1] 73 | 74 | [1] This character class cannot match any characters. 75 | --- 76 | -------------------------------------------------------------------------------- /tests/lib/rules/no-super-linear-move.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-super-linear-move" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-super-linear-move", rule as any, { 12 | valid: [ 13 | String.raw`/regexp/`, 14 | String.raw`/\ba*:/`, 15 | 16 | // no rejecting suffix 17 | String.raw`/a*/`, 18 | 19 | { 20 | code: String.raw`/a*/.test(input)`, 21 | options: [{ report: "potential" }], 22 | }, 23 | { 24 | code: String.raw`export default /a*/`, 25 | options: [{ report: "certain" }], 26 | }, 27 | { code: String.raw`/a*b/.source`, options: [{ ignorePartial: true }] }, 28 | { code: String.raw`/a*b/y`, options: [{ ignoreSticky: true }] }, 29 | String.raw`/[\q{abc}]+/v`, 30 | ], 31 | invalid: [ 32 | String.raw`/a*:/`, 33 | String.raw`/^\s*(\w+)\s*[:=]/m`, 34 | String.raw`/((?:\\{2})*)(\\?)\|/g`, 35 | String.raw`/[a-z_][A-Z_0-9]*(?=\s*\()/i`, 36 | String.raw`/(?!\d)\w+(?=\s*\()/i`, 37 | 38 | { 39 | code: String.raw`export default /a*/`, 40 | options: [{ report: "potential" }], 41 | }, 42 | { 43 | code: String.raw`/a*b/.source`, 44 | options: [{ ignorePartial: false }], 45 | }, 46 | { 47 | code: String.raw`/a*b/y`, 48 | options: [{ ignoreSticky: false }], 49 | }, 50 | String.raw`/[\q{abc}]+a/v`, 51 | ], 52 | }) 53 | -------------------------------------------------------------------------------- /docs/rules/prefer-set-operation.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-set-operation" 5 | description: "prefer character class set operations instead of lookarounds" 6 | since: "v2.0.0-next.9" 7 | --- 8 | # regexp/prefer-set-operation 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > prefer character class set operations instead of lookarounds 17 | 18 | ## :book: Rule Details 19 | 20 | Regular expressions with the `v` flag have access to character class set operations (e.g. `/[\s&&\p{ASCII}]/v`, `/[\w--\d]/v`). These are more readable and performant than using lookarounds to achieve the same effect. For example, `/(?!\d)\w/v` is the same as `/[\w--\d]/v`. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/prefer-set-operation: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /(?!\d)\w/ // requires the v flag 29 | var foo = /(?!\d)\w/u // requires the v flag 30 | 31 | /* ✗ BAD */ 32 | var foo = /(?!\d)\w/v 33 | var foo = /(?!\s)[\w\P{ASCII}]/v 34 | ``` 35 | 36 | 37 | 38 | ## :wrench: Options 39 | 40 | Nothing. 41 | 42 | ## :rocket: Version 43 | 44 | This rule was introduced in eslint-plugin-regexp v2.0.0-next.9 45 | 46 | ## :mag: Implementation 47 | 48 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-set-operation.ts) 49 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-set-operation.ts) 50 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/prefer-regexp-exec.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: prefer-regexp-exec >> invalid 5 | Code: 6 | 1 | 7 | 2 | 'something'.match(/thing/); 8 | | ^~~~~~~~~~~~~~~~~~~~~~~~~~ [1] 9 | 3 | 10 | 4 | 'some things are just things'.match(/thing/); 11 | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2] 12 | 5 | 13 | 6 | const text = 'something'; 14 | 7 | const search = /thing/; 15 | 8 | text.match(search); 16 | | ^~~~~~~~~~~~~~~~~~ [3] 17 | 9 | 18 | 19 | [1] Use the `RegExp#exec()` method instead. 20 | [2] Use the `RegExp#exec()` method instead. 21 | [3] Use the `RegExp#exec()` method instead. 22 | --- 23 | 24 | 25 | Test: prefer-regexp-exec >> invalid 26 | Code: 27 | 1 | 28 | 2 | const fn = (a) => a + '' 29 | 3 | fn(1).match(search); 30 | | ^~~~~~~~~~~~~~~~~~~ [1] 31 | 4 | 32 | 33 | [1] Use the `RegExp#exec()` method instead. 34 | --- 35 | 36 | 37 | Test: prefer-regexp-exec >> invalid 38 | Code: 39 | 1 | 40 | 2 | const v = a + b 41 | 3 | v.match(search); 42 | | ^~~~~~~~~~~~~~~ [1] 43 | 4 | 44 | 5 | const n = 1 + 2 45 | 6 | n.match(search); // ignore 46 | 7 | 47 | 48 | [1] Use the `RegExp#exec()` method instead. 49 | --- 50 | 51 | 52 | Test: prefer-regexp-exec >> invalid 53 | Code: 54 | 1 | 55 | 2 | 'something'.match(/thin[[g]]/v); 56 | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1] 57 | 3 | 58 | 59 | [1] Use the `RegExp#exec()` method instead. 60 | --- 61 | -------------------------------------------------------------------------------- /lib/rules/no-empty-group.ts: -------------------------------------------------------------------------------- 1 | import type { Group, CapturingGroup } from "@eslint-community/regexpp/ast" 2 | import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" 3 | import type { RegExpContext } from "../utils" 4 | import { createRule, defineRegexpVisitor } from "../utils" 5 | 6 | export default createRule("no-empty-group", { 7 | meta: { 8 | docs: { 9 | description: "disallow empty group", 10 | category: "Possible Errors", 11 | recommended: true, 12 | }, 13 | schema: [], 14 | messages: { 15 | unexpected: "Unexpected empty group.", 16 | }, 17 | type: "suggestion", 18 | }, 19 | create(context) { 20 | function verifyGroup( 21 | { node, getRegexpLocation }: RegExpContext, 22 | gNode: Group | CapturingGroup, 23 | ) { 24 | if (gNode.alternatives.every((alt) => alt.elements.length === 0)) { 25 | context.report({ 26 | node, 27 | loc: getRegexpLocation(gNode), 28 | messageId: "unexpected", 29 | }) 30 | } 31 | } 32 | 33 | function createVisitor( 34 | regexpContext: RegExpContext, 35 | ): RegExpVisitor.Handlers { 36 | return { 37 | onGroupEnter(gNode) { 38 | verifyGroup(regexpContext, gNode) 39 | }, 40 | onCapturingGroupEnter(cgNode) { 41 | verifyGroup(regexpContext, cgNode) 42 | }, 43 | } 44 | } 45 | 46 | return defineRegexpVisitor(context, { 47 | createVisitor, 48 | }) 49 | }, 50 | }) 51 | -------------------------------------------------------------------------------- /docs/rules/prefer-named-backreference.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-named-backreference" 5 | description: "enforce using named backreferences" 6 | since: "v0.9.0" 7 | --- 8 | # regexp/prefer-named-backreference 9 | 10 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 11 | 12 | 13 | 14 | > enforce using named backreferences 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports and fixes backreferences that do not use the name of their referenced capturing group. 19 | 20 | 21 | 22 | ```js 23 | /* eslint regexp/prefer-named-backreference: "error" */ 24 | 25 | /* ✓ GOOD */ 26 | var foo = /(a)\1/ 27 | var foo = /(?a)\k/ 28 | 29 | /* ✗ BAD */ 30 | var foo = /(?a)\1/ 31 | ``` 32 | 33 | 34 | 35 | ## :wrench: Options 36 | 37 | Nothing. 38 | 39 | ## :couple: Related rules 40 | 41 | - [regexp/prefer-named-capture-group] 42 | - [regexp/prefer-named-replacement] 43 | - [regexp/prefer-result-array-groups] 44 | 45 | [regexp/prefer-named-capture-group]: ./prefer-named-capture-group.md 46 | [regexp/prefer-named-replacement]: ./prefer-named-replacement.md 47 | [regexp/prefer-result-array-groups]: ./prefer-result-array-groups.md 48 | 49 | ## :rocket: Version 50 | 51 | This rule was introduced in eslint-plugin-regexp v0.9.0 52 | 53 | ## :mag: Implementation 54 | 55 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-named-backreference.ts) 56 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-named-backreference.ts) 57 | -------------------------------------------------------------------------------- /docs/rules/no-octal.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-octal" 5 | description: "disallow octal escape sequence" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/no-octal 9 | 10 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 11 | 12 | 13 | 14 | > disallow octal escape sequence 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports octal escapes. 19 | 20 | `\0` matches the `NUL` character. However, if `\0` is followed by another digit, it will become an octal escape sequence (e.g. `\07`). 21 | 22 | Octal escapes can also easily be confused with backreferences. The same character sequence (e.g. `\3`) can either escape a character or be a backreference depending on the number of capturing groups in the pattern. E.g. the `\2` in `/(a)\2/` is a character but the `\2` in `/(a)(b)\2/` is a backreference. This can be a problem when refactoring regular expressions because an octal escape can become a backreference or vice versa. 23 | 24 | 25 | 26 | ```js 27 | /* eslint regexp/no-octal: "error" */ 28 | 29 | /* ✓ GOOD */ 30 | var foo = /\0/; 31 | var foo = /=/; 32 | var foo = /(a)\1/; 33 | 34 | /* ✗ BAD */ 35 | var foo = /\075/; 36 | var foo = /\1/; 37 | ``` 38 | 39 | 40 | 41 | ## :wrench: Options 42 | 43 | Nothing. 44 | 45 | ## :rocket: Version 46 | 47 | This rule was introduced in eslint-plugin-regexp v0.1.0 48 | 49 | ## :mag: Implementation 50 | 51 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-octal.ts) 52 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-octal.ts) 53 | -------------------------------------------------------------------------------- /docs/rules/negation.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/negation" 5 | description: "enforce use of escapes on negation" 6 | since: "v0.4.0" 7 | --- 8 | # regexp/negation 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > enforce use of escapes on negation 17 | 18 | ## :book: Rule Details 19 | 20 | This rule enforces use of `\D`, `\W`, `\S` and `\P` on negation. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/negation: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | var foo = /\D/ 29 | var foo = /\W/ 30 | var foo = /\S/ 31 | var foo = /\P{ASCII}/u 32 | 33 | var foo = /\d/ 34 | var foo = /\w/ 35 | var foo = /\s/ 36 | var foo = /\p{ASCII}/u 37 | 38 | /* ✗ BAD */ 39 | var foo = /[^\d]/ 40 | var foo = /[^\w]/ 41 | var foo = /[^\s]/ 42 | var foo = /[^\p{ASCII}]/u 43 | 44 | var foo = /[^\D]/ 45 | var foo = /[^\W]/ 46 | var foo = /[^\S]/ 47 | var foo = /[^\P{ASCII}]/u 48 | ``` 49 | 50 | 51 | 52 | ## :wrench: Options 53 | 54 | Nothing. 55 | 56 | ## :couple: Related rules 57 | 58 | - [regexp/simplify-set-operations] 59 | 60 | [regexp/simplify-set-operations]: ./simplify-set-operations.md 61 | 62 | ## :rocket: Version 63 | 64 | This rule was introduced in eslint-plugin-regexp v0.4.0 65 | 66 | ## :mag: Implementation 67 | 68 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/negation.ts) 69 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/negation.ts) 70 | -------------------------------------------------------------------------------- /lib/rules/no-non-standard-flag.ts: -------------------------------------------------------------------------------- 1 | import type { RegExpContext, UnparsableRegExpContext } from "../utils" 2 | import { createRule, defineRegexpVisitor } from "../utils" 3 | 4 | const STANDARD_FLAGS = "dgimsuvy" 5 | 6 | export default createRule("no-non-standard-flag", { 7 | meta: { 8 | docs: { 9 | description: "disallow non-standard flags", 10 | category: "Best Practices", 11 | recommended: true, 12 | }, 13 | schema: [], 14 | messages: { 15 | unexpected: "Unexpected non-standard flag '{{flag}}'.", 16 | }, 17 | type: "suggestion", // "problem", 18 | }, 19 | create(context) { 20 | /** The logic of this rule */ 21 | function visit({ 22 | regexpNode, 23 | getFlagsLocation, 24 | flagsString, 25 | }: RegExpContext | UnparsableRegExpContext) { 26 | if (flagsString) { 27 | const nonStandard = [...flagsString].filter( 28 | (f) => !STANDARD_FLAGS.includes(f), 29 | ) 30 | 31 | if (nonStandard.length > 0) { 32 | context.report({ 33 | node: regexpNode, 34 | loc: getFlagsLocation(), 35 | messageId: "unexpected", 36 | data: { flag: nonStandard[0] }, 37 | }) 38 | } 39 | } 40 | } 41 | 42 | return defineRegexpVisitor(context, { 43 | createVisitor(regexpContext) { 44 | visit(regexpContext) 45 | return {} 46 | }, 47 | visitInvalid: visit, 48 | visitUnknown: visit, 49 | }) 50 | }, 51 | }) 52 | -------------------------------------------------------------------------------- /docs/rules/no-useless-set-operand.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-useless-set-operand" 5 | description: "disallow unnecessary elements in expression character classes" 6 | since: "v2.0.0-next.10" 7 | --- 8 | # regexp/no-useless-set-operand 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow unnecessary elements in expression character classes 17 | 18 | ## :book: Rule Details 19 | 20 | The `v` flag added set operations for character classes, e.g. `[\w&&\D]` and `[\w--\d]`, but there are no limitations on what operands can be used. This rule reports any unnecessary operands. 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/no-useless-set-operand: "error" */ 26 | 27 | /* ✓ GOOD */ 28 | foo = /[\w--\d]/v 29 | foo = /[\w--[\d_]]/v 30 | 31 | /* ✗ BAD */ 32 | foo = /[\w--[\d$]]/v 33 | foo = /[\w&&\d]/v 34 | foo = /[\w&&\s]/v 35 | foo = /[\w&&[\d\s]]/v 36 | foo = /[\w&&[^\d\s]]/v 37 | foo = /[\w--\s]/v 38 | foo = /[\d--\w]/v 39 | foo = /[\w--[\d\s]]/v 40 | foo = /[\w--[^\d\s]]/v 41 | 42 | ``` 43 | 44 | 45 | 46 | ## :wrench: Options 47 | 48 | Nothing. 49 | 50 | ## :rocket: Version 51 | 52 | This rule was introduced in eslint-plugin-regexp v2.0.0-next.10 53 | 54 | ## :mag: Implementation 55 | 56 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-useless-set-operand.ts) 57 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-useless-set-operand.ts) 58 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/grapheme-string-literal.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: grapheme-string-literal >> invalid 5 | Code: 6 | 1 | /[\q{abc}]/v 7 | | ^~~ [1] 8 | 9 | [1] Only single characters and graphemes are allowed inside character classes. Use regular alternatives (e.g. `(?:abc|[...])`) for strings instead. 10 | --- 11 | 12 | 13 | Test: grapheme-string-literal >> invalid 14 | Code: 15 | 1 | /[\q{a|bc|}]/v 16 | | ^~ [1] 17 | 18 | [1] Only single characters and graphemes are allowed inside character classes. Use regular alternatives (e.g. `(?:bc|[...])`) for strings instead. 19 | --- 20 | 21 | 22 | Test: grapheme-string-literal >> invalid 23 | Code: 24 | 1 | /[\q{🇦🇨🇦🇩}]/v 25 | | ^~~~~~~~ [1] 26 | 27 | [1] Only single characters and graphemes are allowed inside character classes. Use regular alternatives (e.g. `(?:🇦🇨🇦🇩|[...])`) for strings instead. 28 | --- 29 | 30 | 31 | Test: grapheme-string-literal >> invalid 32 | Code: 33 | 1 | /[\q{abc|def|ghi|j|k|lm|n}]/v 34 | | ^~~ ^~~ ^~~ ^~ 35 | | [1] [2] [3] [4] 36 | 37 | [1] Only single characters and graphemes are allowed inside character classes. Use regular alternatives (e.g. `(?:abc|def|ghi|lm|[...])`) for strings instead. 38 | [2] Only single characters and graphemes are allowed inside character classes. Use regular alternatives (e.g. `(?:abc|def|ghi|lm|[...])`) for strings instead. 39 | [3] Only single characters and graphemes are allowed inside character classes. Use regular alternatives (e.g. `(?:abc|def|ghi|lm|[...])`) for strings instead. 40 | [4] Only single characters and graphemes are allowed inside character classes. Use regular alternatives (e.g. `(?:abc|def|ghi|lm|[...])`) for strings instead. 41 | --- 42 | -------------------------------------------------------------------------------- /docs/rules/prefer-regexp-test.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-regexp-test" 5 | description: "enforce that `RegExp#test` is used instead of `String#match` and `RegExp#exec`" 6 | since: "v0.3.0" 7 | --- 8 | # regexp/prefer-regexp-test 9 | 10 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 11 | 12 | 13 | 14 | > enforce that `RegExp#test` is used instead of `String#match` and `RegExp#exec` 15 | 16 | ## :book: Rule Details 17 | 18 | This rule is aimed to use `RegExp#test` to check if a pattern matches a string. 19 | 20 | This rule inspired by [unicorn/prefer-regexp-test rule](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-regexp-test.md). 21 | 22 | 23 | 24 | ```js 25 | /* eslint regexp/prefer-regexp-test: "error" */ 26 | 27 | const text = 'something'; 28 | const pattern = /thing/; 29 | 30 | /* ✓ GOOD */ 31 | if (pattern.test(text)) {} 32 | 33 | /* ✗ BAD */ 34 | if (pattern.exec(text)) {} 35 | if (text.match(pattern)) {} 36 | ``` 37 | 38 | 39 | 40 | ## :wrench: Options 41 | 42 | Nothing. 43 | 44 | ## :books: Further reading 45 | 46 | - [unicorn/prefer-regexp-test](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-regexp-test.md) 47 | 48 | ## :rocket: Version 49 | 50 | This rule was introduced in eslint-plugin-regexp v0.3.0 51 | 52 | ## :mag: Implementation 53 | 54 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-regexp-test.ts) 55 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-regexp-test.ts) 56 | -------------------------------------------------------------------------------- /tests/lib/rules/strict.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/strict" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("strict", rule as any, { 12 | valid: [ 13 | `/regexp/`, 14 | String.raw`/\{\}\]/`, 15 | String.raw`/[-\w-]/`, 16 | String.raw`/[a-b-\w]/`, 17 | String.raw`/\0/`, 18 | String.raw`/()\1/`, 19 | String.raw`/(?)\k/`, 20 | String.raw`/\p{L}/u`, 21 | String.raw`/ \( \) \[ \] \{ \} \| \* \+ \? \^ \$ \\ \/ \./`, 22 | String.raw`/[\( \) \[ \] \{ \} \| \* \+ \? \^ \$ \\ \/ \. \-]/`, 23 | String.raw`/\u000f/`, 24 | String.raw`/\x000f/`, 25 | String.raw`/[A--B]/v`, 26 | ], 27 | invalid: [ 28 | // source characters 29 | String.raw`/]/`, 30 | String.raw`/{/`, 31 | String.raw`/}/`, 32 | 33 | // invalid or incomplete escape sequences 34 | String.raw`/\u{42}/`, 35 | String.raw`/\u000;/`, 36 | String.raw`/\x4/`, 37 | String.raw`/\c;/`, 38 | String.raw`/\p/`, 39 | String.raw`/\p{H}/`, 40 | String.raw`/\012/`, 41 | String.raw`/\12/`, 42 | 43 | // incomplete backreference 44 | String.raw`/\k/`, 46 | 47 | // useless escape 48 | String.raw`/\; \_ \a \- \'/`, 49 | String.raw`/[\; \_ \a \']/`, 50 | 51 | // invalid ranges 52 | String.raw`/[\w-a]/`, 53 | String.raw`/[a-\w]/`, 54 | 55 | // quantified assertions 56 | String.raw`/(?!a)+/`, 57 | ], 58 | }) 59 | -------------------------------------------------------------------------------- /docs/rules/prefer-unicode-codepoint-escapes.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-unicode-codepoint-escapes" 5 | description: "enforce use of unicode codepoint escapes" 6 | since: "v0.3.0" 7 | --- 8 | # regexp/prefer-unicode-codepoint-escapes 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > enforce use of unicode codepoint escapes 17 | 18 | ## :book: Rule Details 19 | 20 | This rule enforces the use of Unicode codepoint escapes instead of Unicode escapes using surrogate pairs. 21 | 22 | If you want to enforce characters that do not use surrogate pairs into unicode escapes or unicode code point escapes, use the [regexp/unicode-escape] rule. 23 | 24 | 25 | 26 | ```js 27 | /* eslint regexp/prefer-unicode-codepoint-escapes: "error" */ 28 | 29 | /* ✓ GOOD */ 30 | var foo = /\u{1f600}/u 31 | var foo = /😀/u 32 | 33 | /* ✗ BAD */ 34 | var foo = /\ud83d\ude00/u 35 | ``` 36 | 37 | 38 | 39 | ## :wrench: Options 40 | 41 | Nothing. 42 | 43 | ## :couple: Related rules 44 | 45 | - [regexp/unicode-escape] 46 | 47 | [regexp/unicode-escape]: ./unicode-escape.md 48 | 49 | ## :rocket: Version 50 | 51 | This rule was introduced in eslint-plugin-regexp v0.3.0 52 | 53 | ## :mag: Implementation 54 | 55 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-unicode-codepoint-escapes.ts) 56 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-unicode-codepoint-escapes.ts) 57 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/no-zero-quantifier.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: no-zero-quantifier >> invalid 5 | Code: 6 | 1 | /a{0}/ 7 | | ^~~~ [1] 8 | 9 | [1] Unexpected zero quantifier. The quantifier and its quantified element can be removed without affecting the pattern. 10 | Suggestions: 11 | - Remove this zero quantifier. 12 | Output: 13 | 1 | /(?:)/ 14 | --- 15 | 16 | 17 | Test: no-zero-quantifier >> invalid 18 | Code: 19 | 1 | /a{0}/v 20 | | ^~~~ [1] 21 | 22 | [1] Unexpected zero quantifier. The quantifier and its quantified element can be removed without affecting the pattern. 23 | Suggestions: 24 | - Remove this zero quantifier. 25 | Output: 26 | 1 | /(?:)/v 27 | --- 28 | 29 | 30 | Test: no-zero-quantifier >> invalid 31 | Code: 32 | 1 | /a{0,0}/ 33 | | ^~~~~~ [1] 34 | 35 | [1] Unexpected zero quantifier. The quantifier and its quantified element can be removed without affecting the pattern. 36 | Suggestions: 37 | - Remove this zero quantifier. 38 | Output: 39 | 1 | /(?:)/ 40 | --- 41 | 42 | 43 | Test: no-zero-quantifier >> invalid 44 | Code: 45 | 1 | /a{0,0}?b/ 46 | | ^~~~~~~ [1] 47 | 48 | [1] Unexpected zero quantifier. The quantifier and its quantified element can be removed without affecting the pattern. 49 | Suggestions: 50 | - Remove this zero quantifier. 51 | Output: 52 | 1 | /b/ 53 | --- 54 | 55 | 56 | Test: no-zero-quantifier >> invalid 57 | Code: 58 | 1 | /(a){0}/ 59 | | ^~~~~~ [1] 60 | 61 | [1] Unexpected zero quantifier. The quantifier and its quantified element do not affecting the pattern. Try to remove the elements but be careful because it contains at least one capturing group. 62 | --- 63 | -------------------------------------------------------------------------------- /tests/lib/rules/no-extra-lookaround-assertions.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-extra-lookaround-assertions" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-extra-lookaround-assertions", rule as any, { 12 | valid: [ 13 | `console.log('JavaScript'.replace(/Java(?=Script)/u, 'Type'))`, 14 | `console.log('JavaScript'.replace(/(?<=Java)Script/u, ''))`, 15 | String.raw`console.log('JavaScript'.replace(/(?<=[\q{Java}])Script/v, ''))`, 16 | ], 17 | invalid: [ 18 | `console.log('JavaScript'.replace(/Java(?=Scrip(?=t))/u, 'Type'))`, 19 | `console.log('JavaScript'.replace(/(?<=(?<=J)ava)Script/u, ''))`, 20 | // Within negate 21 | `console.log('JavaScript Java JavaRuntime'.replace(/Java(?!Scrip(?=t))/gu, 'Python'))`, 22 | `console.log('JavaScript TypeScript ActionScript'.replace(/(? 13 | 14 | > enforce using quantifier 15 | 16 | ## :book: Rule Details 17 | 18 | This rule is aimed to use quantifiers instead of consecutive characters in regular expressions. 19 | 20 | 21 | 22 | ```js 23 | /* eslint regexp/prefer-quantifier: "error" */ 24 | 25 | /* ✓ GOOD */ 26 | var foo = /\d{4}-\d{2}-\d{2}/; 27 | 28 | /* ✗ BAD */ 29 | var foo = /\d\d\d\d-\d\d-\d\d/; 30 | ``` 31 | 32 | 33 | 34 | ## :wrench: Options 35 | 36 | ```json 37 | { 38 | "regexp/prefer-quantifier": ["error", { 39 | "allows": ["www", "\\d\\d"] 40 | }] 41 | } 42 | ``` 43 | 44 | - `"allows"` ... Array of allowed patterns. 45 | 46 | ### `{ "allows": ["www", "\\d\\d"] }` 47 | 48 | 49 | 50 | ```js 51 | /* eslint regexp/prefer-quantifier: ["error", { "allows": ["www", "\\d\\d"] }] */ 52 | 53 | /* ✓ GOOD */ 54 | var foo = /(?:www\.)?(.*)/; 55 | var foo = /\d\d:\d\d/; 56 | 57 | /* ✗ BAD */ 58 | var foo = /wwww/; 59 | var foo = /\d\d\d\d/; 60 | ``` 61 | 62 | 63 | 64 | ## :rocket: Version 65 | 66 | This rule was introduced in eslint-plugin-regexp v0.2.0 67 | 68 | ## :mag: Implementation 69 | 70 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-quantifier.ts) 71 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-quantifier.ts) 72 | -------------------------------------------------------------------------------- /lib/rules/prefer-named-backreference.ts: -------------------------------------------------------------------------------- 1 | import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" 2 | import type { RegExpContext } from "../utils" 3 | import { createRule, defineRegexpVisitor } from "../utils" 4 | 5 | export default createRule("prefer-named-backreference", { 6 | meta: { 7 | docs: { 8 | description: "enforce using named backreferences", 9 | category: "Stylistic Issues", 10 | recommended: false, 11 | }, 12 | fixable: "code", 13 | schema: [], 14 | messages: { 15 | unexpected: "Unexpected unnamed backreference.", 16 | }, 17 | type: "suggestion", // "problem", 18 | }, 19 | create(context) { 20 | function createVisitor({ 21 | node, 22 | fixReplaceNode, 23 | getRegexpLocation, 24 | }: RegExpContext): RegExpVisitor.Handlers { 25 | return { 26 | onBackreferenceEnter(bNode) { 27 | if ( 28 | !bNode.ambiguous && 29 | bNode.resolved.name && 30 | !bNode.raw.startsWith("\\k<") 31 | ) { 32 | context.report({ 33 | node, 34 | loc: getRegexpLocation(bNode), 35 | messageId: "unexpected", 36 | fix: fixReplaceNode( 37 | bNode, 38 | `\\k<${bNode.resolved.name}>`, 39 | ), 40 | }) 41 | } 42 | }, 43 | } 44 | } 45 | 46 | return defineRegexpVisitor(context, { 47 | createVisitor, 48 | }) 49 | }, 50 | }) 51 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/prefer-escape-replacement-dollar-char.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: prefer-escape-replacement-dollar-char >> invalid 5 | Code: 6 | 1 | '€1,234'.replace(/€/, '$'); // "$1,234" 7 | | ^ [1] 8 | 9 | [1] Unexpected replacement `$` character without escaping. Use `$$` instead. 10 | --- 11 | 12 | 13 | Test: prefer-escape-replacement-dollar-char >> invalid 14 | Code: 15 | 1 | '€1,234'.replace(/€/v, '$'); // "$1,234" 16 | | ^ [1] 17 | 18 | [1] Unexpected replacement `$` character without escaping. Use `$$` instead. 19 | --- 20 | 21 | 22 | Test: prefer-escape-replacement-dollar-char >> invalid 23 | Code: 24 | 1 | '€1,234'.replaceAll(/€/, '$'); // "$1,234" 25 | | ^ [1] 26 | 27 | [1] Unexpected replacement `$` character without escaping. Use `$$` instead. 28 | --- 29 | 30 | 31 | Test: prefer-escape-replacement-dollar-char >> invalid 32 | Code: 33 | 1 | 'abc'.replace(/./, '$ $$ $'); 34 | | ^ ^ 35 | | [1] [2] 36 | 37 | [1] Unexpected replacement `$` character without escaping. Use `$$` instead. 38 | [2] Unexpected replacement `$` character without escaping. Use `$$` instead. 39 | --- 40 | 41 | 42 | Test: prefer-escape-replacement-dollar-char >> invalid 43 | Code: 44 | 1 | 'abc'.replace(/(?.)/, '$> invalid 52 | Code: 53 | 1 | '€1,234'.replace(/[\q{€}]/v, '$'); // "$1,234" 54 | | ^ [1] 55 | 56 | [1] Unexpected replacement `$` character without escaping. Use `$$` instead. 57 | --- 58 | -------------------------------------------------------------------------------- /docs/rules/no-useless-string-literal.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-useless-string-literal" 5 | description: "disallow string disjunction of single characters in `\\q{...}`" 6 | since: "v2.0.0-next.12" 7 | --- 8 | # regexp/no-useless-string-literal 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow string disjunction of single characters in `\q{...}` 17 | 18 | ## :book: Rule Details 19 | 20 | This rule reports the string alternatives of a single character in `\q{...}`. 21 | It can be placed outside `\q{...}`. 22 | 23 | 24 | 25 | ```js 26 | /* eslint regexp/no-useless-string-literal: "error" */ 27 | 28 | /* ✓ GOOD */ 29 | var foo = /[\q{abc}]/v 30 | var foo = /[\q{ab|}]/v 31 | 32 | /* ✗ BAD */ 33 | var foo = /[\q{a}]/v // => /[a]/v 34 | var foo = /[\q{a|bc}]/v // => /[a\q{bc}]/v 35 | ``` 36 | 37 | 38 | 39 | ## :wrench: Options 40 | 41 | Nothing. 42 | 43 | ## :couple: Related rules 44 | 45 | - [regexp/no-empty-alternative] 46 | - [regexp/no-empty-string-literal] 47 | 48 | [regexp/no-empty-alternative]: ./no-empty-alternative.md 49 | [regexp/no-empty-string-literal]: ./no-empty-string-literal.md 50 | 51 | ## :rocket: Version 52 | 53 | This rule was introduced in eslint-plugin-regexp v2.0.0-next.12 54 | 55 | ## :mag: Implementation 56 | 57 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-useless-string-literal.ts) 58 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-useless-string-literal.ts) 59 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/sort-flags.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: sort-flags >> invalid 5 | Code: 6 | 1 | /\w/yusimg 7 | | ^~~~~~ [1] 8 | 9 | Output: 10 | 1 | /\w/gimsuy 11 | 12 | [1] The flags 'yusimg' should be in the order 'gimsuy'. 13 | --- 14 | 15 | 16 | Test: sort-flags >> invalid 17 | Code: 18 | 1 | /\w/yvsimg 19 | | ^~~~~~ [1] 20 | 21 | Output: 22 | 1 | /\w/gimsvy 23 | 24 | [1] The flags 'yvsimg' should be in the order 'gimsvy'. 25 | --- 26 | 27 | 28 | Test: sort-flags >> invalid 29 | Code: 30 | 1 | new RegExp("\\w", "yusimg") 31 | | ^~~~~~ [1] 32 | 33 | Output: 34 | 1 | new RegExp("\\w", "gimsuy") 35 | 36 | [1] The flags 'yusimg' should be in the order 'gimsuy'. 37 | --- 38 | 39 | 40 | Test: sort-flags >> invalid 41 | Code: 42 | 1 | new RegExp("\\w", "yusimgd") 43 | | ^~~~~~~ [1] 44 | 45 | Output: 46 | 1 | new RegExp("\\w", "dgimsuy") 47 | 48 | [1] The flags 'yusimgd' should be in the order 'dgimsuy'. 49 | --- 50 | 51 | 52 | Test: sort-flags >> invalid 53 | Code: 54 | 1 | new RegExp("\\w)", "ui") 55 | | ^~ [1] 56 | 57 | Output: 58 | 1 | new RegExp("\\w)", "iu") 59 | 60 | [1] The flags 'ui' should be in the order 'iu'. 61 | --- 62 | 63 | 64 | Test: sort-flags >> invalid 65 | Code: 66 | 1 | RegExp('a' + b, 'us'); 67 | | ^~ [1] 68 | 69 | Output: 70 | 1 | RegExp('a' + b, 'su'); 71 | 72 | [1] The flags 'us' should be in the order 'su'. 73 | --- 74 | 75 | 76 | Test: sort-flags >> invalid 77 | Code: 78 | 1 | var a = "foo"; RegExp(foo, 'us'); RegExp(foo, 'u'); 79 | | ^~ [1] 80 | 81 | Output: 82 | 1 | var a = "foo"; RegExp(foo, 'su'); RegExp(foo, 'u'); 83 | 84 | [1] The flags 'us' should be in the order 'su'. 85 | --- 86 | -------------------------------------------------------------------------------- /lib/rules/no-escape-backspace.ts: -------------------------------------------------------------------------------- 1 | import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" 2 | import type { RegExpContext } from "../utils" 3 | import { CP_BACKSPACE, createRule, defineRegexpVisitor } from "../utils" 4 | 5 | export default createRule("no-escape-backspace", { 6 | meta: { 7 | docs: { 8 | description: "disallow escape backspace (`[\\b]`)", 9 | category: "Possible Errors", 10 | recommended: true, 11 | }, 12 | schema: [], 13 | hasSuggestions: true, 14 | messages: { 15 | unexpected: "Unexpected '[\\b]'. Use '\\u0008' instead.", 16 | suggest: "Use '\\u0008'.", 17 | }, 18 | type: "suggestion", // "problem", 19 | }, 20 | create(context) { 21 | function createVisitor({ 22 | node, 23 | getRegexpLocation, 24 | fixReplaceNode, 25 | }: RegExpContext): RegExpVisitor.Handlers { 26 | return { 27 | onCharacterEnter(cNode) { 28 | if (cNode.value === CP_BACKSPACE && cNode.raw === "\\b") { 29 | context.report({ 30 | node, 31 | loc: getRegexpLocation(cNode), 32 | messageId: "unexpected", 33 | suggest: [ 34 | { 35 | messageId: "suggest", 36 | fix: fixReplaceNode(cNode, "\\u0008"), 37 | }, 38 | ], 39 | }) 40 | } 41 | }, 42 | } 43 | } 44 | 45 | return defineRegexpVisitor(context, { 46 | createVisitor, 47 | }) 48 | }, 49 | }) 50 | -------------------------------------------------------------------------------- /docs/rules/no-empty-string-literal.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-empty-string-literal" 5 | description: "disallow empty string literals in character classes" 6 | since: "v2.0.0-next.11" 7 | --- 8 | # regexp/no-empty-string-literal 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 13 | 14 | > disallow empty string literals in character classes 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports empty string literals in character classes. 19 | 20 | If the empty string literal is supposed to match the empty string, then use a 21 | quantifier instead. For example, `[ab\q{}]` should be written as `[ab]?`. 22 | 23 | This rule does not report empty alternatives in string literals. (e.g. `/[\q{a|}]/v`)\ 24 | If you want to report empty alternatives in string literals, use the [regexp/no-empty-alternative] rule. 25 | 26 | 27 | 28 | ```js 29 | /* eslint regexp/no-empty-string-literal: "error" */ 30 | 31 | /* ✓ GOOD */ 32 | var foo = /[\q{a}]/v; 33 | var foo = /[\q{abc}]/v; 34 | var foo = /[\q{a|}]/v; 35 | 36 | /* ✗ BAD */ 37 | var foo = /[\q{}]/v; 38 | var foo = /[\q{|}]/v; 39 | ``` 40 | 41 | 42 | 43 | ## :wrench: Options 44 | 45 | Nothing. 46 | 47 | ## :couple: Related rules 48 | 49 | - [regexp/no-empty-alternative] 50 | 51 | [regexp/no-empty-alternative]: ./no-empty-alternative.md 52 | 53 | ## :rocket: Version 54 | 55 | This rule was introduced in eslint-plugin-regexp v2.0.0-next.11 56 | 57 | ## :mag: Implementation 58 | 59 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-empty-string-literal.ts) 60 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-empty-string-literal.ts) 61 | -------------------------------------------------------------------------------- /docs/rules/no-empty-character-class.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-empty-character-class" 5 | description: "disallow character classes that match no characters" 6 | since: "v1.2.0" 7 | --- 8 | # regexp/no-empty-character-class 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 13 | 14 | > disallow character classes that match no characters 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports character classes that cannot match any characters. 19 | 20 | Character classes that cannot match any characters are either empty or negated character classes of elements that contain all characters. 21 | 22 | The reports for this rule include reports for the ESLint core [no-empty-character-class] rule. That is, if you use this rule, you can turn off the ESLint core [no-empty-character-class] rule. 23 | 24 | 25 | 26 | ```js 27 | /* eslint regexp/no-empty-character-class: "error" */ 28 | 29 | /* ✓ GOOD */ 30 | var foo = /abc[d]/; 31 | var foo = /abc[a-z]/; 32 | var foo = /[^]/; 33 | var foo = /[\s\S]/; 34 | 35 | /* ✗ BAD */ 36 | var foo = /abc[]/; 37 | var foo = /[^\s\S]/; 38 | ``` 39 | 40 | 41 | 42 | ## :wrench: Options 43 | 44 | Nothing. 45 | 46 | ## :books: Further reading 47 | 48 | - [no-empty-character-class] 49 | 50 | [no-empty-character-class]: https://eslint.org/docs/rules/no-empty-character-class 51 | 52 | ## :rocket: Version 53 | 54 | This rule was introduced in eslint-plugin-regexp v1.2.0 55 | 56 | ## :mag: Implementation 57 | 58 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-empty-character-class.ts) 59 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-empty-character-class.ts) 60 | -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from "eslint" 2 | import type { JSONSchema4 } from "json-schema" 3 | 4 | export type RuleListener = Rule.RuleListener 5 | 6 | export interface RuleModule { 7 | meta: RuleMetaData 8 | create(context: Rule.RuleContext): RuleListener 9 | } 10 | 11 | export type RuleCategory = 12 | | "Possible Errors" 13 | | "Best Practices" 14 | | "Stylistic Issues" 15 | 16 | export type SeverityString = "error" | "warn" | "off" 17 | 18 | export interface RuleMetaData { 19 | docs: { 20 | description: string 21 | category: RuleCategory 22 | recommended: boolean 23 | url: string 24 | ruleId: string 25 | ruleName: string 26 | default?: Exclude 27 | } 28 | messages: { [messageId: string]: string } 29 | fixable?: "code" | "whitespace" 30 | schema: JSONSchema4 | JSONSchema4[] 31 | deprecated?: boolean 32 | replacedBy?: string[] 33 | type: "problem" | "suggestion" | "layout" 34 | hasSuggestions?: boolean 35 | } 36 | 37 | export interface PartialRuleModule { 38 | meta: PartialRuleMetaData 39 | create: (context: Rule.RuleContext) => RuleListener 40 | } 41 | 42 | export interface PartialRuleMetaData { 43 | docs: { 44 | description: string 45 | category: RuleCategory 46 | recommended: boolean 47 | default?: Exclude 48 | } 49 | messages: { [messageId: string]: string } 50 | fixable?: "code" | "whitespace" 51 | schema: JSONSchema4 | JSONSchema4[] 52 | deprecated?: boolean 53 | replacedBy?: string[] 54 | type: "problem" | "suggestion" | "layout" 55 | hasSuggestions?: boolean 56 | } 57 | 58 | export type RegexpSettings = { 59 | allowedCharacterRanges?: string | readonly string[] 60 | } 61 | export type ObjectOption = Record 62 | -------------------------------------------------------------------------------- /lib/rules/prefer-regexp-exec.ts: -------------------------------------------------------------------------------- 1 | import type { CallExpression } from "estree" 2 | import { createRule } from "../utils" 3 | import { isKnownMethodCall, getStaticValue } from "../utils/ast-utils" 4 | import { createTypeTracker } from "../utils/type-tracker" 5 | 6 | // Inspired by https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md 7 | export default createRule("prefer-regexp-exec", { 8 | meta: { 9 | docs: { 10 | description: 11 | "enforce that `RegExp#exec` is used instead of `String#match` if no global flag is provided", 12 | category: "Best Practices", 13 | recommended: false, 14 | }, 15 | schema: [], 16 | messages: { 17 | disallow: "Use the `RegExp#exec()` method instead.", 18 | }, 19 | type: "suggestion", // "problem", 20 | }, 21 | create(context) { 22 | const typeTracer = createTypeTracker(context) 23 | 24 | return { 25 | CallExpression(node: CallExpression) { 26 | if (!isKnownMethodCall(node, { match: 1 })) { 27 | return 28 | } 29 | const arg = node.arguments[0] 30 | const evaluated = getStaticValue(context, arg) 31 | if ( 32 | evaluated && 33 | evaluated.value instanceof RegExp && 34 | evaluated.value.flags.includes("g") 35 | ) { 36 | return 37 | } 38 | if (!typeTracer.isString(node.callee.object)) { 39 | return 40 | } 41 | context.report({ 42 | node, 43 | messageId: "disallow", 44 | }) 45 | }, 46 | } 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /docs/rules/sort-alternatives.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/sort-alternatives" 5 | description: "sort alternatives if order doesn't matter" 6 | since: "v0.12.0" 7 | --- 8 | # regexp/sort-alternatives 9 | 10 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 11 | 12 | 13 | 14 | > sort alternatives if order doesn't matter 15 | 16 | ## :book: Rule Details 17 | 18 | This rule will sort alternatives to improve readability and maintainability. 19 | 20 | The primary target of this rule are lists of words and/or numbers. These lists are somewhat common, and sorting them makes it easy for readers to check whether a particular word or number is included. 21 | 22 | This rule will only sort alternatives if reordering the alternatives doesn't affect the pattern.\ 23 | However, character classes containing strings are ensured to match the longest string, so they can always be sorted. 24 | 25 | 26 | 27 | ```js 28 | /* eslint regexp/sort-alternatives: "error" */ 29 | 30 | /* ✓ GOOD */ 31 | var foo = /\b(1|2|3)\b/; 32 | var foo = /\b(alpha|beta|gamma)\b/; 33 | var foo = /[\q{blue|green|red}]/v; 34 | 35 | /* ✗ BAD */ 36 | var foo = /\b(2|1|3)\b/; 37 | var foo = /__(?:Foo|Bar)__/; 38 | var foo = /\((?:TM|R|C)\)/; 39 | var foo = /[\q{red|green|blue}]/v; 40 | ``` 41 | 42 | 43 | 44 | ## :wrench: Options 45 | 46 | Nothing. 47 | 48 | ## :rocket: Version 49 | 50 | This rule was introduced in eslint-plugin-regexp v0.12.0 51 | 52 | ## :mag: Implementation 53 | 54 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/sort-alternatives.ts) 55 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/sort-alternatives.ts) 56 | -------------------------------------------------------------------------------- /docs/rules/prefer-named-capture-group.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/prefer-named-capture-group" 5 | description: "enforce using named capture groups" 6 | since: "v1.2.0" 7 | --- 8 | # regexp/prefer-named-capture-group 9 | 10 | 11 | 12 | > enforce using named capture groups 13 | 14 | ## :book: Rule Details 15 | 16 | This rule reports capturing groups without a name. 17 | 18 | This rule is inspired by the [prefer-named-capture-group] rule. The positions of reports are improved over the core rule and arguments of `new RegExp()` are also checked. 19 | 20 | 21 | 22 | ```js 23 | /* eslint regexp/prefer-named-capture-group: "error" */ 24 | 25 | /* ✓ GOOD */ 26 | var foo = /(?ba+r)/; 27 | var foo = /\b(?:foo)+\b/; 28 | 29 | /* ✗ BAD */ 30 | var foo = /\b(foo)+\b/; 31 | ``` 32 | 33 | 34 | 35 | ## :wrench: Options 36 | 37 | Nothing. 38 | 39 | ## :couple: Related rules 40 | 41 | - [regexp/prefer-named-backreference] 42 | - [regexp/prefer-named-replacement] 43 | - [regexp/prefer-result-array-groups] 44 | 45 | [regexp/prefer-named-backreference]: ./prefer-named-backreference.md 46 | [regexp/prefer-named-replacement]: ./prefer-named-replacement.md 47 | [regexp/prefer-result-array-groups]: ./prefer-result-array-groups.md 48 | 49 | ## :books: Further reading 50 | 51 | - [prefer-named-capture-group] 52 | 53 | [prefer-named-capture-group]: https://eslint.org/docs/rules/prefer-named-capture-group 54 | 55 | ## :rocket: Version 56 | 57 | This rule was introduced in eslint-plugin-regexp v1.2.0 58 | 59 | ## :mag: Implementation 60 | 61 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/prefer-named-capture-group.ts) 62 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/prefer-named-capture-group.ts) 63 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/prefer-plus-quantifier.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: prefer-plus-quantifier >> invalid 5 | Code: 6 | 1 | /a{1,}/ 7 | | ^~~~ [1] 8 | 9 | Output: 10 | 1 | /a+/ 11 | 12 | [1] Unexpected quantifier '{1,}'. Use '+' instead. 13 | --- 14 | 15 | 16 | Test: prefer-plus-quantifier >> invalid 17 | Code: 18 | 1 | /a{1,}?/ 19 | | ^~~~ [1] 20 | 21 | Output: 22 | 1 | /a+?/ 23 | 24 | [1] Unexpected quantifier '{1,}'. Use '+' instead. 25 | --- 26 | 27 | 28 | Test: prefer-plus-quantifier >> invalid 29 | Code: 30 | 1 | /(a){1,}/ 31 | | ^~~~ [1] 32 | 33 | Output: 34 | 1 | /(a)+/ 35 | 36 | [1] Unexpected quantifier '{1,}'. Use '+' instead. 37 | --- 38 | 39 | 40 | Test: prefer-plus-quantifier >> invalid 41 | Code: 42 | 1 | /(a){1,}/v 43 | | ^~~~ [1] 44 | 45 | Output: 46 | 1 | /(a)+/v 47 | 48 | [1] Unexpected quantifier '{1,}'. Use '+' instead. 49 | --- 50 | 51 | 52 | Test: prefer-plus-quantifier >> invalid 53 | Code: 54 | 1 | /(a){1,}?/ 55 | | ^~~~ [1] 56 | 57 | Output: 58 | 1 | /(a)+?/ 59 | 60 | [1] Unexpected quantifier '{1,}'. Use '+' instead. 61 | --- 62 | 63 | 64 | Test: prefer-plus-quantifier >> invalid 65 | Code: 66 | 1 | 67 | 2 | const s = "a{1,}" 68 | | ^~~~ [1] 69 | 3 | new RegExp(s) 70 | 4 | 71 | 72 | Output: 73 | 1 | 74 | 2 | const s = "a+" 75 | 3 | new RegExp(s) 76 | 4 | 77 | 78 | [1] Unexpected quantifier '{1,}'. Use '+' instead. 79 | --- 80 | 81 | 82 | Test: prefer-plus-quantifier >> invalid 83 | Code: 84 | 1 | 85 | 2 | const s = "a{1"+",}" 86 | | ^~~~~~~~~~ [1] 87 | 3 | new RegExp(s) 88 | 4 | 89 | 90 | Output: unchanged 91 | 92 | [1] Unexpected quantifier '{1,}'. Use '+' instead. 93 | --- 94 | -------------------------------------------------------------------------------- /tests/lib/rules/__snapshots__/prefer-star-quantifier.ts.eslintsnap: -------------------------------------------------------------------------------- 1 | # eslint-snapshot-rule-tester format: v1 2 | 3 | 4 | Test: prefer-star-quantifier >> invalid 5 | Code: 6 | 1 | /a{0,}/ 7 | | ^~~~ [1] 8 | 9 | Output: 10 | 1 | /a*/ 11 | 12 | [1] Unexpected quantifier '{0,}'. Use '*' instead. 13 | --- 14 | 15 | 16 | Test: prefer-star-quantifier >> invalid 17 | Code: 18 | 1 | /a{0,}?/ 19 | | ^~~~ [1] 20 | 21 | Output: 22 | 1 | /a*?/ 23 | 24 | [1] Unexpected quantifier '{0,}'. Use '*' instead. 25 | --- 26 | 27 | 28 | Test: prefer-star-quantifier >> invalid 29 | Code: 30 | 1 | /(a){0,}/ 31 | | ^~~~ [1] 32 | 33 | Output: 34 | 1 | /(a)*/ 35 | 36 | [1] Unexpected quantifier '{0,}'. Use '*' instead. 37 | --- 38 | 39 | 40 | Test: prefer-star-quantifier >> invalid 41 | Code: 42 | 1 | /(a){0,}/v 43 | | ^~~~ [1] 44 | 45 | Output: 46 | 1 | /(a)*/v 47 | 48 | [1] Unexpected quantifier '{0,}'. Use '*' instead. 49 | --- 50 | 51 | 52 | Test: prefer-star-quantifier >> invalid 53 | Code: 54 | 1 | /(a){0,}?/ 55 | | ^~~~ [1] 56 | 57 | Output: 58 | 1 | /(a)*?/ 59 | 60 | [1] Unexpected quantifier '{0,}'. Use '*' instead. 61 | --- 62 | 63 | 64 | Test: prefer-star-quantifier >> invalid 65 | Code: 66 | 1 | 67 | 2 | const s = "a{0,}" 68 | | ^~~~ [1] 69 | 3 | new RegExp(s) 70 | 4 | 71 | 72 | Output: 73 | 1 | 74 | 2 | const s = "a*" 75 | 3 | new RegExp(s) 76 | 4 | 77 | 78 | [1] Unexpected quantifier '{0,}'. Use '*' instead. 79 | --- 80 | 81 | 82 | Test: prefer-star-quantifier >> invalid 83 | Code: 84 | 1 | 85 | 2 | const s = "a{0"+",}" 86 | | ^~~~~~~~~~ [1] 87 | 3 | new RegExp(s) 88 | 4 | 89 | 90 | Output: unchanged 91 | 92 | [1] Unexpected quantifier '{0,}'. Use '*' instead. 93 | --- 94 | -------------------------------------------------------------------------------- /docs/rules/no-standalone-backslash.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-standalone-backslash" 5 | description: "disallow standalone backslashes (`\\`)" 6 | since: "v0.10.0" 7 | --- 8 | # regexp/no-standalone-backslash 9 | 10 | 11 | 12 | > disallow standalone backslashes (`\`) 13 | 14 | ## :book: Rule Details 15 | 16 | This rule disallows backslash (`\`) without escape. 17 | 18 | E.g. the regular expression `/\c/` without the unicode (`u`) flag is the same pattern as `/\\c/`. 19 | 20 | In most cases, standalone backslashes are used by accident when a control escape sequence (`\cX`) or another escape sequence was intended. They are very confusing and should not be used intentionally. 21 | 22 | This behavior is described in [Annex B] of the ECMAScript specification. 23 | 24 | [Annex B]: https://tc39.es/ecma262/#sec-regular-expressions-patterns 25 | 26 | 27 | 28 | ```js 29 | /* eslint regexp/no-standalone-backslash: "error" */ 30 | 31 | /* ✓ GOOD */ 32 | var foo = /\cX/; 33 | 34 | /* ✗ BAD */ 35 | var foo = /\c/; 36 | var foo = /\c-/; 37 | var foo = /[\c]/; 38 | ``` 39 | 40 | 41 | 42 | ## :wrench: Options 43 | 44 | Nothing. 45 | 46 | ## :couple: Related rules 47 | 48 | - [regexp/no-useless-escape](./no-useless-escape.md) 49 | 50 | ## :books: Further reading 51 | 52 | - [ECMAScript® 2022 Language Specification > Annex B > B.1.4 Regular Expressions Patterns](https://tc39.es/ecma262/#sec-regular-expressions-patterns) 53 | 54 | ## :rocket: Version 55 | 56 | This rule was introduced in eslint-plugin-regexp v0.10.0 57 | 58 | ## :mag: Implementation 59 | 60 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-standalone-backslash.ts) 61 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-standalone-backslash.ts) 62 | -------------------------------------------------------------------------------- /tests/lib/rules/negation.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/negation" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("negation", rule as any, { 12 | valid: [ 13 | String.raw`/[\d]/`, 14 | String.raw`/[^\d\s]/`, 15 | String.raw`/[^\p{ASCII}]/iu`, 16 | String.raw`/[^\P{Ll}]/iu`, 17 | String.raw`/[\p{Basic_Emoji}]/v`, 18 | String.raw`/[^\P{Lowercase_Letter}]/iu`, 19 | String.raw`/[^[^a][^b]]/v`, 20 | ], 21 | invalid: [ 22 | String.raw`/[^\d]/`, 23 | String.raw`/[^\D]/`, 24 | String.raw`/[^\w]/`, 25 | String.raw`/[^\W]/`, 26 | String.raw`/[^\s]/`, 27 | String.raw`/[^\S]/`, 28 | String.raw`/[^\p{ASCII}]/u`, 29 | String.raw`/[^\P{ASCII}]/u`, 30 | String.raw`/[^\p{Script=Hiragana}]/u`, 31 | String.raw`/[^\P{Script=Hiragana}]/u`, 32 | String.raw`/[^\P{Ll}]/u;`, 33 | String.raw`/[^\P{White_Space}]/iu;`, 34 | String.raw`const s ="[^\\w]" 35 | new RegExp(s)`, 36 | String.raw`const s ="[^\\w]" 37 | new RegExp(s) 38 | new RegExp(s)`, 39 | String.raw`const s ="[^\\w]" 40 | new RegExp(s, "i") 41 | new RegExp(s)`, 42 | String.raw`const s ="[^\\w]" 43 | Number(s) 44 | new RegExp(s)`, 45 | String.raw`/[^\P{Lowercase_Letter}]/iv`, 46 | String.raw`/[^[^abc]]/v`, 47 | String.raw`/[^[^\q{a|1|A}&&\w]]/v`, 48 | String.raw`/[^[^a]]/iv`, 49 | String.raw`/[^[^\P{Lowercase_Letter}]]/iv`, 50 | String.raw`/[^[^[\p{Lowercase_Letter}&&[ABC]]]]/iv`, 51 | String.raw`/[^[^[\p{Lowercase_Letter}&&A]--B]]/iv`, 52 | ], 53 | }) 54 | -------------------------------------------------------------------------------- /docs/rules/no-dupe-characters-character-class.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/no-dupe-characters-character-class" 5 | description: "disallow duplicate characters in the RegExp character class" 6 | since: "v0.1.0" 7 | --- 8 | # regexp/no-dupe-characters-character-class 9 | 10 | 💼 This rule is enabled in the following configs: 🟢 `flat/recommended`, 🔵 `recommended`. 11 | 12 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 13 | 14 | 15 | 16 | > disallow duplicate characters in the RegExp character class 17 | 18 | Because multiple same character classes in regular expressions only one is useful, they might be typing mistakes. 19 | 20 | ```js 21 | var foo = /\\(\\)/; 22 | ``` 23 | 24 | ## :book: Rule Details 25 | 26 | This rule disallows duplicate characters in the RegExp character class. 27 | 28 | 29 | 30 | ```js 31 | /* eslint regexp/no-dupe-characters-character-class: "error" */ 32 | 33 | /* ✓ GOOD */ 34 | var foo = /[\(\)]/; 35 | 36 | var foo = /[a-z\s]/; 37 | 38 | var foo = /[\w]/; 39 | 40 | /* ✗ BAD */ 41 | var foo = /[\\(\\)]/; 42 | // ^^ ^^ "\\" are duplicated 43 | var foo = /[a-z\\s]/; 44 | // ^^^ ^ "s" are duplicated 45 | var foo = /[\w0-9]/; 46 | // ^^^^^ "0-9" are duplicated 47 | ``` 48 | 49 | 50 | 51 | ## :wrench: Options 52 | 53 | Nothing. 54 | 55 | ## :rocket: Version 56 | 57 | This rule was introduced in eslint-plugin-regexp v0.1.0 58 | 59 | ## :mag: Implementation 60 | 61 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-dupe-characters-character-class.ts) 62 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-dupe-characters-character-class.ts) 63 | -------------------------------------------------------------------------------- /tests/lib/rules/simplify-set-operations.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/simplify-set-operations" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("simplify-set-operations", rule as any, { 12 | valid: [ 13 | String.raw`/[[abc]]/v`, 14 | String.raw`/[\d]/u`, 15 | String.raw`/[^\d]/v`, 16 | String.raw`/[a--b]/v`, 17 | String.raw`/[a&&b]/v`, 18 | String.raw`/[^ab]/v`, 19 | String.raw`/[^a&&b]/v;`, 20 | String.raw`/[\s\p{ASCII}]/u`, 21 | String.raw`/[^\S\P{ASCII}]/u`, 22 | String.raw`/[^[]]/v`, 23 | String.raw`/[a&&b&&[c]]/v`, 24 | String.raw`/[a--b--[c]]/v`, 25 | ], 26 | invalid: [ 27 | String.raw`/[a&&[^b]]/v`, 28 | String.raw`/[a&&b&&[^c]]/v`, 29 | String.raw`/[a&&[^b]&&c]/v`, 30 | String.raw`/[a&&b&&[^c]&&d]/v`, 31 | String.raw`/[[^a]&&b&&c]/v`, 32 | String.raw`/[[^b]&&a]/v`, 33 | String.raw`/[[abc]&&[^def]]/v`, 34 | String.raw`/[a--[^b]]/v`, 35 | String.raw`/[a--[^b]--c]/v`, 36 | String.raw`/[a--b--[^c]]/v`, 37 | String.raw`/[[abc]--[^def]]/v`, 38 | String.raw`/[[^a]&&[^b]]/v`, 39 | String.raw`/[^[^a]&&[^b]]/v`, 40 | String.raw`/[[^a]&&[^b]&&\D]/v`, 41 | String.raw`/[^[^a]&&[^b]&&\D]/v`, 42 | String.raw`/[[^a]&&\D&&b]/v`, 43 | String.raw`/[[^abc]&&[^def]&&\D]/v`, 44 | String.raw`/[[^a]&&[b]&&[^c]]/v`, 45 | String.raw`/[[^a][^b]]/v`, 46 | String.raw`/[[^abc][^def]]/v`, 47 | String.raw`/[^[^a][^b]]/v`, 48 | String.raw`/[^\S\P{ASCII}]/v`, 49 | String.raw`/[a&&[^b]&&[^c]&&d]/v`, 50 | String.raw`/[[^bc]&&a&&d]/v`, 51 | ], 52 | }) 53 | -------------------------------------------------------------------------------- /docs/rules/require-unicode-regexp.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/require-unicode-regexp" 5 | description: "enforce the use of the `u` flag" 6 | since: "v1.2.0" 7 | --- 8 | # regexp/require-unicode-regexp 9 | 10 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 11 | 12 | 13 | 14 | > enforce the use of the `u` flag 15 | 16 | ## :book: Rule Details 17 | 18 | This rule reports regular expressions without the `u` flag. 19 | 20 | It will automatically add the `u` flag to regular expressions where it is statically guaranteed to be safe to do so. In all other cases, the developer has to check that adding the `u` flag doesn't cause the regex to behave incorrectly. 21 | 22 | This rule is inspired by the [require-unicode-regexp] rule. The position of the report is improved over the core rule and arguments of `new RegExp()` are also checked. 23 | 24 | 25 | 26 | ```js 27 | /* eslint regexp/require-unicode-regexp: "error" */ 28 | 29 | /* ✓ GOOD */ 30 | var foo = /foo/u; 31 | var foo = /a\s+b/u; 32 | 33 | /* ✗ BAD */ 34 | var foo = /foo/; 35 | var foo = RegExp("a\\s+b"); 36 | var foo = /[a-z]/i; 37 | var foo = /\S/; 38 | ``` 39 | 40 | 41 | 42 | ## :wrench: Options 43 | 44 | Nothing. 45 | 46 | ## :books: Further reading 47 | 48 | - [require-unicode-regexp] 49 | 50 | [require-unicode-regexp]: https://eslint.org/docs/rules/require-unicode-regexp 51 | 52 | ## :rocket: Version 53 | 54 | This rule was introduced in eslint-plugin-regexp v1.2.0 55 | 56 | ## :mag: Implementation 57 | 58 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/require-unicode-regexp.ts) 59 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/require-unicode-regexp.ts) 60 | -------------------------------------------------------------------------------- /docs/rules/sort-character-class-elements.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: "rule-details" 3 | sidebarDepth: 0 4 | title: "regexp/sort-character-class-elements" 5 | description: "enforces elements order in character class" 6 | since: "v0.12.0" 7 | --- 8 | # regexp/sort-character-class-elements 9 | 10 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 11 | 12 | 13 | 14 | > enforces elements order in character class 15 | 16 | ## :book: Rule Details 17 | 18 | This rule checks elements of character classes are sorted. 19 | 20 | 21 | 22 | ```js 23 | /* eslint regexp/sort-character-class-elements: "error" */ 24 | 25 | /* ✓ GOOD */ 26 | var foo = /[abcdef]/ 27 | var foo = /[ab-f]/ 28 | 29 | /* ✗ BAD */ 30 | var foo = /[bcdefa]/ 31 | var foo = /[b-fa]/ 32 | ``` 33 | 34 | 35 | 36 | ## :wrench: Options 37 | 38 | ```json5 39 | { 40 | "regexp/sort-character-class-elements": ["error", { 41 | "order": [ 42 | "\\s", // \s or \S 43 | "\\w", // \w or \W 44 | "\\d", // \d or \D 45 | "\\p", // \p{...} or \P{...} 46 | "*", // Others (A character or range of characters or an element you did not specify.) 47 | "\\q", // \q{...} 48 | "[]", // Nesting character class, or character class expression 49 | ] 50 | }] 51 | } 52 | ``` 53 | 54 | - `"order"` ... An array of your preferred order. The default is `["\\s", "\\w", "\\d", "\\p", "*", "\\q", "[]"]`. 55 | 56 | ## :rocket: Version 57 | 58 | This rule was introduced in eslint-plugin-regexp v0.12.0 59 | 60 | ## :mag: Implementation 61 | 62 | - [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/sort-character-class-elements.ts) 63 | - [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/sort-character-class-elements.ts) 64 | -------------------------------------------------------------------------------- /tests/lib/rules/no-trivially-nested-assertion.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/no-trivially-nested-assertion" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("no-trivially-nested-assertion", rule as any, { 12 | valid: [ 13 | `/(?=(?=a)b)/`, 14 | 15 | // these anchors cannot be negated, so they have to be allowed 16 | `/(?!$)/`, 17 | `/(?({ 52 | prototype: null, 53 | }) 54 | return new TypeGlobalFunction(() => BOOLEAN, BOOLEAN_TYPES) 55 | } 56 | 57 | const getPrototypes: () => { 58 | [key in keyof boolean]: TypeInfo | null 59 | } = lazy(() => 60 | createObject<{ 61 | [key in keyof boolean]: TypeInfo | null 62 | }>({ 63 | ...getObjectPrototypes(), 64 | // ES5 65 | valueOf: RETURN_BOOLEAN, 66 | }), 67 | ) 68 | -------------------------------------------------------------------------------- /tests/lib/rules/hexadecimal-escape.ts: -------------------------------------------------------------------------------- 1 | import { SnapshotRuleTester } from "eslint-snapshot-rule-tester" 2 | import rule from "../../../lib/rules/hexadecimal-escape" 3 | 4 | const tester = new SnapshotRuleTester({ 5 | languageOptions: { 6 | ecmaVersion: "latest", 7 | sourceType: "module", 8 | }, 9 | }) 10 | 11 | tester.run("hexadecimal-escape", rule as any, { 12 | valid: [ 13 | String.raw`/a \x0a \cM \0 \u0100 \u{100}/u`, 14 | String.raw`/\7/`, 15 | { 16 | code: String.raw`/a \x0a \cM \0 \u0100 \u{100}/u`, 17 | options: ["always"], 18 | }, 19 | { 20 | code: String.raw`/\7/`, 21 | options: ["always"], 22 | }, 23 | { 24 | code: String.raw`/a \u000a \u{a} \cM \0 \u0100 \u{100}/u`, 25 | options: ["never"], 26 | }, 27 | { 28 | code: String.raw`/\7/`, 29 | options: ["never"], 30 | }, 31 | String.raw`/\cA \cB \cM/`, 32 | { 33 | code: String.raw`/[\q{\x0a}]/v`, 34 | options: ["always"], 35 | }, 36 | { 37 | code: String.raw`/[\q{\u000a}]/v`, 38 | options: ["never"], 39 | }, 40 | ], 41 | invalid: [ 42 | String.raw`/\u000a \u{00000a}/u`, 43 | { 44 | code: String.raw`/\u000a \u{00000a}/u`, 45 | options: ["always"], 46 | }, 47 | { 48 | code: String.raw`/\x0f \xff/u`, 49 | options: ["never"], 50 | }, 51 | { 52 | code: String.raw`/\x0a \x0b \x41/u`, 53 | options: ["never"], 54 | }, 55 | { 56 | code: String.raw`/[\q{\u000a \u{00000a}}]/v`, 57 | options: ["always"], 58 | }, 59 | { 60 | code: String.raw`/[\q{\x0f \xff}]/v`, 61 | options: ["never"], 62 | }, 63 | ], 64 | }) 65 | --------------------------------------------------------------------------------