Skip to content

Commit ce599ff

Browse files
RunDevelopmentota-meshi
authored andcommitted
Fix regexp/negation false positives (#275)
* Fix `regexp/negation` false positives * Fixed cache bug toCharSet is cached and asumes that given elements are not changed. I previously broke that assumption.
1 parent 448d349 commit ce599ff

File tree

2 files changed

+49
-9
lines changed

2 files changed

+49
-9
lines changed

lib/rules/negation.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,51 @@ export default createRule("negation", {
3131
node,
3232
getRegexpLocation,
3333
fixReplaceNode,
34+
toCharSet,
35+
flags,
3436
}: RegExpContext): RegExpVisitor.Handlers {
3537
return {
3638
onCharacterClassEnter(ccNode) {
3739
if (!ccNode.negate || ccNode.elements.length !== 1) {
3840
return
3941
}
42+
4043
const element = ccNode.elements[0]
41-
if (element.type === "CharacterSet") {
42-
const negatedCharSet = getNegationText(element)
43-
context.report({
44-
node,
45-
loc: getRegexpLocation(ccNode),
46-
messageId: "unexpected",
47-
data: { negatedCharSet },
48-
fix: fixReplaceNode(ccNode, negatedCharSet),
44+
if (element.type !== "CharacterSet") {
45+
return
46+
}
47+
48+
if (flags.ignoreCase && element.kind === "property") {
49+
// The ignore case canonicalization affects negated
50+
// Unicode property escapes in a weird way. In short,
51+
// /\p{Foo}/i is not the same as /[^\P{Foo}]/i if
52+
// \p{Foo} contains case-varying characters.
53+
//
54+
// Note: This only affects Unicode property escapes.
55+
// All other character sets are either case-invariant
56+
// (/./, /\s/, /\d/) or inconsistent (/\w/).
57+
58+
const ccSet = toCharSet(ccNode)
59+
60+
const negatedElementSet = toCharSet({
61+
...element,
62+
negate: !element.negate,
4963
})
64+
65+
if (!ccSet.equals(negatedElementSet)) {
66+
// We cannot remove the negative
67+
return
68+
}
5069
}
70+
71+
const negatedCharSet = getNegationText(element)
72+
context.report({
73+
node,
74+
loc: getRegexpLocation(ccNode),
75+
messageId: "unexpected",
76+
data: { negatedCharSet },
77+
fix: fixReplaceNode(ccNode, negatedCharSet),
78+
})
5179
},
5280
}
5381
}

tests/lib/rules/negation.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ const tester = new RuleTester({
99
})
1010

1111
tester.run("negation", rule as any, {
12-
valid: [String.raw`/[\d]/`, String.raw`/[^\d\s]/`],
12+
valid: [
13+
String.raw`/[\d]/`,
14+
String.raw`/[^\d\s]/`,
15+
String.raw`/[^\p{ASCII}]/iu`,
16+
String.raw`/[^\P{Ll}]/iu`,
17+
],
1318
invalid: [
1419
{
1520
code: String.raw`/[^\d]/`,
@@ -94,6 +99,13 @@ tester.run("negation", rule as any, {
9499
"Unexpected negated character class. Use '\\p{Ll}' instead.",
95100
],
96101
},
102+
{
103+
code: String.raw`/[^\P{White_Space}]/iu;`,
104+
output: String.raw`/\p{White_Space}/iu;`,
105+
errors: [
106+
"Unexpected negated character class. Use '\\p{White_Space}' instead.",
107+
],
108+
},
97109
{
98110
code: String.raw`const s ="[^\\w]"
99111
new RegExp(s)`,

0 commit comments

Comments
 (0)