-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathno-control-character.ts
116 lines (102 loc) · 3.85 KB
/
no-control-character.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import type { Character } from "@eslint-community/regexpp/ast"
import type { RegExpVisitor } from "@eslint-community/regexpp/visitor"
import type { Rule } from "eslint"
import type { RegExpContext } from "../utils"
import { createRule, defineRegexpVisitor } from "../utils"
import { mentionChar, mention } from "../utils/mention"
import { CP_TAB, CP_LF, CP_VT, CP_FF, CP_CR } from "../utils/unicode"
const CONTROL_CHARS = new Map<number, string>([
[0, "\\0"],
[CP_TAB, "\\t"],
[CP_LF, "\\n"],
[CP_VT, "\\v"],
[CP_FF, "\\f"],
[CP_CR, "\\r"],
])
const ALLOWED_CONTROL_CHARS = /^\\[0fnrtv]$/u
export default createRule("no-control-character", {
meta: {
docs: {
description: "disallow control characters",
category: "Possible Errors",
recommended: false,
},
schema: [],
messages: {
unexpected: "Unexpected control character {{ char }}.",
// suggestions
escape: "Use {{ escape }} instead.",
},
type: "suggestion",
hasSuggestions: true,
},
create(context) {
function createVisitor(
regexpContext: RegExpContext,
): RegExpVisitor.Handlers {
const { node, patternSource, getRegexpLocation, fixReplaceNode } =
regexpContext
function isBadEscapeRaw(raw: string, cp: number): boolean {
return (
raw.codePointAt(0)! === cp ||
raw.startsWith("\\x") ||
raw.startsWith("\\u")
)
}
function isAllowedEscapeRaw(raw: string): boolean {
return (
ALLOWED_CONTROL_CHARS.test(raw) ||
(raw.startsWith("\\") &&
ALLOWED_CONTROL_CHARS.test(raw.slice(1)))
)
}
/**
* Whether the given char is represented using an unwanted escape
* sequence.
*/
function isBadEscape(char: Character): boolean {
// We are only interested in control escapes in RegExp literals
const range = patternSource.getReplaceRange(char)?.range
const sourceRaw = range
? context.sourceCode.text.slice(...range)
: char.raw
if (
isAllowedEscapeRaw(char.raw) ||
isAllowedEscapeRaw(sourceRaw)
) {
return false
}
return (
isBadEscapeRaw(char.raw, char.value) ||
(char.raw.startsWith("\\") &&
isBadEscapeRaw(char.raw.slice(1), char.value))
)
}
return {
onCharacterEnter(cNode) {
if (cNode.value <= 0x1f && isBadEscape(cNode)) {
const suggest: Rule.SuggestionReportDescriptor[] = []
const allowedEscape = CONTROL_CHARS.get(cNode.value)
if (allowedEscape !== undefined) {
suggest.push({
messageId: "escape",
data: { escape: mention(allowedEscape) },
fix: fixReplaceNode(cNode, allowedEscape),
})
}
context.report({
node,
loc: getRegexpLocation(cNode),
messageId: "unexpected",
data: { char: mentionChar(cNode) },
suggest,
})
}
},
}
}
return defineRegexpVisitor(context, {
createVisitor,
})
},
})