序章

昔から残っている大きなJavaScriptファイルがあるとしましょう。 70,000行の長さで、webpackまたはコンソートを使用して必死に分割する必要があります。 次に、グローバルスコープに公開する関数または定数を知る必要があります。

コンピューターにコードを読み取らせ、そこから必要なものを抽出します。

これは、抽象構文木(AST)の仕事です。

次の例は小さいです。 私たちの使命は、あなたがそれを受け入れることを選択した場合、グローバルスコープで公開されているすべての関数の名前を抽出することです。

test.js
// test the code
function decrementAndAdd(a, b) {
   function add(c, d) {
      return c + d;
   }
   a--;
   b = b - 1;
   return add(a,b)
}

// test the code
function incrementAndMultiply(a, b) {
    function multiply(c, d) {
      return c * d;
    }
    a++;
    b = b + 1;
    return multiply(a, b)
}

結果は次のようになります ["decrementAndAdd", "incrementAndMultiply"].

コードの解析

ASTは、コードを解析した結果です。 JavaScriptの場合、ASTはソースのツリー表現を含むJavaScriptオブジェクトです。 使用する前に、作成する必要があります。 解析するコードに応じて、適切なパーサーを選択します。

ここで、コードはES5互換であるため、 acorn パーサー。

最もよく知られているオープンソースECMAScriptパーサーのいくつかを次に示します。

パーサー サポートされている言語 GitHub
どんぐり esnext&JSX(acorn-jsxを使用) https://github.com/acornjs/acorn
エスプリマ esnext&older https://github.com/jquery/esprima
cherow esnext&older https://github.com/cherow/cherow
espree esnext&older https://github.com/eslint/espree
シフト esnext&older https://github.com/shapesecurity/shift-parser-js
バベル esnext、JSX、typescript https://github.com/babel/babel
TypeScript esnext&typescript https://github.com/Microsoft/TypeScript

すべてのパーサーは同じように機能します。 それにいくつかのコードを与えて、 AST.

const { Parser } = require('acorn')

const ast = Parser.parse(readFileSync(fileName).toString())

TypeScriptパーサーの構文は少し異なります。 しかし、それはここに文書化されています

これはで得られた木です @babel/parser 解析:

test-2.js
// test the code
function decrementAndAdd(a, b) {
  return add(a, b)
}

トラバース

抽出しようとしているものを見つけるために、AST全体を一度に処理しない方がよい場合がよくあります。 小さなコードスニペットの場合でも、数千のノードを持つ大きなオブジェクトになります。 したがって、必要な情報を抽出する前に、検索を絞り込みます。

そのための最善の方法は、気になるトークンのみをフィルタリングすることです。

繰り返しになりますが、このトラバース部分を実行するための多くのツールが利用可能です。 この例では、recastを使用します。 これは非常に高速で、コードのバージョンを変更しないという利点があります。 このようにして、元のフォーマットで必要なコードの部分を返すことができます。

トラバースしている間、私たちはすべてを見つけるでしょう function トークン。 これが私たちが使用する理由です visitFunctionDeclaration 方法。

変数の割り当てを確認したい場合は、 visitAssignmentExpression.

recast-acorn-example.js
const recast = require('recast');
const { Parser } = require('acorn');

const ast = Parser.parse(readFileSync(fileName).toString());

recast.visit(
  ast,
  {
    visitFunctionDeclaration: (path) => {
      // the navigation code here...

      // return false to stop at this depth
      return false;
    }
  }
)

ASTノードタイプ

通常、トークンタイプの名前は明確ではありません。 ast-explorer を使用して、調査したタイプを検索できます。 左側のパネルにコードを貼り付け、使用しているパーサーを選択して、「voilà!」と入力するだけです。 右側の解析されたコードを参照して、何を見つけますか Node Type あなたが探しています。

浅いまたは深い

ツリーのすべてのレベルを常に見たいとは限りません。 詳細な検索を実行したい場合もあれば、最上位のレイヤーを確認したい場合もあります。 フレームワークによって、構文は異なります。 幸いなことに、それは通常十分に文書化されています。

recast、現在の深さで検索を停止したい場合は、 return false 終わったら。 これは私たちが以前にしたことです。 トラバース(深く)したい場合は、 this.traverse(path) 以下に示すように。

@babel/traverse 言う必要はありません babel 続行する場所。 どこで停止するかを指定するだけで済みます return false 声明。

recast-acorn-example.js
recast.visit(
  ast,
  {
    visitFunctionDeclaration: (path) => {
      // deal with the path here...

      // run the visit on every child node
      this.traverse(path);
    }
  }
)

非常に幅広い検索から、より小さなサンプルに移行しました。 これで、必要なデータを抽出できます。

The path に渡されたオブジェクト visitFunctionDeclarationNodePath. このオブジェクトは、親と子のASTノード間の接続を表します。 これ path 関数宣言と関数の本体の間のリンクを表すため、それ自体は役に立ちません。

使用する ast-explorer、探しているパスの内容を見つけます。

行うべき古典的なこと: path.node. 親子関係の子ノードを取得します。 関数を検索することを選択した場合、ノードは path.node タイプになります Function:

recast-acorn-example.js
const functionNames = [];

recast.visit(
  ast,
  {
    visitFunctionDeclaration: (path) => {
      console.log(path.node.type); // will print "FunctionDeclaration"
      functionNames.push(path.node.id.name); // will add the name of the function to the array

      // return false to avoid looking inside of the functions body
      // we stop our search at this level
      return false;
    }
  }
)

サブツリーを確認するために、トラバース関数を相互にラップしてみてください。 以下のコードは、正確に2番目のレベルにあるすべての関数を返します。 関数内の関数内の関数を認識しません。

recast-acorn-example.js
const functionNames = [];

recast.visit(
  ast,
  {
    visitFunctionDeclaration: (path) => {
      let newPath = path.get('body');

      // subtraversing
      recast.visit(
        newPath,
        {
          visitFunctionDeclaration: (path) => {
            functionNames.push(path.node.id.name);
            return false;
          }
        }
      )

      // return false to not look at other functions contained in this function
      // leave this role to the sub-traversing
      return false;
    }
  }
)

任務完了!!

プログラムですべての関数名を見つけました。 引数または公開された変数の名前を簡単に見つけることができます。

用語集

ASTノードツリー内の1つのオブジェクト。 例:関数宣言、変数代入、オブジェクト式

ツリー内の親ノードと子ノード間のNodePathリンク

ノードの定義のNodeProperty部分。 ノードに応じて、名前だけまたはより多くの情報を持つことができます