読者です 読者をやめる 読者になる 読者になる

若き JavaScripter の悩み

何かをdisっているときは、たいていツンデレですので大目に見てやってください。愛です。

escopeの仕様意訳

es**シリーズの仕様意訳第三回。前回はestraverseでした。今回はescope。

escope

escopeは、ASTを喰ってスコープを解析するためのモジュール。 estraverseと同じように、ASTをescope.analyzeに喰わせるだけで動く。 デモを見ると挙動がよく分かる。サンプルコードがモナドなあたりに玄人臭を感じますねw

使い方は前回、前々回同様、超カンタン。

escopeとASTを吐かせるためのesprimaをnpmで取ってくる。

npm install esprima
npm install escope

あとはこんな感じで実行。

var esprima = require('esprima');
var escope = require('escope');

var ast = esprima.parse('console.log("Hello, world!");');
var scopes = escope.analyze(ast).scopes;

console.log(scopes);

このシリーズのモジュールは本当に質が高くて惚れ惚れする。 もうちょっとドキュメントがあると嬉しいんだけれど…。

得られるスコープオブジェクトの仕様は

/**
 * スコープオブジェクト。
 * @constructor
 * @param {AstNode} block スコープを生成できるASTノード。FunctionExpression
 *   やWithStatementなどが該当する。
 * @param {Object} opt オプション(ユーザがインスタンス化させるわけじゃないので割愛)。
 */
function Scope(block, opt) {
  /**
   * このスコープの種類。catch、with、global、functionのいずれか。
   * @type {stirng}
   */
  this.type;

  /**
   * このスコープに含まれる変数のマップ。
   * @type {Map}
   */
  this.set;

  /**
   * taints=汚れ?(よくわからない)
   * @type {Map}
   */
  this.taints;

  /**
   * このスコープが動的かどうか(type が global か with ならば true)。
   * @type {boolean}
   */
  this.dynamic;

  /**
   * このスコープを生成しているASTノード。
   * @type {AstNode}
   */
  this.block;

  /**
   * このスコープ(内包したスコープ含む)に含まれる代入式のうち、このスコープで定義されていない変数の配列。
   * @type {Array.<Reference>}
   */
  this.through;

  /**
   * このスコープによって宣言された変数の配列。arguments を含む。
   * @type {Array.<Reference>}
   */
  this.variables;

  /**
   * このスコープでアクセスされた変数の配列。
   */
  this.references;

  /**
   * 内包する代入式の左辺が一時的に格納される。
   * @type {null|AstNode}
   */
  this.left;

  /**
   * このスコープ内で変数が定義されたとき、変数が登録されるスコープ。Withstatement などでは変数が登録されるスコープが、自身のスコープと一致しないので。
   */
  this.variableScope;

  /**
   * Function式によるスコープかどうか。
   * @type {boolean}
   */
  this.functionExpressionScope;

  /**
   * evalが呼ばれたときにこのスコープにアクセスできるかどうか。
   * @type {boolean}
   */
  this.directCallToEvalScope;

  /**
   * thisキーワードが使われたかどうか。
   * @type {boolean}
   */
  this.thisFound;

  /**
   * ひとつ上のスコープ。
   * @type {escope.Scope}
   */
  this.upper = currentScope;

  /**
   * このスコープが strict モードかどうか。
   * @type {boolean}
   */
  this.isStrict = isStrictScope(this, block);
}

escope.analyze の戻り値は escope.ScopeManagerインスタンスになっていて、スコープオブジェクトのリストはescope.ScopeManager#scopesから取得できる。

また、ASTそのものにスコープ情報を追加して欲しい場合は、escope.ScopeManager#attach() でスコープを生成しているASTノードに__$escope$__のプロパティ名でスコープオブジェクトが追加される(escope.ScopeManager#detach()で戻せる)。

また、関数宣言ノード/関数式ノードからスコープ情報が欲しい場合は、escope.ScopeManager#acquire(node)のようにしてやることで、スコープオブジェクトを得ることができる。

うーん、素晴らしいモジュールだ。