Skip to content

Commit b0308cb

Browse files
authored
Update no-dupe-characters-character-class#charSetToReadableString() to be shared by rules. (#161)
1 parent 41e843f commit b0308cb

File tree

7 files changed

+81
-84
lines changed

7 files changed

+81
-84
lines changed

lib/rules/no-dupe-characters-character-class.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import type {
1010
AnyCharacterSet,
1111
} from "regexpp/ast"
1212
import type { RegExpContext } from "../utils"
13-
import { createRule, defineRegexpVisitor } from "../utils"
13+
import { createRule, defineRegexpVisitor, toCharSetSource } from "../utils"
1414
import type { CharSet } from "refa"
15-
import { JS } from "refa"
1615
import type { ReadonlyFlags } from "regexp-ast-analysis"
1716

1817
/**
@@ -75,22 +74,6 @@ function groupingElements(
7574
}
7675
}
7776

78-
/**
79-
* Returns a readable representation of the given char set.
80-
*/
81-
function charSetToReadableString(
82-
charSet: CharSet,
83-
flags: ReadonlyFlags,
84-
): string {
85-
return JS.toLiteral(
86-
{
87-
type: "Concatenation",
88-
elements: [{ type: "CharacterClass", characters: charSet }],
89-
},
90-
{ flags },
91-
).source
92-
}
93-
9477
export default createRule("no-dupe-characters-character-class", {
9578
meta: {
9679
type: "suggestion",
@@ -155,10 +138,7 @@ export default createRule("no-dupe-characters-character-class", {
155138
data: {
156139
elementA: element.raw,
157140
elementB: intersectElement.raw,
158-
intersection: charSetToReadableString(
159-
intersection,
160-
flags,
161-
),
141+
intersection: toCharSetSource(intersection, flags),
162142
},
163143
})
164144
}

lib/rules/no-invisible-character.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import type { RegExpContextForLiteral, RegExpContextForSource } from "../utils"
44
import {
55
createRule,
66
defineRegexpVisitor,
7-
invisibleEscape,
87
isInvisible,
8+
toCharSetSource,
99
} from "../utils"
1010

1111
export default createRule("no-invisible-character", {
@@ -30,6 +30,7 @@ export default createRule("no-invisible-character", {
3030
*/
3131
function createLiteralVisitor({
3232
node,
33+
flags,
3334
getRegexpLocation,
3435
getRegexpRange,
3536
}: RegExpContextForLiteral): RegExpVisitor.Handlers {
@@ -39,9 +40,7 @@ export default createRule("no-invisible-character", {
3940
return
4041
}
4142
if (cNode.raw.length === 1 && isInvisible(cNode.value)) {
42-
const instead = invisibleEscape(
43-
String.fromCodePoint(cNode.value),
44-
)
43+
const instead = toCharSetSource(cNode.value, flags)
4544
context.report({
4645
node,
4746
loc: getRegexpLocation(cNode),
@@ -63,14 +62,14 @@ export default createRule("no-invisible-character", {
6362
/**
6463
* Verify a given string literal.
6564
*/
66-
function verifyString({ node }: RegExpContextForSource): void {
65+
function verifyString({ node, flags }: RegExpContextForSource): void {
6766
const text = sourceCode.getText(node)
6867

6968
let index = 0
7069
for (const c of text) {
7170
const cp = c.codePointAt(0)!
7271
if (isInvisible(cp)) {
73-
const instead = invisibleEscape(cp)
72+
const instead = toCharSetSource(cp, flags)
7473
const range: AST.Range = [
7574
node.range![0] + index,
7675
node.range![0] + index + c.length,

lib/utils/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { ReadonlyFlags, ToCharSetElement } from "regexp-ast-analysis"
1616
// eslint-disable-next-line no-restricted-imports -- Implement RegExpContext#toCharSet
1717
import { toCharSet } from "regexp-ast-analysis"
1818
import type { CharSet } from "refa"
19+
import { JS } from "refa"
1920
export * from "./unicode"
2021

2122
export type ToCharSet = (
@@ -750,6 +751,28 @@ export function quantToString(quant: Readonly<Quant>): string {
750751
return value
751752
}
752753

754+
/**
755+
* Returns a regexp literal source of the given char set or char.
756+
*/
757+
export function toCharSetSource(
758+
charSetOrChar: CharSet | number,
759+
flags: ReadonlyFlags,
760+
): string {
761+
let charSet
762+
if (typeof charSetOrChar === "number") {
763+
charSet = JS.createCharSet([charSetOrChar], flags)
764+
} else {
765+
charSet = charSetOrChar
766+
}
767+
return JS.toLiteral(
768+
{
769+
type: "Concatenation",
770+
elements: [{ type: "CharacterClass", characters: charSet }],
771+
},
772+
{ flags },
773+
).source
774+
}
775+
753776
/* eslint-disable complexity -- X( */
754777
/**
755778
* Returns whether the concatenation of the two string might create new escape

lib/utils/unicode.ts

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -176,44 +176,3 @@ export function isInvisible(codePoint: number): boolean {
176176
codePoint === CP_BRAILLE_PATTERN_BLANK
177177
)
178178
}
179-
180-
/**
181-
* Returns a string with invisible characters converted to escape characters.
182-
*/
183-
export function invisibleEscape(val: string | number): string {
184-
let result = ""
185-
186-
for (const cp of typeof val === "number" ? [val] : codePoints(val)) {
187-
if (cp !== CP_SPACE && isInvisible(cp)) {
188-
if (cp === CP_TAB) {
189-
result += "\\t"
190-
} else if (cp === CP_LF) {
191-
result += "\\r"
192-
} else if (cp === CP_CR) {
193-
result += "\\n"
194-
} else if (cp === CP_VT) {
195-
result += "\\v"
196-
} else if (cp === CP_FF) {
197-
result += "\\f"
198-
} else {
199-
result += `\\u${`${cp.toString(16)}`.padStart(4, "0")}`
200-
}
201-
} else {
202-
result += String.fromCodePoint(cp)
203-
}
204-
}
205-
return result
206-
}
207-
208-
/**
209-
* String to code points
210-
*/
211-
function* codePoints(s: string) {
212-
for (let i = 0; i < s.length; i += 1) {
213-
const cp = s.codePointAt(i)!
214-
yield cp
215-
if (cp >= 0x10000) {
216-
i += 1
217-
}
218-
}
219-
}

tests/lib/rules/no-invisible-character.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ tester.run("no-invisible-character", rule as any, {
2323
invalid: [
2424
{
2525
code: "/\u00a0/",
26-
output: "/\\u00a0/",
27-
errors: ["Unexpected invisible character. Use '\\u00a0' instead."],
26+
output: "/\\xa0/",
27+
errors: ["Unexpected invisible character. Use '\\xa0' instead."],
2828
},
2929
{
3030
code: "/[\t]/",
@@ -35,10 +35,10 @@ tester.run("no-invisible-character", rule as any, {
3535
code:
3636
"/[\t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff\u0085\u200b]/",
3737
output:
38-
"/[\\t\u00a0\\u1680\u180e\\u2000\u2001\\u2002\u2003\\u2004\u2005\\u2006\u2007\\u2008\u2009\\u200a\u202f\\u205f\u3000\\ufeff\u0085\\u200b]/",
38+
"/[\\t\xa0\\u1680\u180e\\u2000\u2001\\u2002\u2003\\u2004\u2005\\u2006\u2007\\u2008\u2009\\u200a\u202f\\u205f\u3000\\ufeff\x85\\u200b]/",
3939
errors: [
4040
"Unexpected invisible character. Use '\\t' instead.",
41-
"Unexpected invisible character. Use '\\u00a0' instead.",
41+
"Unexpected invisible character. Use '\\xa0' instead.",
4242
"Unexpected invisible character. Use '\\u1680' instead.",
4343
"Unexpected invisible character. Use '\\u180e' instead.",
4444
"Unexpected invisible character. Use '\\u2000' instead.",
@@ -56,17 +56,17 @@ tester.run("no-invisible-character", rule as any, {
5656
"Unexpected invisible character. Use '\\u205f' instead.",
5757
"Unexpected invisible character. Use '\\u3000' instead.",
5858
"Unexpected invisible character. Use '\\ufeff' instead.",
59-
"Unexpected invisible character. Use '\\u0085' instead.",
59+
"Unexpected invisible character. Use '\\x85' instead.",
6060
"Unexpected invisible character. Use '\\u200b' instead.",
6161
],
6262
},
6363
{
6464
code:
6565
"/[\\t\u00a0\\u1680\u180e\\u2000\u2001\\u2002\u2003\\u2004\u2005\\u2006\u2007\\u2008\u2009\\u200a\u202f\\u205f\u3000\\ufeff\u0085\\u200b]/",
6666
output:
67-
"/[\\t\\u00a0\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\ufeff\\u0085\\u200b]/",
67+
"/[\\t\\xa0\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\ufeff\\x85\\u200b]/",
6868
errors: [
69-
"Unexpected invisible character. Use '\\u00a0' instead.",
69+
"Unexpected invisible character. Use '\\xa0' instead.",
7070
"Unexpected invisible character. Use '\\u180e' instead.",
7171
"Unexpected invisible character. Use '\\u2001' instead.",
7272
"Unexpected invisible character. Use '\\u2003' instead.",
@@ -75,17 +75,17 @@ tester.run("no-invisible-character", rule as any, {
7575
"Unexpected invisible character. Use '\\u2009' instead.",
7676
"Unexpected invisible character. Use '\\u202f' instead.",
7777
"Unexpected invisible character. Use '\\u3000' instead.",
78-
"Unexpected invisible character. Use '\\u0085' instead.",
78+
"Unexpected invisible character. Use '\\x85' instead.",
7979
],
8080
},
8181
{
8282
code:
8383
"new RegExp('\t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff\u0085\u200b')",
8484
output:
85-
"new RegExp('\\t\u00a0\\u1680\u180e\\u2000\u2001\\u2002\u2003\\u2004\u2005\\u2006\u2007\\u2008\u2009\\u200a\u202f\\u205f\u3000\\ufeff\u0085\\u200b')",
85+
"new RegExp('\\t\xa0\\u1680\u180e\\u2000\u2001\\u2002\u2003\\u2004\u2005\\u2006\u2007\\u2008\u2009\\u200a\u202f\\u205f\u3000\\ufeff\x85\\u200b')",
8686
errors: [
8787
"Unexpected invisible character. Use '\\t' instead.",
88-
"Unexpected invisible character. Use '\\u00a0' instead.",
88+
"Unexpected invisible character. Use '\\xa0' instead.",
8989
"Unexpected invisible character. Use '\\u1680' instead.",
9090
"Unexpected invisible character. Use '\\u180e' instead.",
9191
"Unexpected invisible character. Use '\\u2000' instead.",
@@ -103,7 +103,7 @@ tester.run("no-invisible-character", rule as any, {
103103
"Unexpected invisible character. Use '\\u205f' instead.",
104104
"Unexpected invisible character. Use '\\u3000' instead.",
105105
"Unexpected invisible character. Use '\\ufeff' instead.",
106-
"Unexpected invisible character. Use '\\u0085' instead.",
106+
"Unexpected invisible character. Use '\\x85' instead.",
107107
"Unexpected invisible character. Use '\\u200b' instead.",
108108
],
109109
},

tests/lib/utils/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import assert from "assert"
2+
import { toCharSetSource } from "../../../lib/utils"
3+
4+
describe("toCharSetSource", () => {
5+
for (const c of "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789") {
6+
const cp = c.codePointAt(0)!
7+
it(`0x${cp.toString(16)} to ${c}`, () => {
8+
assert.strictEqual(toCharSetSource(cp, {}), c)
9+
})
10+
}
11+
it(`0x9 to \\t`, () => {
12+
assert.strictEqual(toCharSetSource(9, {}), "\\t")
13+
})
14+
it(`0xA to \\n`, () => {
15+
assert.strictEqual(toCharSetSource(10, {}), "\\n")
16+
})
17+
it(`0xC to \\f`, () => {
18+
assert.strictEqual(toCharSetSource(12, {}), "\\f")
19+
})
20+
it(`0xD to \\n`, () => {
21+
assert.strictEqual(toCharSetSource(13, {}), "\\r")
22+
})
23+
})
24+
25+
describe("toCharSetSource with invisible chars", () => {
26+
const str =
27+
"\v\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff" +
28+
"\u0085\u200b\u200c\u200d\u200e\u200f\u2800"
29+
30+
for (const c of str) {
31+
const cp = c.codePointAt(0)!
32+
it(`0x${cp.toString(16)}`, () => {
33+
assert.notStrictEqual(toCharSetSource(cp, {}), c)
34+
})
35+
}
36+
})

tests/lib/utils/unicode.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import assert from "assert"
2+
import { toCharSetSource } from "../../../lib/utils"
23
import {
34
isSpace,
45
isInvisible,
5-
invisibleEscape,
66
CP_NEL,
77
CP_ZWSP,
88
} from "../../../lib/utils/unicode"
@@ -12,13 +12,13 @@ const SPACES =
1212

1313
describe("isSpace", () => {
1414
for (const c of SPACES) {
15-
it(`${invisibleEscape(c)} is space`, () => {
15+
it(`${toCharSetSource(c.codePointAt(0)!, {})} is space`, () => {
1616
assert.ok(isSpace(c.codePointAt(0)!))
1717
})
1818
}
1919

2020
for (const c of [CP_NEL, CP_ZWSP]) {
21-
it(`${invisibleEscape(c)} is not space`, () => {
21+
it(`${toCharSetSource(c, {})} is not space`, () => {
2222
assert.ok(!isSpace(c))
2323
})
2424
}
@@ -28,7 +28,7 @@ describe("isInvisible", () => {
2828
const str = `${SPACES}\u0085\u200b\u200c\u200d\u200e\u200f\u2800`
2929

3030
for (const c of str) {
31-
it(`${invisibleEscape(c)} is invisible`, () => {
31+
it(`${toCharSetSource(c.codePointAt(0)!, {})} is invisible`, () => {
3232
assert.ok(isInvisible(c.codePointAt(0)!))
3333
})
3434
}

0 commit comments

Comments
 (0)