ASTを使用してJavaScriptソースコードを読む
序章
昔から残っている大きな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); } } )
非常に幅広い検索から、より小さなサンプルに移行しました。 これで、必要なデータを抽出できます。
パスからノード、プロパティへの移動
visitFunctionDeclaration
に渡されるpath
オブジェクトは、NodePath
です。 このオブジェクトは、親と子の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部分。 ノードに応じて、名前だけまたはより多くの情報を持つことができます