-
-
Notifications
You must be signed in to change notification settings - Fork 353
/
escape-case.js
142 lines (121 loc) · 3.56 KB
/
escape-case.js
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
'use strict';
const {
visitRegExpAST,
parseRegExpLiteral
} = require('regexpp');
const getDocumentationUrl = require('./utils/get-documentation-url');
const replaceTemplateElement = require('./utils/replace-template-element');
const escapeWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/;
const escapePatternWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[a-z])/;
const hasLowercaseCharacter = /[a-z]+/;
const message = 'Use uppercase characters for the value of the escape sequence.';
const fix = (value, regexp) => {
const results = regexp.exec(value);
if (results) {
const {data} = results.groups;
const fixedEscape = data.slice(0, 1) + data.slice(1).toUpperCase();
return (
value.slice(0, results.index) +
fixedEscape +
value.slice(results.index + data.length)
);
}
return value;
};
/**
Find the `[start, end]` position of the lowercase escape sequence in a regular expression literal ASTNode.
@param {string} value - String representation of a literal ASTNode.
@returns {number[] | undefined} The `[start, end]` pair if found, or null if not.
*/
const findLowercaseEscape = value => {
const ast = parseRegExpLiteral(value);
let escapeNodePosition;
visitRegExpAST(ast, {
/**
Record escaped node position in regexpp ASTNode. Returns undefined if not found.
@param {ASTNode} node A regexpp ASTNode. Note that it is of different type to the ASTNode of ESLint parsers
@returns {undefined}
*/
onCharacterLeave(node) {
if (escapeNodePosition) {
return;
}
const matches = node.raw.match(escapePatternWithLowercase);
if (matches && matches.groups.data.slice(1).match(hasLowercaseCharacter)) {
// There is no `range` property in AST from `regexpp`
// reference: https://github.com/mysticatea/regexpp/blob/master/src/ast.ts#L60-L71
escapeNodePosition = [node.start, node.end];
}
}
});
return escapeNodePosition;
};
/**
Produce a fix if there is a lowercase escape sequence in the node.
@param {ASTNode} node - The regular expression literal ASTNode to check.
@returns {string} The fixed `node.raw` string.
*/
const fixRegExp = node => {
const escapeNodePosition = findLowercaseEscape(node.raw);
const {raw} = node;
if (escapeNodePosition) {
const [start, end] = escapeNodePosition;
return (
raw.slice(0, start) +
fix(raw.slice(start, end), escapePatternWithLowercase) +
raw.slice(end, raw.length)
);
}
return raw;
};
const create = context => {
return {
Literal(node) {
if (typeof node.value !== 'string') {
return;
}
const matches = node.raw.match(escapeWithLowercase);
if (matches && matches.groups.data.slice(1).match(hasLowercaseCharacter)) {
context.report({
node,
message,
fix: fixer => fixer.replaceText(node, fix(node.raw, escapeWithLowercase))
});
}
},
'Literal[regex]'(node) {
const escapeNodePosition = findLowercaseEscape(node.raw);
if (escapeNodePosition) {
context.report({
node,
message,
fix: fixer => fixer.replaceText(node, fixRegExp(node))
});
}
},
TemplateElement(node) {
const matches = node.value.raw.match(escapeWithLowercase);
if (matches && matches.groups.data.slice(1).match(hasLowercaseCharacter)) {
context.report({
node,
message,
fix: fixer => replaceTemplateElement(
fixer,
node,
fix(node.value.raw, escapeWithLowercase)
)
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};