正则表达式是用于匹配字符串模式的规则工具,可实现文本校验、提取、替换和分割;其核心由普通字符与元字符(如^、$、.、d、[]、()等)构成,配合限定符(*、+、?、{n,m})和标志(i、g、u等)形成灵活匹配能力。

直接用 @babel/traverse 遍历 Literal 和 NewExpression 节点,结合正则字面量结构与构造函数调用模式,能准确识别所有 RegExp 定义;但仅靠 AST 无法判断「是否符合规范」,必须注入业务规则(如禁止 g 标志、要求显式 u、禁用动态字符串拼接),否则重写等于盲改。
怎么识别 RegExp 字面量和 new RegExp() 调用
正则在 AST 中分两类:字面量是 Literal 节点且 node.regex 存在;构造调用是 NewExpression 节点且 callee.name === 'RegExp'。不能只查 Identifier 名为 RegExp,那会漏掉别名场景(const RE = RegExp; new RE('a'))。
-
Literal类型的正则,检查node.regex?.flags和node.regex?.pattern即可拿到原始内容 -
NewExpression必须递归解析arguments[0]和arguments[1]:前者可能是StringLiteral或TemplateLiteral,后者通常是StringLiteral或Identifier(如变量传入标志) - 跳过
eval()、Function()内部的字符串,AST 层面无法安全还原其运行时是否构成正则——这类必须人工标注或告警,不自动重写
为什么不能统一替换为 /.../u 形式
字面量和构造函数语义不同:/d+/g 是编译期常量,而 new RegExp(pattern, flags) 支持运行时拼接。强行把后者转成字面量会破坏逻辑,比如 new RegExp(`^${userInput}$`, 'i') 一旦转成 /^${userInput}$/i 就变成静态字符串,失去动态能力。
- 仅当
arguments[0]是纯StringLiteral且arguments[1]是纯StringLiteral或缺失时,才允许转字面量 - 若
arguments[0]含TemplateLiteral或BinaryExpression(如'a' + 'b'),保留new RegExp()结构,只修正 flags 参数 - 字面量补
u标志相对安全,但new RegExp(..., 'g')补u必须确保第一个参数字符串本身合法(如含 Unicode 字符),否则运行时报错
重写时最容易踩的坑
正则重写不是改字符串那么简单,标志位变更、转义处理、作用域污染都可能引发静默故障。
- 字面量中反斜杠需双写:AST 的
node.regex.pattern是已解析后的字符串(\d在源码里是\\d),直接拼进新字面量会少一层转义,要用JSON.stringify()包裹再切头尾引号 - 修改 flags 时,不能简单字符串拼接(如
flags + 'u'),要先去重:用new Set(flags.split(''))再 join,避免生成guu - 全局作用域下
var re = /a/g被重写后,若原代码依赖re.lastIndex的可变状态,改成const re = /a/gu不影响行为;但若被闭包捕获并反复exec(),需确认u是否改变匹配边界(如 w 在 Unicode 模式下包含更多字符) - TS 项目中若正则赋值给带类型注解的变量(
const re: RegExp = /a/;),重写后类型仍有效;但若注解为更具体的typeof /a/,字面量变更可能导致类型不兼容
如何验证重写结果不破坏运行时行为
AST 替换完必须过三层检查,缺一不可:语法层看是否生成合法代码,执行层看是否报错,逻辑层看匹配结果是否一致。
- 语法验证:跑一遍
eslint --no-ignore --ext .js,.ts src/,重点看no-invalid-regexp和no-misleading-character-class是否新增报错 - 执行验证:对每个被修改的正则,提取其 pattern 和 flags,在 Node.js 里执行
new RegExp(pattern, flags).test('sample'),并与原正则结果比对;特别关注、w、Unicode 字符等敏感点 - 逻辑验证:若有单元测试覆盖正则使用场景(如路由匹配、表单校验),必须全量回归;没有的话,至少抽样 5 个高频正则,手动构造边界输入(空字符串、null、超长文本、特殊 Unicode)验证
真正难的不是找到正则节点,而是判断「这个正则在它所处的上下文中,改了之后会不会让某个 if 分支永远进不去、让某个 replace 替换错位、或者让第三方库内部的正则预编译失败」——这些只能靠执行验证兜底,AST 本身给不了答案。










