使用acorn對JavaScript程式碼進行解析。

MaxCentaur發表於2017-04-15

使用acorn對JavaScript程式碼進行解析。
Acorn簡介

Acorn是一個小小的快速JavaScript解析器,完全用JavaScript編寫並且效能和效率比Esprima更勝一籌。解析思路是把程式碼解析成語法樹的形式。如下程式碼是一個簡單的例子。

c = 1 + 1;

通過Acorn解析結果如下:
這裡寫圖片描述

AST樹簡介

在電腦科學中,抽象語法樹(abstract syntax tree或者縮寫為AST),或者語法樹(syntax
tree),是原始碼的抽象語法結構的樹狀表現形式,這裡特指程式語言的原始碼。樹上的每個節點都表示原始碼中的一種結構。之所以說語法是“抽象”的,是因為這裡的語法並不會表示出真實語法中出現的每個細節。比如,巢狀括號被隱含在樹的結構中,並沒有以節點的形式呈現;而類似於if-condition-then這樣的條件跳轉語句,可以使用帶有兩個分支的節點來表示。
AST樹作為程式的一種中間表示形式,在程式分析等諸多領域有廣泛的應用.利用抽象語法樹可以方便地實現多種源程式處理工具,比如源程式瀏覽器、智慧編輯器、語言翻譯器等.

var AST = "is Tree";

這裡寫圖片描述

這裡的解析結果和Acorn的解析結果看起來很像,他們在形式上是相同的區別只是在於對不同資訊的儲存上或多或少,比如下程式碼

int a = 1;//給a賦值
int b = 2;

Acorn的處理邏輯會把註釋和第二句程式碼連結起來,而Esprima則是會註釋的資訊分別和兩句話都連結起來。那麼這也就在處理程式碼和註釋的對應上有一點誤差(後面會講到)。
解析過程和核心程式碼介紹
要使用Acorn進行解析那麼第一步就是安裝Acorn,通過npm install acorn 即可安裝。

我們的目的是提取JavaScript程式碼中的註釋和對應的程式碼,所以對應的操作就是
1. 如何解析出AST樹。
2. 如何遍歷AST樹拿出其中的註釋和找到註釋對應的程式碼。
3. 如何把註釋和註釋對應的程式碼存成對應的格式。

Acorn對程式碼的解析很簡單

var ast = acorn.parse(source, {
            sourceType: "script",
            locations: true,
            onComment: comments,
            onToken: tokens,
        });

官方給出這樣的解釋“*parse(input, options) is used to parse a JavaScript
program. The input parameter is a string, options can be undefined or
an object setting some of the options listed below. The return value
will be an abstract syntax tree object as specified by the ESTree
spec.*”通過以上操作我們便可以得到解析完成AST,第一個引數是String型別的文字,第二個引數是一個物件,是對AST樹的一些設定。下邊對上面用到的引數做一個收集

locations :When true, each node has a loc object attached with start and end subobjects, each of which contains the one-based line and zero-based column numbers in {line, column} form. Default is false.
onComment: If a function is passed for this option, whenever a comment is encountered the function will be called with the following parameters:

block: true if the comment is a block comment, false if it is a line comment.
text: The content of the comment.
start: Character offset of the start of the comment.
end: Character offset of the end of the comment.

每個註釋都會被按照下面的形式儲存起來
這裡寫圖片描述
當我們通過Acorn拿到了帶有完整註釋資訊的AST樹那麼就有對樹進行一個遍歷,取出對註釋和註釋對應的程式碼進行儲存。

//註釋說明1
int a = 2;
//註釋說明2
int b = 3;
//註釋說明3

經過對於AST樹中的節點來說 註釋一是程式碼一的leadingComments,註釋而是程式碼一的trailingComments,同樣程式碼二也有對應的leadingComments和trailingComments分別是註釋二和註釋三。但是也有一種特殊的情況如下

int a = 2; //註釋說明1
int b = 3;
//註釋說明2

註釋一和註釋二會被認為是程式碼二的leadingComments和trailingComments,而程式碼一沒有trailingComments。這樣的話就和我們的處理邏輯是不一樣的,我們需要然後上面例子中註釋一是程式碼一的trailingComments。所以就需要對整個樹進行一個遍歷,對節點資訊記性一個設定。
遍歷會用到estraverse這個庫。處理邏輯如下程式碼所示

estraverse.traverse(ast, {
    leave: function (node, parent) {
         if (!node.hasOwnProperty('trailingComments')) {
             var old_node = node;
             var old_line = node.loc.end.line;
             var old_column = node.loc.end.column;
             estraverse.traverse(parent, {
              leave: function (node, parent) {
              if (node !== old_node &&
              node.hasOwnProperty('leadingComments') &&
              node.leadingComments[0].loc.start.line === old_line &&
              node.leadingComments[0].loc.start.column >= old_column) {
              old_node.trailingComments = [];
              old_node.trailingComments[0] = node.leadingComments.shift();
              if (node.leadingComments.length === 0) {
                     delete node.leadingComments;
                     }
                     estraverse.VisitorOption.Break;
                  }
               }
          });
              node = old_node;
   }

通過如上操作便可對上文提到的特殊情況進行更正。
現在最後一個問題就是如何把得到的註釋和程式碼對應起來,對AST樹來說這裡操作就很簡單了,我們需要對AST在遍歷時候對每個節點的狀態進行判別。

var t = node.hasOwnProperty('trailingComments');
var l = node.hasOwnProperty('leadingComments');
if (t && l) {
    comment_code.comment = node.leadingComments.concat(node.trailingComments);
    comment_code.code.push(escodegen.generate(node));//將註釋和程式碼對應起來
    extract.push(comment_code);
    }
else if (l) {
comment_code.comment = node.leadingComments;
comment_code.code.push(escodegen.generate(node));
extract.push(comment_code);
    }
else if (t) {
    comment_code.comment = node.trailingComments;
    comment_code.code.push(escodegen.generate(node));
    extract.push(comment_code);
    }
comment_code = {comment: [], code: []};

把程式碼和註釋都存在陣列中之後,通過



var fs = require("fs");

fs.writeFileSync(filepaht, filecontent);

進行儲存。到此處理完畢。

相關文章