Skip to content

Commit 7d77243

Browse files
Remove duplicate code and cache JS.Parser instances (#424)
* Remove duplicate code and cache `JS.Parser` instances * Change sort-alternatives to use getParser Co-authored-by: yosuke ota <[email protected]>
1 parent cde355f commit 7d77243

File tree

5 files changed

+59
-99
lines changed

5 files changed

+59
-99
lines changed

lib/rules/no-dupe-disjunctions.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
fixRemoveCharacterClassElement,
1616
fixRemoveAlternative,
1717
} from "../utils"
18-
import { isCoveredNode, isEqualNodes } from "../utils/regexp-ast"
18+
import { getParser, isCoveredNode, isEqualNodes } from "../utils/regexp-ast"
1919
import type { Expression, FiniteAutomaton, NoParent, ReadonlyNFA } from "refa"
2020
import {
2121
combineTransformers,
@@ -35,7 +35,6 @@ import {
3535
getEffectiveMaximumRepetition,
3636
canReorder,
3737
} from "regexp-ast-analysis"
38-
import { RegExpParser } from "regexpp"
3938
import { UsageOfPattern } from "../utils/get-usage-of-pattern"
4039
import { mention, mentionChar } from "../utils/mention"
4140
import type { NestedAlternative } from "../utils/partial-parser"
@@ -964,25 +963,10 @@ export default createRule("no-dupe-disjunctions", {
964963
function createVisitor(
965964
regexpContext: RegExpContext,
966965
): RegExpVisitor.Handlers {
967-
const {
968-
patternAst,
969-
flagsString,
970-
flags,
971-
node,
972-
getRegexpLocation,
973-
getUsageOfPattern,
974-
} = regexpContext
975-
976-
const parser = JS.Parser.fromAst({
977-
pattern: patternAst,
978-
flags: new RegExpParser().parseFlags(
979-
[
980-
...new Set(
981-
(flagsString || "").replace(/[^gimsuy]/gu, ""),
982-
),
983-
].join(""),
984-
),
985-
})
966+
const { flags, node, getRegexpLocation, getUsageOfPattern } =
967+
regexpContext
968+
969+
const parser = getParser(regexpContext)
986970

987971
/** Returns the filter information for the given node */
988972
function getFilterInfo(parentNode: ParentNode): FilterInfo {

lib/rules/no-super-linear-backtracking.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import type { RegExpVisitor } from "regexpp/visitor"
22
import type { RegExpContext } from "../utils"
33
import { createRule, defineRegexpVisitor } from "../utils"
44
import { UsageOfPattern } from "../utils/get-usage-of-pattern"
5-
import type { ParsedLiteral } from "scslre"
65
import { analyse } from "scslre"
76
import type { Position, SourceLocation } from "estree"
87
import { mention } from "../utils/mention"
8+
import { getJSRegexppAst } from "../utils/regexp-ast"
99

1010
/**
1111
* Returns the combined source location of the two given locations.
@@ -27,31 +27,6 @@ function unionLocations(a: SourceLocation, b: SourceLocation): SourceLocation {
2727
}
2828
}
2929

30-
/**
31-
* Create a parsed literal object as required by the scslre library.
32-
*/
33-
function getParsedLiteral(context: RegExpContext): ParsedLiteral {
34-
const { flags, flagsString, patternAst } = context
35-
36-
return {
37-
pattern: patternAst,
38-
flags: {
39-
type: "Flags",
40-
raw: flagsString ?? "",
41-
parent: null,
42-
start: NaN,
43-
end: NaN,
44-
dotAll: flags.dotAll ?? false,
45-
global: flags.global ?? false,
46-
hasIndices: flags.hasIndices ?? false,
47-
ignoreCase: flags.ignoreCase ?? false,
48-
multiline: flags.multiline ?? false,
49-
sticky: flags.sticky ?? false,
50-
unicode: flags.unicode ?? false,
51-
},
52-
}
53-
}
54-
5530
export default createRule("no-super-linear-backtracking", {
5631
meta: {
5732
docs: {
@@ -102,7 +77,7 @@ export default createRule("no-super-linear-backtracking", {
10277
getUsageOfPattern,
10378
} = regexpContext
10479

105-
const result = analyse(getParsedLiteral(regexpContext), {
80+
const result = analyse(getJSRegexppAst(regexpContext), {
10681
reportTypes: { Move: false },
10782
assumeRejectingSuffix:
10883
reportUncertain &&

lib/rules/no-super-linear-move.ts

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { Alternative, Pattern, Quantifier } from "regexpp/ast"
33
import type { RegExpContext } from "../utils"
44
import { createRule, defineRegexpVisitor } from "../utils"
55
import { UsageOfPattern } from "../utils/get-usage-of-pattern"
6-
import type { ParsedLiteral } from "scslre"
76
import { analyse } from "scslre"
87
import type { Descendant } from "regexp-ast-analysis"
98
import {
@@ -19,40 +18,13 @@ import {
1918
combineTransformers,
2019
Transformers,
2120
} from "refa"
21+
import { getJSRegexppAst } from "../utils/regexp-ast"
2222

2323
interface Report {
2424
quant: Quantifier
2525
attack: string
2626
}
2727

28-
/**
29-
* Create a parsed literal object as required by the scslre library.
30-
*/
31-
function getParsedLiteral(
32-
context: RegExpContext,
33-
ignoreSticky: boolean,
34-
): ParsedLiteral {
35-
const { flags, flagsString, patternAst } = context
36-
37-
return {
38-
pattern: patternAst,
39-
flags: {
40-
type: "Flags",
41-
raw: flagsString ?? "",
42-
parent: null,
43-
start: NaN,
44-
end: NaN,
45-
dotAll: flags.dotAll ?? false,
46-
global: flags.global ?? false,
47-
hasIndices: flags.hasIndices ?? false,
48-
ignoreCase: flags.ignoreCase ?? false,
49-
multiline: flags.multiline ?? false,
50-
sticky: !ignoreSticky && (flags.sticky ?? false),
51-
unicode: flags.unicode ?? false,
52-
},
53-
}
54-
}
55-
5628
/**
5729
* Removes duplicates from the given reports.
5830
*/
@@ -187,7 +159,7 @@ export default createRule("no-super-linear-move", {
187159
): Iterable<Report> {
188160
const { flags } = regexpContext
189161

190-
const result = analyse(getParsedLiteral(regexpContext, true), {
162+
const result = analyse(getJSRegexppAst(regexpContext, true), {
191163
reportTypes: { Move: true, Self: false, Trade: false },
192164
assumeRejectingSuffix,
193165
})
@@ -227,7 +199,7 @@ export default createRule("no-super-linear-move", {
227199
const { patternAst, flags } = regexpContext
228200

229201
const parser = JS.Parser.fromAst(
230-
getParsedLiteral(regexpContext, true),
202+
getJSRegexppAst(regexpContext, true),
231203
)
232204

233205
for (const q of findReachableQuantifiers(patternAst)) {

lib/rules/sort-alternatives.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ import {
3333
} from "regexp-ast-analysis"
3434
import type { CharSet, Word, ReadonlyWord } from "refa"
3535
import { NFA, JS, transform } from "refa"
36-
import { getPossiblyConsumedChar } from "../utils/regexp-ast"
37-
import { RegExpParser } from "regexpp"
36+
import { getParser, getPossiblyConsumedChar } from "../utils/regexp-ast"
3837

3938
interface AllowedChars {
4039
allowed: CharSet
@@ -540,28 +539,13 @@ export default createRule("sort-alternatives", {
540539
function createVisitor(
541540
regexpContext: RegExpContext,
542541
): RegExpVisitor.Handlers {
543-
const {
544-
node,
545-
getRegexpLocation,
546-
fixReplaceNode,
547-
flags,
548-
flagsString,
549-
patternAst,
550-
} = regexpContext
542+
const { node, getRegexpLocation, fixReplaceNode, flags } =
543+
regexpContext
551544

552545
const allowedChars = getAllowedChars(flags)
553546

554547
const possibleCharsCache = new Map<Alternative, CharSet>()
555-
const parser = JS.Parser.fromAst({
556-
pattern: patternAst,
557-
flags: new RegExpParser().parseFlags(
558-
[
559-
...new Set(
560-
(flagsString || "").replace(/[^gimsuy]/gu, ""),
561-
),
562-
].join(""),
563-
),
564-
})
548+
const parser = getParser(regexpContext)
565549

566550
/** A cached version of getPossiblyConsumedChar */
567551
function getPossibleChars(a: Alternative): CharSet {

lib/utils/regexp-ast/index.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Rule } from "eslint"
33
import type { Expression } from "estree"
44
import { parseRegExpLiteral, RegExpParser, visitRegExpAST } from "regexpp"
55
import { getStaticValue } from "../ast-utils"
6+
import { JS } from "refa"
67
import type { CharRange, CharSet } from "refa"
78
import type { ReadonlyFlags } from "regexp-ast-analysis"
89
import {
@@ -11,6 +12,7 @@ import {
1112
isEmptyBackreference,
1213
toCharSet,
1314
} from "regexp-ast-analysis"
15+
import type { RegExpContext } from ".."
1416
export { ShortCircuit, getFirstConsumedCharPlusAfter } from "./common"
1517
export * from "./is-covered"
1618
export * from "./is-equals"
@@ -130,3 +132,46 @@ export function getPossiblyConsumedChar(
130132

131133
return { char, exact }
132134
}
135+
136+
/**
137+
* Create a `JS.RegexppAst` object as required by refa's `JS.Parser.fromAst`
138+
* method and `ParsedLiteral` interface of the scslre library.
139+
*/
140+
export function getJSRegexppAst(
141+
context: RegExpContext,
142+
ignoreSticky = false,
143+
): JS.RegexppAst {
144+
const { flags, flagsString, patternAst } = context
145+
146+
return {
147+
pattern: patternAst,
148+
flags: {
149+
type: "Flags",
150+
raw: flagsString ?? "",
151+
parent: null,
152+
start: NaN,
153+
end: NaN,
154+
dotAll: flags.dotAll ?? false,
155+
global: flags.global ?? false,
156+
hasIndices: flags.hasIndices ?? false,
157+
ignoreCase: flags.ignoreCase ?? false,
158+
multiline: flags.multiline ?? false,
159+
sticky: !ignoreSticky && (flags.sticky ?? false),
160+
unicode: flags.unicode ?? false,
161+
},
162+
}
163+
}
164+
165+
const parserCache = new WeakMap<RegExpContext, JS.Parser>()
166+
167+
/**
168+
* Returns a `JS.Parser` for the given regex context.
169+
*/
170+
export function getParser(context: RegExpContext): JS.Parser {
171+
let cached = parserCache.get(context)
172+
if (cached === undefined) {
173+
cached = JS.Parser.fromAst(getJSRegexppAst(context))
174+
parserCache.set(context, cached)
175+
}
176+
return cached
177+
}

0 commit comments

Comments
 (0)