109 lines
3.1 KiB
JavaScript
109 lines
3.1 KiB
JavaScript
"use strict"
|
|
|
|
const { RegExpValidator } = require("@eslint-community/regexpp")
|
|
const { getRegExpCalls } = require("../utils")
|
|
const { getSourceCode } = require("eslint-compat-utils")
|
|
|
|
const allVisitorBuilder = new WeakMap()
|
|
|
|
/**
|
|
* @typedef {RegExpValidator.Options & {onExit:()=>void}} RuleValidator
|
|
*/
|
|
/**
|
|
* Define handlers to regexp nodes.
|
|
* @param {RuleContext} context The rule context.
|
|
* @param {(node: Node) => RuleValidator} visitorBuilder The regexp node visitor builder.
|
|
* @returns {Record<string, (node: ASTNode) => void>} The defined handlers.
|
|
*/
|
|
function defineRegExpHandler(context, visitorBuilder) {
|
|
const sourceCode = getSourceCode(context)
|
|
const programNode = sourceCode.ast
|
|
|
|
let handler = {}
|
|
let builders = allVisitorBuilder.get(programNode)
|
|
if (!builders) {
|
|
builders = []
|
|
allVisitorBuilder.set(programNode, builders)
|
|
handler = {
|
|
"Literal[regex]"(node) {
|
|
const { pattern, flags } = node.regex
|
|
visitRegExp(builders, node, pattern || "", flags || "")
|
|
},
|
|
|
|
"Program:exit"() {
|
|
allVisitorBuilder.delete(programNode)
|
|
|
|
const scope = sourceCode.getScope(programNode)
|
|
for (const { node, pattern, flags } of getRegExpCalls(scope)) {
|
|
visitRegExp(builders, node, pattern || "", flags || "")
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
builders.push(visitorBuilder)
|
|
|
|
return handler
|
|
}
|
|
|
|
module.exports = { defineRegExpHandler }
|
|
|
|
/**
|
|
* Visit a given regular expression.
|
|
* @param {((node: Node) => RuleValidator)[]} visitorBuilders The array of validator options builders.
|
|
* @param {Node} node The AST node to report.
|
|
* @param {string} pattern The pattern part of a RegExp.
|
|
* @param {string} flags The flags part of a RegExp.
|
|
* @returns {void}
|
|
*/
|
|
function visitRegExp(visitorBuilders, node, pattern, flags) {
|
|
try {
|
|
const visitors = visitorBuilders.map((r) => r(node, { pattern, flags }))
|
|
const composedVisitor = composeRegExpVisitors(visitors)
|
|
|
|
new RegExpValidator(composedVisitor).validatePattern(
|
|
pattern,
|
|
0,
|
|
pattern.length,
|
|
flags.includes("u"),
|
|
)
|
|
|
|
if (typeof composedVisitor.onExit === "function") {
|
|
composedVisitor.onExit()
|
|
}
|
|
} catch (error) {
|
|
//istanbul ignore else
|
|
if (error.message.startsWith("Invalid regular expression:")) {
|
|
return
|
|
}
|
|
//istanbul ignore next
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a single visitor handler that executes all the given visitors.
|
|
* @param {RuleValidator[]} visitors
|
|
* @returns {RuleValidator}
|
|
*/
|
|
function composeRegExpVisitors(visitors) {
|
|
const result = {}
|
|
|
|
for (const visitor of visitors) {
|
|
const entries = Object.entries(visitor)
|
|
|
|
for (const [key, fn] of entries) {
|
|
const orig = result[key]
|
|
if (orig) {
|
|
result[key] = (...args) => {
|
|
orig(...args)
|
|
fn(...args)
|
|
}
|
|
} else {
|
|
result[key] = fn
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|