2 * @author Toru Nagashima <https://github.com/mysticatea>
6 const { CALL
, CONSTRUCT
, ReferenceTracker
, getStringIfConstant
} = require("eslint-utils");
7 const { RegExpParser
, visitRegExpAST
} = require("regexpp");
8 const { isCombiningCharacter
, isEmojiModifier
, isRegionalIndicatorSymbol
, isSurrogatePair
} = require("./utils/unicode");
10 //------------------------------------------------------------------------------
12 //------------------------------------------------------------------------------
15 * Iterate character sequences of a given nodes.
17 * CharacterClassRange syntax can steal a part of character sequence,
18 * so this function reverts CharacterClassRange syntax and restore the sequence.
19 * @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
20 * @returns {IterableIterator<number[]>} The list of character sequences.
22 function *iterateCharacterSequence(nodes
) {
25 for (const node
of nodes
) {
31 case "CharacterClassRange":
32 seq
.push(node
.min
.value
);
34 seq
= [node
.max
.value
];
53 const hasCharacterSequence
= {
54 surrogatePairWithoutUFlag(chars
) {
55 return chars
.some((c
, i
) => i
!== 0 && isSurrogatePair(chars
[i
- 1], c
));
58 combiningClass(chars
) {
59 return chars
.some((c
, i
) => (
61 isCombiningCharacter(c
) &&
62 !isCombiningCharacter(chars
[i
- 1])
66 emojiModifier(chars
) {
67 return chars
.some((c
, i
) => (
70 !isEmojiModifier(chars
[i
- 1])
74 regionalIndicatorSymbol(chars
) {
75 return chars
.some((c
, i
) => (
77 isRegionalIndicatorSymbol(c
) &&
78 isRegionalIndicatorSymbol(chars
[i
- 1])
83 const lastIndex
= chars
.length
- 1;
85 return chars
.some((c
, i
) => (
89 chars
[i
- 1] !== 0x200d &&
90 chars
[i
+ 1] !== 0x200d
95 const kinds
= Object
.keys(hasCharacterSequence
);
97 //------------------------------------------------------------------------------
99 //------------------------------------------------------------------------------
106 description
: "disallow characters which are made with multiple code points in character class syntax",
107 category
: "Possible Errors",
109 url
: "https://eslint.org/docs/rules/no-misleading-character-class"
115 surrogatePairWithoutUFlag
: "Unexpected surrogate pair in character class. Use 'u' flag.",
116 combiningClass
: "Unexpected combined character in character class.",
117 emojiModifier
: "Unexpected modified Emoji in character class.",
118 regionalIndicatorSymbol
: "Unexpected national flag in character class.",
119 zwj
: "Unexpected joined character sequence in character class."
123 const parser
= new RegExpParser();
126 * Verify a given regular expression.
127 * @param {Node} node The node to report.
128 * @param {string} pattern The regular expression pattern to verify.
129 * @param {string} flags The flags of the regular expression.
132 function verify(node
, pattern
, flags
) {
134 surrogatePairWithoutUFlag
: false,
135 combiningClass
: false,
136 variationSelector
: false,
137 emojiModifier
: false,
138 regionalIndicatorSymbol
: false,
144 patternNode
= parser
.parsePattern(
152 // Ignore regular expressions with syntax errors
156 visitRegExpAST(patternNode
, {
157 onCharacterClassEnter(ccNode
) {
158 for (const chars
of iterateCharacterSequence(ccNode
.elements
)) {
159 for (const kind
of kinds
) {
160 has
[kind
] = has
[kind
] || hasCharacterSequence
[kind
](chars
);
166 for (const kind
of kinds
) {
168 context
.report({ node
, messageId
: kind
});
174 "Literal[regex]"(node
) {
175 verify(node
, node
.regex
.pattern
, node
.regex
.flags
);
178 const scope
= context
.getScope();
179 const tracker
= new ReferenceTracker(scope
);
182 * Iterate calls of RegExp.
183 * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
184 * `const {RegExp: a} = window; new a()`, etc...
186 for (const { node
} of tracker
.iterateGlobalReferences({
187 RegExp
: { [CALL
]: true, [CONSTRUCT
]: true }
189 const [patternNode
, flagsNode
] = node
.arguments
;
190 const pattern
= getStringIfConstant(patternNode
, scope
);
191 const flags
= getStringIfConstant(flagsNode
, scope
);
193 if (typeof pattern
=== "string") {
194 verify(node
, pattern
, flags
|| "");