├── .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 |
2 |
11 |
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 |
13 |
14 |
15 |
16 |
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 |
--------------------------------------------------------------------------------