Skip to content

Commit 58324c1

Browse files
authored
Add regexp/no-standalone-backslash rule (#175)
1 parent 3e5fc64 commit 58324c1

File tree

9 files changed

+163
-43
lines changed

9 files changed

+163
-43
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
104104
| [regexp/no-octal](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-octal.html) | disallow octal escape sequence | :star: |
105105
| [regexp/no-optional-assertion](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-optional-assertion.html) | disallow optional assertions | |
106106
| [regexp/no-potentially-useless-backreference](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-potentially-useless-backreference.html) | disallow backreferences that reference a group that might not be matched | |
107+
| [regexp/no-standalone-backslash](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-standalone-backslash.html) | disallow standalone backslashes (`\`) | |
107108
| [regexp/no-trivially-nested-assertion](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-trivially-nested-assertion.html) | disallow trivially nested assertions | :wrench: |
108109
| [regexp/no-trivially-nested-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-trivially-nested-quantifier.html) | disallow nested quantifiers that can be rewritten as one quantifier | :wrench: |
109110
| [regexp/no-unused-capturing-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-unused-capturing-group.html) | disallow unused capturing group | |

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
3232
| [regexp/no-octal](./no-octal.md) | disallow octal escape sequence | :star: |
3333
| [regexp/no-optional-assertion](./no-optional-assertion.md) | disallow optional assertions | |
3434
| [regexp/no-potentially-useless-backreference](./no-potentially-useless-backreference.md) | disallow backreferences that reference a group that might not be matched | |
35+
| [regexp/no-standalone-backslash](./no-standalone-backslash.md) | disallow standalone backslashes (`\`) | |
3536
| [regexp/no-trivially-nested-assertion](./no-trivially-nested-assertion.md) | disallow trivially nested assertions | :wrench: |
3637
| [regexp/no-trivially-nested-quantifier](./no-trivially-nested-quantifier.md) | disallow nested quantifiers that can be rewritten as one quantifier | :wrench: |
3738
| [regexp/no-unused-capturing-group](./no-unused-capturing-group.md) | disallow unused capturing group | |

docs/rules/no-standalone-backslash.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "regexp/no-standalone-backslash"
5+
description: "disallow standalone backslashes (`\\`)"
6+
---
7+
# regexp/no-standalone-backslash
8+
9+
> disallow standalone backslashes (`\`)
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
13+
## :book: Rule Details
14+
15+
This rule disallows backslash (`\`) without escape.
16+
17+
E.g. the regular expression `/\c/` without the unicode (`u`) flag is the same pattern as `/\\c/`.
18+
19+
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.
20+
21+
This behavior is described in [Annex B] of the ECMAScript specification.
22+
23+
[Annex B]: https://tc39.es/ecma262/#sec-regular-expressions-patterns
24+
25+
<eslint-code-block>
26+
27+
```js
28+
/* eslint regexp/no-standalone-backslash: "error" */
29+
30+
/* ✓ GOOD */
31+
var foo = /\cX/;
32+
33+
/* ✗ BAD */
34+
var foo = /\c/;
35+
var foo = /\c-/;
36+
var foo = /[\c]/;
37+
```
38+
39+
</eslint-code-block>
40+
41+
## :wrench: Options
42+
43+
Nothing.
44+
45+
## :couple: Related rules
46+
47+
- [regexp/no-useless-escape](./no-useless-escape.md)
48+
49+
## :books: Further reading
50+
51+
- [ECMAScript® 2022 Language Specification > Annex B > B.1.4 Regular Expressions Patterns](https://tc39.es/ecma262/#sec-regular-expressions-patterns)
52+
53+
## :mag: Implementation
54+
55+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-standalone-backslash.ts)
56+
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-standalone-backslash.ts)

docs/rules/no-useless-escape.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ var foo = /\u{[41]}/
5454

5555
Nothing.
5656

57-
## :books: Further reading
57+
## :couple: Related rules
5858

59+
- [regexp/no-standalone-backslash]
5960
- [no-useless-escape]
6061

62+
[regexp/no-standalone-backslash]: ./no-standalone-backslash.md
6163
[no-useless-escape]: https://eslint.org/docs/rules/no-useless-escape
6264

6365
## :rocket: Version

lib/rules/no-standalone-backslash.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { RegExpVisitor } from "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+
// TODO Switch to recommended in the major version.
10+
// recommended: true,
11+
recommended: false,
12+
},
13+
schema: [],
14+
messages: {
15+
unexpected:
16+
"Unexpected standalone backslash (`\\`). It looks like an escape sequence, but it's a single `\\` character pattern.",
17+
},
18+
type: "suggestion", // "problem",
19+
},
20+
create(context) {
21+
/**
22+
* Create visitor
23+
*/
24+
function createVisitor({
25+
node,
26+
getRegexpLocation,
27+
}: RegExpContext): RegExpVisitor.Handlers {
28+
return {
29+
onCharacterEnter(cNode) {
30+
if (cNode.value === CP_BACK_SLASH && cNode.raw === "\\") {
31+
context.report({
32+
node,
33+
loc: getRegexpLocation(cNode),
34+
messageId: "unexpected",
35+
})
36+
}
37+
},
38+
}
39+
}
40+
41+
return defineRegexpVisitor(context, {
42+
createVisitor,
43+
})
44+
},
45+
})

lib/rules/no-useless-escape.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -121,24 +121,6 @@ export default createRule("no-useless-escape", {
121121
}
122122
report(cNode, 0, char)
123123
}
124-
// else if (cNode.value === CP_BACK_SLASH) {
125-
// // Invalid escape for /\c/
126-
// if (cNode.raw === "\\") {
127-
// const parent = cNode.parent
128-
// if (
129-
// parent.type === "Alternative" ||
130-
// parent.type === "CharacterClass"
131-
// ) {
132-
// const next =
133-
// parent.elements[
134-
// parent.elements.indexOf(cNode) + 1
135-
// ]
136-
// if (next && next.raw.length === 1) {
137-
// report(cNode, 0, next.raw)
138-
// }
139-
// }
140-
// }
141-
// }
142124
}
143125
},
144126
}

lib/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import noObscureRange from "../rules/no-obscure-range"
2020
import noOctal from "../rules/no-octal"
2121
import noOptionalAssertion from "../rules/no-optional-assertion"
2222
import noPotentiallyUselessBackreference from "../rules/no-potentially-useless-backreference"
23+
import noStandaloneBackslash from "../rules/no-standalone-backslash"
2324
import noTriviallyNestedAssertion from "../rules/no-trivially-nested-assertion"
2425
import noTriviallyNestedQuantifier from "../rules/no-trivially-nested-quantifier"
2526
import noUnusedCapturingGroup from "../rules/no-unused-capturing-group"
@@ -76,6 +77,7 @@ export const rules = [
7677
noOctal,
7778
noOptionalAssertion,
7879
noPotentiallyUselessBackreference,
80+
noStandaloneBackslash,
7981
noTriviallyNestedAssertion,
8082
noTriviallyNestedQuantifier,
8183
noUnusedCapturingGroup,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { RuleTester } from "eslint"
2+
import rule from "../../../lib/rules/no-standalone-backslash"
3+
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 2020,
7+
sourceType: "module",
8+
},
9+
})
10+
11+
tester.run("no-standalone-backslash", rule as any, {
12+
valid: [String.raw`/\cX/`],
13+
invalid: [
14+
{
15+
code: String.raw`/\c/`,
16+
errors: [
17+
{
18+
message:
19+
"Unexpected standalone backslash (`\\`). It looks like an escape sequence, but it's a single `\\` character pattern.",
20+
column: 2,
21+
},
22+
],
23+
},
24+
{
25+
code: String.raw`/\c-/`,
26+
errors: [
27+
{
28+
message:
29+
"Unexpected standalone backslash (`\\`). It looks like an escape sequence, but it's a single `\\` character pattern.",
30+
column: 2,
31+
},
32+
],
33+
},
34+
{
35+
code: String.raw`/\c1/`,
36+
errors: [
37+
{
38+
message:
39+
"Unexpected standalone backslash (`\\`). It looks like an escape sequence, but it's a single `\\` character pattern.",
40+
column: 2,
41+
},
42+
],
43+
},
44+
{
45+
code: String.raw`/[\c]/`,
46+
errors: [
47+
{
48+
message:
49+
"Unexpected standalone backslash (`\\`). It looks like an escape sequence, but it's a single `\\` character pattern.",
50+
column: 3,
51+
},
52+
],
53+
},
54+
],
55+
})

tests/lib/rules/no-useless-escape.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -122,29 +122,5 @@ tester.run("no-useless-escape", rule as any, {
122122
"Unnecessary escape character: \\P.",
123123
],
124124
},
125-
// {
126-
// code: String.raw`/\c1/`,
127-
// errors: [
128-
// {
129-
// message: "Unnecessary escape character: \\c.",
130-
// line: 1,
131-
// column: 2,
132-
// endLine: 1,
133-
// endColumn: 3,
134-
// },
135-
// ],
136-
// },
137-
// {
138-
// code: String.raw`/[\c]/`,
139-
// errors: [
140-
// {
141-
// message: "Unnecessary escape character: \\c.",
142-
// line: 1,
143-
// column: 3,
144-
// endLine: 1,
145-
// endColumn: 4,
146-
// },
147-
// ],
148-
// },
149125
],
150126
})

0 commit comments

Comments
 (0)