从JavaScript文件中删除注释

3

我正在试图构建一个正则表达式,以删除JavaScript代码中的所有注释,包括单行注释(//...)和多行注释(/*..*/)

下面是我想出来的正则表达式:

/\"[^\"]*\"|'[^']*'|(\/\/.*$|\/\*[^\*]*\*\/)/mg

描述:如您所见,它还搜索字符串字面量。这是因为字符串字面量可能包含可以匹配注释模式的内容(例如:location.href = "http://www.domain.com";将作为单行注释匹配)。因此,我把字符串字面量模式放在备选模式的首位。接下来是两个模式,分别用于捕获单行注释和多行注释。这些都被包含在同一个捕获组中,以便我可以使用string.replace(pattern, "")来删除注释。

我已经使用几个js文件测试了表达式,并且似乎可以正常工作。我的问题是是否有其他模式需要考虑或者是否有其他事情需要考虑(例如是否存在对正则表达式的有限支持或需要考虑的某些浏览器的替代实现)。


“我正在尝试构建一个正则表达式,以从JavaScript代码中剥离所有注释。” 你不能这样做,这不是正则表达式可以单独解决的问题。你可以接近目标,但是一定会有情况出现错误,可能会导致破坏性结果(例如删除代码)。 - T.J. Crowder
1
你有任何可能出现的问题情况的例子吗?还有什么建议可以用来组合或替代删除注释。 - instantMartin
我猜T.J.的意思是可能由'\'(不结束字符串)、\\ (\\'结束字符串,\\\'不结束)、'..."...'(这里"不开始或结束字符串)以及所有'"\符号的组合引起的问题。因此,实际上,对于每一行,首先必须解析字符串字面量(或者在解析注释的同时解析字符串字面量),然后删除实际上不是字符串部分的注释。 - YakovL
具体涉及HTML中的注释(包括JS注释)。这可能会有所帮助:https://dev59.com/LW035IYBdhLWcg3wC7mf#64617472 - justFatLard
5个回答

2
使用C/C++样式的注释去除器。
下面的正则表达式可以完成以下任务:
  • 去除/**/和//两种风格的注释
  • 处理行连接风格
  • 保留格式

有两种形式的正则表达式可以进行格式保留:

  1. 水平制表符\h和换行符\n构造
  2. 空格和制表符[ \t]\r?\n构造

标志是多行全局
替换是捕获组2,$2\2

