Skip to content

Commit 49f6c89

Browse files
authored
Improve performance (#29)
* Improve performance * update
1 parent dada98d commit 49f6c89

File tree

6 files changed

+193
-108
lines changed

6 files changed

+193
-108
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
!/docs/.vuepress
99
!/.vscode
1010
!/.github
11+
/tests/fixtures/integrations/eslint-plugin/test.js

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ npm install --save-dev eslint eslint-plugin-regexp
3131
```
3232

3333
> **Requirements**
34-
>
34+
>
3535
> - ESLint v6.0.0 and above
3636
> - Node.js v8.10.0 and above
3737

lib/utils/index.ts

Lines changed: 143 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ import {
1313
import type { Rule, AST, SourceCode } from "eslint"
1414
export * from "./unicode"
1515

16+
type RegexpRule = {
17+
createLiteralVisitor?: (
18+
node: ESTree.RegExpLiteral,
19+
pattern: string,
20+
flags: string,
21+
) => RegExpVisitor.Handlers
22+
createSourceVisitor?: (
23+
node: ESTree.Expression,
24+
pattern: string,
25+
flags: string,
26+
) => RegExpVisitor.Handlers
27+
}
28+
const regexpRules = new WeakMap<ESTree.Program, RegexpRule[]>()
29+
1630
export const FLAG_GLOBAL = "g"
1731
export const FLAG_DOTALL = "s"
1832
export const FLAG_IGNORECASE = "i"
@@ -49,46 +63,64 @@ export function createRule(
4963
export function defineRegexpVisitor(
5064
context: Rule.RuleContext,
5165
rule:
52-
| {
53-
createLiteralVisitor?: (
54-
node: ESTree.RegExpLiteral,
55-
pattern: string,
56-
flags: string,
57-
) => RegExpVisitor.Handlers
58-
createSourceVisitor?: (
59-
node: ESTree.Expression,
60-
pattern: string,
61-
flags: string,
62-
) => RegExpVisitor.Handlers
63-
}
66+
| RegexpRule
6467
| {
6568
createVisitor?: (
6669
node: ESTree.Expression,
6770
pattern: string,
6871
flags: string,
6972
) => RegExpVisitor.Handlers
7073
},
74+
): RuleListener {
75+
const programNode = context.getSourceCode().ast
76+
77+
let visitor: RuleListener
78+
let rules = regexpRules.get(programNode)
79+
if (!rules) {
80+
rules = []
81+
regexpRules.set(programNode, rules)
82+
visitor = buildRegexpVisitor(context, rules, () => {
83+
regexpRules.delete(programNode)
84+
})
85+
} else {
86+
visitor = {}
87+
}
88+
89+
const createLiteralVisitor =
90+
"createVisitor" in rule
91+
? rule.createVisitor
92+
: "createLiteralVisitor" in rule
93+
? rule.createLiteralVisitor
94+
: undefined
95+
const createSourceVisitor =
96+
"createVisitor" in rule
97+
? rule.createVisitor
98+
: "createSourceVisitor" in rule
99+
? rule.createSourceVisitor
100+
: undefined
101+
102+
rules.push({ createLiteralVisitor, createSourceVisitor })
103+
104+
return visitor
105+
}
106+
107+
/** Build RegExp visitor */
108+
function buildRegexpVisitor(
109+
context: Rule.RuleContext,
110+
rules: RegexpRule[],
111+
programExit: (node: ESTree.Program) => void,
71112
): RuleListener {
72113
const parser = new RegExpParser()
73114

74115
/**
75116
* Verify a given regular expression.
76-
* @param node The node to report.
77117
* @param pattern The regular expression pattern to verify.
78118
* @param flags The flags of the regular expression.
79119
*/
80-
function verify<T extends ESTree.Expression>(
81-
node: T,
120+
function verify(
82121
pattern: string,
83122
flags: string,
84-
createVisitor: (
85-
// eslint-disable-next-line no-shadow -- ignore
86-
node: T,
87-
// eslint-disable-next-line no-shadow -- ignore
88-
pattern: string,
89-
// eslint-disable-next-line no-shadow -- ignore
90-
flags: string,
91-
) => RegExpVisitor.Handlers,
123+
createVisitors: () => IterableIterator<RegExpVisitor.Handlers>,
92124
) {
93125
let patternNode
94126

@@ -104,98 +136,102 @@ export function defineRegexpVisitor(
104136
return
105137
}
106138

107-
const visitor = createVisitor(node, pattern, flags)
108-
if (Object.keys(visitor).length === 0) {
139+
const handler: RegExpVisitor.Handlers = {}
140+
for (const visitor of createVisitors()) {
141+
for (const [key, fn] of Object.entries(visitor) as [
142+
keyof RegExpVisitor.Handlers,
143+
RegExpVisitor.Handlers[keyof RegExpVisitor.Handlers],
144+
][]) {
145+
const orig = handler[key]
146+
if (orig) {
147+
handler[key] = (
148+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
149+
node: any,
150+
) => {
151+
orig(node)
152+
fn!(node)
153+
}
154+
} else {
155+
// @ts-expect-error -- ignore
156+
handler[key] = fn
157+
}
158+
}
159+
}
160+
if (Object.keys(handler).length === 0) {
109161
return
110162
}
111163

112-
visitRegExpAST(patternNode, visitor)
164+
visitRegExpAST(patternNode, handler)
113165
}
114166

115-
const createLiteralVisitor =
116-
"createVisitor" in rule
117-
? rule.createVisitor
118-
: "createLiteralVisitor" in rule
119-
? rule.createLiteralVisitor
120-
: null
121-
const createSourceVisitor =
122-
"createVisitor" in rule
123-
? rule.createVisitor
124-
: "createSourceVisitor" in rule
125-
? rule.createSourceVisitor
126-
: null
127-
128167
return {
129-
...(createLiteralVisitor
130-
? {
131-
"Literal[regex]"(node: ESTree.RegExpLiteral) {
132-
verify(
133-
node,
134-
node.regex.pattern,
135-
node.regex.flags,
136-
createLiteralVisitor,
137-
)
138-
},
139-
}
140-
: null),
141-
...(createSourceVisitor
142-
? {
143-
Program() {
144-
const scope = context.getScope()
145-
const tracker = new ReferenceTracker(scope)
168+
"Program:exit": programExit,
169+
"Literal[regex]"(node: ESTree.RegExpLiteral) {
170+
verify(node.regex.pattern, node.regex.flags, function* () {
171+
for (const rule of rules) {
172+
if (rule.createLiteralVisitor) {
173+
yield rule.createLiteralVisitor(
174+
node,
175+
node.regex.pattern,
176+
node.regex.flags,
177+
)
178+
}
179+
}
180+
})
181+
},
182+
Program() {
183+
const scope = context.getScope()
184+
const tracker = new ReferenceTracker(scope)
146185

147-
// Iterate calls of RegExp.
148-
// E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
149-
// `const {RegExp: a} = window; new a()`, etc...
150-
for (const { node } of tracker.iterateGlobalReferences({
151-
RegExp: { [CALL]: true, [CONSTRUCT]: true },
152-
})) {
153-
const newOrCall = node as
154-
| ESTree.NewExpression
155-
| ESTree.CallExpression
156-
const [patternNode, flagsNode] = newOrCall.arguments
157-
if (
158-
!patternNode ||
159-
patternNode.type === "SpreadElement"
160-
) {
161-
continue
162-
}
163-
const pattern = getStringIfConstant(
164-
patternNode,
165-
scope,
166-
)
167-
const flags = getStringIfConstant(flagsNode, scope)
186+
// Iterate calls of RegExp.
187+
// E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
188+
// `const {RegExp: a} = window; new a()`, etc...
189+
for (const { node } of tracker.iterateGlobalReferences({
190+
RegExp: { [CALL]: true, [CONSTRUCT]: true },
191+
})) {
192+
const newOrCall = node as
193+
| ESTree.NewExpression
194+
| ESTree.CallExpression
195+
const [patternNode, flagsNode] = newOrCall.arguments
196+
if (!patternNode || patternNode.type === "SpreadElement") {
197+
continue
198+
}
199+
const pattern = getStringIfConstant(patternNode, scope)
200+
const flags = getStringIfConstant(flagsNode, scope)
168201

169-
if (typeof pattern === "string") {
170-
let verifyPatternNode = patternNode
171-
if (patternNode.type === "Identifier") {
172-
const variable = findVariable(
173-
context.getScope(),
174-
patternNode,
175-
)
176-
if (variable && variable.defs.length === 1) {
177-
const def = variable.defs[0]
178-
if (
179-
def.type === "Variable" &&
180-
def.parent.kind === "const" &&
181-
def.node.init &&
182-
def.node.init.type === "Literal"
183-
) {
184-
verifyPatternNode = def.node.init
185-
}
186-
}
187-
}
188-
verify(
189-
verifyPatternNode,
190-
pattern,
191-
flags || "",
192-
createSourceVisitor,
193-
)
194-
}
195-
}
196-
},
197-
}
198-
: null),
202+
if (typeof pattern === "string") {
203+
let verifyPatternNode = patternNode
204+
if (patternNode.type === "Identifier") {
205+
const variable = findVariable(
206+
context.getScope(),
207+
patternNode,
208+
)
209+
if (variable && variable.defs.length === 1) {
210+
const def = variable.defs[0]
211+
if (
212+
def.type === "Variable" &&
213+
def.parent.kind === "const" &&
214+
def.node.init &&
215+
def.node.init.type === "Literal"
216+
) {
217+
verifyPatternNode = def.node.init
218+
}
219+
}
220+
}
221+
verify(pattern, flags || "", function* () {
222+
for (const rule of rules) {
223+
if (rule.createSourceVisitor) {
224+
yield rule.createSourceVisitor(
225+
verifyPatternNode,
226+
pattern,
227+
flags || "",
228+
)
229+
}
230+
}
231+
})
232+
}
233+
}
234+
},
199235
}
200236
}
201237

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict"
2+
3+
module.exports = {
4+
root: true,
5+
extends: [
6+
// add more generic rulesets here, such as:
7+
// 'eslint:recommended',
8+
"plugin:regexp/recommended",
9+
],
10+
rules: {
11+
// override/add rules settings here, such as:
12+
// 'regexp/rule-name': 'error'
13+
},
14+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
var reg = /[aa]/
2+
var reg = /[a-zA-Z0-9_][0-9]/

tests/lib/eslint-plugin.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import path from "path"
2+
import assert from "assert"
3+
import { CLIEngine } from "eslint"
4+
import plugin from "../../lib/index"
5+
6+
// -----------------------------------------------------------------------------
7+
// Tests
8+
// -----------------------------------------------------------------------------
9+
10+
const TEST_CWD = path.join(__dirname, "../fixtures/integrations/eslint-plugin")
11+
12+
describe("Integration with eslint-plugin-regexp", () => {
13+
it("should lint without errors", () => {
14+
const engine = new CLIEngine({
15+
cwd: TEST_CWD,
16+
})
17+
engine.addPlugin("eslint-plugin-regexp", plugin)
18+
const r = engine.executeOnFiles(["test.js"])
19+
20+
assert.strictEqual(r.results.length, 1)
21+
assert.deepStrictEqual(
22+
r.results[0].messages.map((m) => m.ruleId),
23+
[
24+
"regexp/no-dupe-characters-character-class",
25+
"regexp/no-dupe-characters-character-class",
26+
"regexp/prefer-w",
27+
"regexp/prefer-d",
28+
"regexp/prefer-d",
29+
],
30+
)
31+
})
32+
})

0 commit comments

Comments
 (0)