深入剖析vscode工具函数(八)解密复杂正则表达式

2023-10-31 19:17:29 浏览数 (1)

深入剖析vscode工具函数(八)解密复杂正则表达式

VSCode中的一段正则

正则表达式是程序员的有力武器,但对于复杂的正则表达式,很多人可能感到困惑。今天,我们来分析一段高级正则表达式,并探讨它的内涵与应用场景。

代码语言:javascript复制
const regexp = /("[^"\]*(?:\.[^"\]*)*")|('[^'\]*(?:\.[^'\]*)*')|(/*[^/*]*(?:(?:*|/)[^/*]*)*?*/)|(/{2,}.*?(?:(?:r?n)|$))|(,s*[}]])/g;

这个表达式非常长,我们可以先从大体上拆分一下它的结构:

代码语言:javascript复制
/(...)|(...)|(...)|(...)|(...)/

可以看到整个正则表达式就是有五个分组构成,我们分别对这五个分组进行详细拆解:

1. 匹配双引号内的字符串:**"[^"\]*(?:\.[^"\]*)*"**

  • **"**:匹配双引号;
  • **[^"\]***:匹配非双引号和非反斜杠的字符零次或多次;
  • **(?:\.[^"\]*)***:非捕获分组,匹配转义字符后的任意字符,以及随后的非双引号和非反斜杠的字符零次或多次;这个分组可以重复零次或多次;
  • **"**:匹配双引号。

这个正则的作用就是匹配一个双引号字符串,比如:

代码语言:javascript复制
"Hello World"

我们先不管它怎么实现,如果自己要实现怎么办?简单的思路就是:

代码语言:javascript复制
/"[^"]*"/

这个我相信大多数人都能写出来,无非就是匹配两个引号和中间的内容。但这里的问题就在于转义字符,比如我这个字符串是这样的:

代码语言:javascript复制
"Hello " World"

如果用上面的正则去匹配,在遇到 " 就终止了,实际上难点就是把这些转义字符也识别出来,不能让他们阻断整个表达式。

识别转义字符很简单,就是一个反斜杠加上任意一个字符:

代码语言:javascript复制
/\./

在转义字符之后还有任意的字符,甚至还能有转义字符,所以剩余的匹配部分就是:

代码语言:javascript复制
/[^\"]*/

两个加起来,我们就能匹配到后面那个字符串了:

代码语言:javascript复制
/\.[^\"]*/
这个匹配的是 `" World`

再把这个模式重复多次,就可以匹配到多个转义字符以及后面的字符了:

代码语言:javascript复制
/(\.[^\"]*)*/

最后,由于这个分组并不需要捕获,所以我们加上 ?: 来提高性能,其实这里最难的正则就出来了:

代码语言:javascript复制
/(?:\.[^\"]*)*/

2. 匹配单引号内的字符串:**'[^'\]*(?:\.[^'\]*)*'**

这个和1是一样的,只是双引号变成了单引号,就不赘述了。

3. 匹配块注释:**/*[^/*]*(?:(?:*|/)[^/*]*)*?*/**

  • **/***:匹配 **/***;
  • **[^/*]***:匹配非星号和非斜杠的字符零次或多次;
  • **(?:(?:*|/)[^/*]*)*?**:非捕获分组,匹配星号或斜杠后的非星号和非斜杠的字符零次或多次;这个分组可以重复零次或多次,但尽量少重复(懒惰匹配);
  • ***/**:匹配 */

同样我们可以想象一下自己要匹配块注释怎么写,很自然地想到:

代码语言:javascript复制
//*(.*?)*//

这个正则可以匹配一般的注释,但是无法匹配嵌套注释的情况。

因此,我们和前面一样,把关键词先排除:

代码语言:javascript复制
//*[^/*]**//

但这样会导致遇到 /* 就停止了,所以我们还得匹配 /*

代码语言:javascript复制
/((*|/)[^/*]*)*?/

其实这样就实现了匹配一个 / 一些其他字符,或者是 * 加上其他字符,并且是非贪婪匹配,这样能够匹配到最后的那个 */

把这几个加起来,再加上非捕获分组,就是这里的正则表达的含义了。

4. 匹配行注释:**/{2,}.*?(?:(?:r?n)|$)**

  • **/{2,}**:匹配两个或更多的斜杠;
  • **.*?**:匹配任意字符零次或多次,但尽量少重复(懒惰匹配);
  • (?:(?:r?n)|$):非捕获分组,匹配换行符(rn 或 **n**)或字符串末尾。

行注释就简单地多了,只需要匹配两个斜杠开头,然后一直匹配到换行符或者整个字符串的末尾就行。

5. 匹配尾部多余的逗号:**,s*[}]]**

  • **,**:匹配逗号;
  • **s***:匹配空白字符(空格、制表符、换行符等)零次或多次;
  • **[}]]**:匹配右大括号或右方括号。

这个相信大家都能看懂了。

正则的使用

代码语言:javascript复制
function stripComments(content) {
   return content.replace(regexp, function (match, _m1, _m2, m3, m4, m5) {
    // Only one of m1, m2, m3, m4, m5 matches
    if (m3) {
     // A block comment. Replace with nothing
     return '';
    } else if (m4) {
     // Since m4 is a single line comment is is at least of length 2 (e.g. //)
     // If it ends in r?n then keep it.
     const length = m4.length;
     if (m4[length - 1] === 'n') {
      return m4[length - 2] === 'r' ? 'rn' : 'n';
     }
     else {
      return '';
     }
    } else if (m5) {
     // Remove the trailing comma
     return match.substring(1);
    } else {
     // We match a string
     return match;
    }
   });
  }

VSCode利用这段正则移除给定内容中的注释。以下是 stripComments 函数的逐行解析:

  1. 使用 content.replace(regexp, ...) 方法查找并替换 content 中与正则表达式 regexp 匹配的内容。这个方法的第二个参数是一个回调函数,它根据匹配结果来决定替换内容。
  2. 回调函数接收 6 个参数:**match** 是整个匹配的字符串,**_m1**, _m2, m3, m4, m5 分别对应正则表达式中的捕获分组。这里,我们只关心 m3, m4, **m5**,因为它们分别代表块注释、单行注释和多余的逗号。
  3. 使用 if (m3) { ... } 判断是否匹配到了块注释。如果是,返回空字符串(**''**),即将块注释移除。
  4. 使用 else if (m4) { ... } 判断是否匹配到了单行注释。如果是,首先获取单行注释的长度。然后判断单行注释是否以换行符(**r?n)结尾。如果以换行符结尾,则保留换行符;否则返回空字符串(''**),即将单行注释移除。
  5. 使用 else if (m5) { ... } 判断是否匹配到了多余的逗号。如果是,返回匹配字符串去掉首字符的子字符串,即将多余的逗号移除。
  6. 如果都没有匹配到(即匹配到了字符串),则返回原始匹配字符串。

在VSCode中的应用

这个函数在VSCode中用来去除 json 中的注释,因为 json 本身是不支持注释的。

关于为什么 JSON 不支持注释,Douglas Crockford 在他的许多演讲和访谈中都谈到了这个问题。他的解释主要包括以下几点:

  1. 简单性:Crockford 认为 JSON 应该保持简单、紧凑和易于解析。引入注释会增加解析器的复杂性,从而违背了 JSON 设计的初衷。通过避免注释,JSON 可以确保跨平台和编程语言的可读性和可用性。
  2. 减少滥用:Crockford 观察到在 XML 中,注释经常被滥用,如用于配置文件中的条件处理等。他不希望同样的情况出现在 JSON 中,因此决定从一开始就不支持注释。
  3. 数据与元数据的分离:JSON 的设计目标是用于数据交换。允许注释可能导致数据与元数据之间的界限变得模糊,使解析和处理 JSON 数据变得困难。为了保持数据与元数据的分离,Crockford 决定不在 JSON 中支持注释。

如果一定要用注释,可以像VSCode这样提供一个去除注释的方法,也可以使用 JSON5

JSON5 的起源可以追溯到 2012 年,当时 JSON 已经成为许多 Web 开发者的首选数据交换格式。然而,尽管 JSON 的简洁性和跨平台兼容性使其在许多场景中非常实用,但其严格的语法规则使得在某些方面使用起来不够便捷。

为了解决这些问题,JSON5 的创建者 Michael Bolin 开发了一个基于 JSON 的扩展,旨在使 JSON 更容易阅读和编写。Michael Bolin 受到了 ECMAScript 5的启发,因此将其命名为 JSON5JSON5 的设计目标是继承原始 JSON 的优点,同时添加一些类似 JavaScript 对象字面量的功能,以提高灵活性和易用性。

JSON5 的主要特性包括支持注释、宽松的字符串引号规则、尾随逗号、更灵活的数字表示、未引用的属性名以及多行字符串。这些特性使得 JSON5 在阅读和编写方面更加友好,尤其适用于需要添加注释或使用更接近 JavaScript 语法的场景。

不过目前很多解析器都不支持 JSON5 ,为了保证更高效简洁的性能,多半还是采用 VSCode 这种minify的方式,在最后将注释剔除。

小结

本文介绍了VSCode中如何实现去除JSON注释。由于JSON本身不支持注释,因此需要使用正则表达式去除注释。VSCode使用了一个很复杂的正则表达式的多个分组,分别用于匹配双引号内的字符串、单引号内的字符串、块注释、单行注释以及尾部多余的逗号。在这里我们详细拆解分析了整个正则的细节和作用。

虽然JSON不支持注释,但是可以使用JSON5这种扩展格式来支持注释。不过目前很多解析器都不支持JSON5,因此在实际开发中,还是需要使用类似VSCode这种minify的方式来去除注释,以保证更高效简洁的性能。

0 人点赞