形式1:

 raw:  ((?:(?:^\h*)?(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/(?:\h*\n(?=\h*(?:\n|/\*|//)))?|//(?:[^\\]|\\\n?)*?(?:\n(?=\h*(?:\n|/\*|//))|(?=\n))))+)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|[\S\s][^/"'\\\s]*)
 delimited:  /((?:(?:^\h*)?(?:\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/(?:\h*\n(?=\h*(?:\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\\n?)*?(?:\n(?=\h*(?:\n|\/\*|\/\/))|(?=\n))))+)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|[\S\s][^\/"'\\\s]*)/mg     

表单 2:

 raw:   ((?:(?:^[ \t]*)?(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|/\*|//)))?|//(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|/\*|//))|(?=\r?\n))))+)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|(?:\r?\n|[\S\s])[^/"'\\\s]*)
 delimited:  /((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|(?:\r?\n|[\S\s])[^\/"'\\\s]*)/mg

这是表格2的扩展(使用此工具格式化)版本:

 (                                # (1 start), Comments 
        (?:
             (?: ^ [ \t]* )?                  # <- To preserve formatting
             (?:
                  /\*                              # Start /* .. */ comment
                  [^*]* \*+
                  (?: [^/*] [^*]* \*+ )*
                  /                                # End /* .. */ comment
                  (?:                              # <- To preserve formatting 
                       [ \t]* \r? \n                                      
                       (?=
                            [ \t]*                  
                            (?: \r? \n | /\* | // )
                       )
                  )?
               |  
                  //                               # Start // comment
                  (?:                              # Possible line-continuation
                       [^\\] 
                    |  \\ 
                       (?: \r? \n )?
                  )*?
                  (?:                              # End // comment
                       \r? \n                               
                       (?=                              # <- To preserve formatting
                            [ \t]*                          
                            (?: \r? \n | /\* | // )
                       )
                    |  (?= \r? \n )
                  )
             )
        )+                               # Grab multiple comment blocks if need be
   )                                # (1 end)

|                                 ## OR

   (                                # (2 start), Non - comments 
        "
        (?: \\ [\S\s] | [^"\\] )*        # Double quoted text
        "
     |  '
        (?: \\ [\S\s] | [^'\\] )*        # Single quoted text
        ' 
     |  (?: \r? \n | [\S\s] )            # Linebreak or Any other char
        [^/"'\\\s]*                      # Chars which doesn't start a comment, string, escape,
                                         # or line continuation (escape + newline)
   )                                # (2 end)

太好了,非常感谢。这涵盖了我所关心的所有内容 - 还有一些保留格式的条款。我有点担心运行时间,所以我可能会去掉保留格式的部分来加快速度(因为保留格式不是优先考虑的)。在运行此操作之前,我还可能使用更简单的表达式(例如我最初发布的表达式或者更简单的表达式)来搜索注释的存在(以便可以跳过没有注释的文件/部分)。您也肯定激发了我终于要得到一个正则表达式编辑器 :-) - instantMartin
@MartinÖstlund - 我认为保留格式结构不会影响性能,因为它只作用于注释。 - user557597
你说得完全正确,@sln ,不要减慢执行速度。我的错误——我误读了正则表达式。 - instantMartin
我认为我找到了一个(还有更多,我几乎确定)这个链接中存在问题的情况:https://gist.github.com/davidhq/1ca7112f589fb6791a317cd40310103e ... 有人可以确认一下吗? - davidhq
@davidhq - Re1 是表单#1的正则表达式,适用于支持水平空格的引擎,因此在JS中无效。那么为什么你还要在JS代码中使用它并要求人们测试它呢?Re2是适用于JS的正则表达式,是表单#2 /((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|(?:\r?\n|[\S\s])[^\/"'\\\s]*)/mg,你可以在这里进行测试 https://regex101.com/r/Crs1bX/1。 - user12097764
@davidhq - 认为这个正则表达式有问题和证明它有问题是不同的。它仍然是我见过的最完美的正则表达式之一。 - user12097764

2
import prettier from 'prettier';

function decomment(jsCodeStr) {
  const options = { printWidth: 160, singleQuote: true, trailingComma: 'none' };

  // actually strip comments:
  options.parser = (text, { babel }) => {
    const ast = babel(text);
    delete ast.comments;
    return ast;
  };

  return prettier.format(jsCodeStr, options);
}

https://github.com/prettier/prettier 获取更好的代码格式化。

1
我尝试了所有使用正则表达式的解决方案,看看是否有一些能够合理地工作...但是没有一个。这是剥离注释的唯一准确方法...或者使用AST(抽象语法树)的其他方法。 - davidhq
这是不正确的,因为该正则表达式在所有情况下都可以完美地工作。在C/C++中,像大多数其他语言一样,定界符是引号和注释,因此注释解析是次要的。如果您能找到这个人的正则表达式无法完美工作的情况,请告诉我。 - user12097764
你能否尝试运行这段代码,并告诉我这个正则表达式是否正确执行?https://gist.github.com/davidhq/1ca7112f589fb6791a317cd40310103e - davidhq
我无法尝试代码,因为我不是github的成员。但是,我可以告诉你那个示例中使用了错误的正则表达式。那个正则表达式是_Form #1_,它使用水平空白构造\h。JavaScript的正则表达式是_Form #2_ /((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|(?:\r?\n|[\S\s])[^\/"'\\\s]*)/mg,你可以在这里玩弄它 https://regex101.com/r/YHTVee/1`。 - user12097764
如果您不需要保留格式,则可以使用基本的正则表达式/(\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n|$))|("(?:\\[\S\s]|[^"\\])*"|'(?:\\[\S\s]|[^'\\])*'|[\S\s][^\/"'\\]*)/。如果是介于两者之间的情况,请告诉我,如果可以在5分钟内完成,我会尽力满足您的要求。如果时间更长,我会收费。 - user12097764
显示剩余3条评论

0

可以做到这一点(无正则表达式的纯JavaScript),但有一些限制。我为您实时实现了一些东西(25分钟)。使用的方法是逐行解析源文件。

如果您的js文件正确且没有3个异常,则结果是正确的。

在此处查找实现:http://jsfiddle.net/ch14em6w/

这是代码的关键部分:

//parse file input
function displayFileLineByLine(contents)
{
    var lines = contents.split('\n');
    var element = document.getElementById('file-content');
    var output = '';
    for(var line = 0; line < lines.length; line++){

        var normedline = stripOut(lines[line]);
        if (normedline.length > 0 )
        {
            output += normedline;
        }
    }
    element.innerHTML = output;  
}
// globa scope flag showing '/*' is open
var GlobalComentOpen = false;

//recursive line coments removal method
function stripOut(stringline, step){
        //index global coment start
        var igcS = stringline.indexOf('/*');
        //index global coment end
        var igcE = stringline.indexOf('*/');
        //index inline coment pos
        var iicP = stringline.indexOf('//');
        var gorecursive = false;
        if (igcS != -1)
        {
            gorecursive = true;
            if (igcS < igcE) { 
                stringline = stringline.replace(stringline.slice(igcS, igcE +2), "");
            }
            else if (igcS > igcE && GlobalComentOpen) {
                stringline = stringline.replace(stringline.slice(0, igcE +2), "");
                igcS = stringline.indexOf('/*');
                stringline = stringline.replace(stringline.slice(igcS, stringline.length), "");
            }
            else if (igcE == -1){
                GlobalComentOpen = true;
                stringline = stringline.replace(stringline.slice(igcS, stringline.length), "");
            }
            else
            {
                console.log('incorect format');
            }

        }
        if (!gorecursive && igcE != -1)
        {
            gorecursive = true;
            GlobalComentOpen = false;
            stringline = stringline.replace(stringline.slice(0, igcE +2), "");
        }
        if (!gorecursive && iicP != -1)
        {
            gorecursive = true;
            stringline = stringline.replace(stringline.slice(iicP, stringline.length), "");
        }
        if (!gorecursive && GlobalComentOpen && step == undefined)
        {
            return "";
        }
        if (gorecursive)
        {
            step = step == undefined ? 0 : step++;
            return stripOut(stringline, step);
        }
        return stringline;
}

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - instantMartin
你的问题让我有些措手不及,为什么我要这样实现,你说得对,唯一的问题是字符串字面量中存在注释。我知道如何实现这些异常,但在阅读了这里的所有内容后,我明白这不是重点。所以,你最初的问题是合理的,并且除了你所说的之外,还有多种情况可能出现注释,你的正则表达式/JavaScript 实现必须优先考虑注释包装器的出现:优先级1:''/",优先级2:"/",优先级3:"//"。 - SilentTremor

0
更新:这是C#代码,我认为这不是正确的地方。无论如何,下面是代码。

我使用以下类并获得了良好的结果。

未测试包含在字符串内的注释,例如:

a = "hi /* comment */ there";
a = "hi there // ";

该类能检测一行开头或至少以一个空格开始的注释。所以以下情况是有效的。
a = "hi// there";
a = "hi//there";

这是代码

    static public class CommentRemover
    {
        static readonly RegexOptions ROptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Multiline; 
 
        const string SSingleLineComments = @"\s//.*";       // comments with // in the beginning of a line or after a space
        const string SMultiLineComments = @"/\*[\s\S]*?\*/";
        const string SCommentPattern = SSingleLineComments + "|" + SMultiLineComments;  
        const string SEmptyLinePattern = @"^\s+$[\r\n]*";

        static Regex CommentRegex;
        static Regex EmptyLineRegex; 

        static public string RemoveEmptyLines(string Text)
        {
            if (EmptyLineRegex == null)
                EmptyLineRegex = new Regex(SEmptyLinePattern, ROptions);

            return EmptyLineRegex.Replace(Text, string.Empty); 
        }
        static public string RemoveComments(string Text)
        {
            if (CommentRegex == null)
                CommentRegex = new Regex(SCommentPattern, ROptions);
            return CommentRegex.Replace(Text, string.Empty);
        }
        static public string RemoveComments(string Text, string Pattern)
        {
            Regex R = new Regex(Pattern, ROptions);
            return R.Replace(Text, string.Empty);
        }
 
        static public string Execute(string Text)
        {
            Text = RemoveComments(Text);
            Text = RemoveEmptyLines(Text);
            return Text;
        }
        static public void ExecuteFile(string SourceFilePth, string DestFilePath)
        {
            string DestFolder = Path.GetDirectoryName(DestFilePath);
            Directory.CreateDirectory(DestFolder);

            string Text = File.ReadAllText(SourceFilePth);
            Text = Execute(Text);
            File.WriteAllText(DestFilePath, Text);
        }
        static public void ExecuteFolder(string FilePattern, string SourcePath, string DestPath, bool Recursive = true)
        {
            string[] FilePathList = Directory.GetFiles(SourcePath, FilePattern, Recursive? SearchOption.AllDirectories: SearchOption.TopDirectoryOnly);
            string FileName;
            string DestFilePath;
            foreach (string SourceFilePath in FilePathList)
            {
                FileName = Path.GetFileName(SourceFilePath);
                DestFilePath = Path.Combine(DestPath, FileName);
                ExecuteFile(SourceFilePath, DestFilePath);
            }
        }
        static public void ExecuteCommandLine(string[] Args)
        {

            void DisplayCommandLineHelp()
            {
                string Text = @"
-h, --help          Flag. Displays this message. E.g. -h
-s, --source        Source folder when the -p is present. Else source filename. E.g. -s C:\app\js or -s C:\app\js\main.js
-d, --dest          Dest folder when the -p is present. Else dest filename. E.g. -d C:\app\js\out or -d C:\app\js\out\main.js
-p, --pattern       The pattern to use when finding files. E.g. -p *.js
-r, --recursive     Flag. Search in sub-folders too. E.g. -r

EXAMPLE
    CommentStripper -s .\Source -d .\Dest -p *.js
";

                Console.WriteLine(Text.Trim());
            }

            string Pattern = null;
            
            string Source = null;
            string Dest = null;

            bool Recursive = false;
            bool Help = false;
 
            string Arg;
            if (Args.Length > 0)
            {
                try
                {
                    for (int i = 0; i < Args.Length; i++)
                    {
                        Arg = Args[i].ToLower();

                        switch (Arg)
                        {
                            case "-s":
                            case "--source":
                                Source = Args[i + 1].Trim();
                                break;
                            case "-d":
                            case "--dest":
                                Dest = Args[i + 1].Trim();
                                break;
                            case "-p":
                            case "--pattern":
                                Pattern = Args[i + 1].Trim();
                                break;
                            case "-r":
                            case "--recursive":
                                Recursive = true;
                                break;
                            case "-h":
                            case "--help":
                                Help = true;
                                break;
                        }

                    }


                    if (Help)
                    {
                        DisplayCommandLineHelp();                        
                    }
                    else
                    {
                        if (!string.IsNullOrWhiteSpace(Pattern))
                        {
                            ExecuteFolder(Pattern, Source, Dest, Recursive);
                        }
                        else
                        {
                            ExecuteFile(Source, Dest);
                        }
 
                    }

                    // Console.ReadLine();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.WriteLine();
                    DisplayCommandLineHelp();
                }
            }



        }
    }

祝你好运。


0

该链接指向一个HTML缩小器 - 我只想从JavaScript中删除注释。不过,感谢您的提示,因为缩小器可能是开始寻找的好地方。毕竟,JavaScript缩小器确实会去除注释。 - instantMartin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接