String.prototype.replace 与隐藏的“$”

tl;dr: replace 的第二个参数应当使用字面量或函数不应使用带变量的字符串表达式否则可能发生意想不到的错误替换

String.prototype.replace 的第二个参数

(reference: String.prototype.replace() - JavaScript | MDN)

replace(pattern, replacement);

第二个参数 replacement 可以是字符串或函数如果是函数则由匹配信息作为参数计算出 replacement如果是字符串则可以使用一些 special replacement pattern

Pattern Inserts
$$ Inserts a "$".
$& Inserts the matched substring.
$` Inserts the portion of the string that precedes the matched substring.
$' Inserts the portion of the string that follows the matched substring.
$n Inserts the nth (1-indexed) capturing group where n is a positive integer less than 100.
$<Name> Inserts the named capturing group where Name is the group name.

使用变量作为 replacement 带来的问题

我实际遇到的问题是 iles#224由于使用了模板字符串作为 replacement在变量中包含上面这些 special replacement pattern 时就会错误地替换

解决方法也很简单将含变量的表达式改成函数前面加上 () => 就可以了regex - javascript - Better Way to Escape Dollar Signs in the String Used By String.prototype.replace - Stack Overflow

因为解决的代价非常小虽然有的时候根据代码逻辑可以推断出 replacement 不含 $依然可以认为凡是 replacement 需要用到变量的都应当替换成函数

使用 ESLint 检测这一问题

写了个 no-restricted-syntax 的配置

{
  rules: {
    'no-restricted-syntax': [
      'error',
      {
        selector: "CallExpression[callee.property.name='replace'] > .arguments:nth-child(2):not(Literal):not(ArrowFunctionExpression):not(FunctionExpression)",
        message: 'Only literals and functions are permitted as the 2nd argument of String.prototype.replace. Use a function that returns the expression instead.',
      },
    ],
  },
}

因为只是分析 AST有很多情况会误报例如 replacement 是一个函数名但实际代码应该很少出现这样的情况真遇到了的话再套一层函数就 ok 了实在不行还能用注释 disable 掉 lint

没研究过不知道写 ESLint plugin 能不能更加准确地检测但是差不多得了