AST-web端javascript逆向殺器之常用API


來了,前面一篇文章的相關api,常用的使用方法和參數來了。

 

本文轉自:點我

 

@babel/parse

解析函數

babelParser.parse(code, [options])

將提供的代碼作為一個完整的ECMAScript程序進行解析

babelParser.parseExpression(code, [options])

用於解析單個Expression,性能比parse()要高

options 函數參數

  • allowImportExportEverywhere
    默認情況下,import 和 export 聲明語句只能出現在程序的最頂層
    把這個設置為true,可以使得語句在任何地方都可以聲明

  • allowAwaitOutsideFunction
    默認情況下,僅在 異步函數內部 或 啟用topLevelAwait插件時 在模塊的頂層內允許使用await
    把這個設置為true,可以使得語句在任何地方都可以聲明

  • allowReturnOutsideFunction
    默認情況下,如果在頂層中使用return語句會引起錯誤
    把這個設置為true,就不會報錯

  • allowSuperOutsideMethod
    默認情況下,在類和對象方法之外不允許使用super
    把這個設置為true就可以聲明

  • allowUndeclaredExports
    默認情況下,export一個在當前作用域下未聲明的內容會報錯
    把這個設置為true就可以防止解析器過早地拋出未聲明的錯誤

  • createParenthesizedExpressions
    默認情況下,parser會在expression節點設置extra.parenthesized
    把這個設置為true,則會設置ParenthesizedExpressionAST節點

  • errorRecovery
    默認情況下,如果Babel發現一些 不正常的代碼 就會拋出錯誤
    把這個設置為true,則會在保存解析錯誤的同時繼續解析代碼,錯誤的記錄將被保存在 最終生成的AST的errors屬性中
    注意,那些嚴重的錯誤依然會終止解析

  • plugins
    記錄希望啟動的插件的數組

  • sourceType
    代碼的解析方式,你可以填入"script"(默認),"module" 或 "unambiguous"
    如果設置為”unambiguous”,那么系統會根據ES6語法中的importsexport來判斷是"module"還是"script"

  • sourceFilename
    將輸出的AST節點與其源文件名相關聯
    在你處理多個文件時,這個功能會很有用

  • startLine
    默認情況下,第一行代碼就是line 1。你可以傳入一個數字,作為起始行數
    這個功能在你整合其他插件的時候會很有用

  • strictMode
    默認情況下,只有在聲明了”use strict”條件下,ECMAScript代碼才會被嚴格解析
    將此選項設置為true則始終以嚴格模式解析文件

  • ranges
    添加ranges屬性到每一個節點中

    ranges: [node.start, node.end]

  • tokens
    將所有已經解析的tokens保存到File節點的tokens屬性中

輸出 Output

Babel parser是根據 Babel AST format 創建AST的
Babel AST format是基於 ESTree 規范 建立的

ESTree 代碼生成對應節點文檔
Babel parser 代碼生成對應節點文檔

Babel parserESTree的不同之處

@babel/generator

官方文檔:https://babeljs.io/docs/en/babel-generator

generate(ast, options, code);

函數用於根據ast生成代碼,可以傳入一些參數

options 參數

name 參數名 type 類型 default 默認值 description 描述
auxiliaryCommentAfter string   Optional 在輸出文件內容末尾添加的注釋塊文字
auxiliaryCommentBefore string   Optional 在輸出文件內容頭部添加的注釋塊文字
comments boolean true 輸出內容是否包含注釋
compact boolean or 'auto' opts.minified 是否不添加空格來讓代碼看起來緊密
concise boolean false 是否減少空格來讓代碼看起來緊湊一些
只是減少空格,而不是不添加
decoratorsBeforeExport boolean   是否在導出之前print一下裝飾器
filename string   Used in warning messages
jsescOption object   Use jsesc to process literals. jsesc is applied to numbers only if jsescOption.numbers (added in v7.9.0) is present. You can customize jsesc by passing options to it.
jsonCompatibleStrings boolean false Set to true to run jsesc with “json”: true to print “\u00A9” vs. “©”;
minified boolean false Should the output be minified
是否壓縮代碼
retainFunctionParens boolean false Retain parens around function expressions (could be used to change engine parsing behavior)
retainLines boolean false 嘗試在輸出代碼中使用與源代碼中相同的行號(用於追蹤堆棧)
shouldPrintComment function opts.comments Function that takes a comment (as a string) and returns true if the comment should be included in the output.
By default, comments are included if opts.comments is true or if opts.minified is false and the comment contains @preserve or @license

@babel/traverse

index

 

NodePath基礎屬性

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f(){
    var b = 123;
    a = ['a', 'b'];
}`;

const visitor = {
    BlockStatement(path)
    {
        console.log('當前路徑 源碼:\n', path.toString());
        console.log('當前路徑 節點:\n', path.node)
        console.log('當前路徑 父級節點:\n', path.parent);
        console.log('當前路徑 父級路徑:\n', path.parentPath)
        console.log('當前路徑 類型:\n', path.type)

        console.log('當前路徑 contexts:\n', path.contexts);
        console.log('當前路徑 hub:\n', path.hub);
        console.log('當前路徑 state:\n', path.state);
        console.log('當前路徑 opts:\n', path.opts)
        console.log('當前路徑 skipKeys:\n', path.skipKeys)
        console.log('當前路徑 container:\n', path.container)
        console.log('當前路徑 key:\n', path.key)
        console.log('當前路徑 scope:\n', path.scope)
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  

你會發現其中有不少值都是沒有定義的,這是因為很多值都是懶加載的
而且會給與專門的方法進行獲取,並不是這樣直接獲取的

ancestry

父級/祖先相關

 

NodePath.findParent(callback)

@return NodePath | None
逐級遞歸尋找父級節點的Path,並將Path作為參數傳入的判斷函數進行判斷
當判斷函數返回true, 則Path.findParent(callback)返回對應Path
當判斷函數返回false, 則遞歸繼續尋找父級, 進行判斷。若已無父級,則返回null

例: 尋找當前Path的父級函數節點

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    AssignmentExpression(path){
        console.log('當前路徑源碼:\n', path.toString());
        
        // 尋找父級
        function to_parent_function_path(x){  // 進行判斷是否是函數聲明節點的判斷函數
            if(x.isFunctionDeclaration()){return true}else{return false}
        }
        //      將判斷函數傳入,進行遞歸尋找父級path
        the_path = path.findParent(to_parent_function_path)
        console.log('to_parent_function_path 最終路徑源碼:\n', the_path.toString())

        //      遞歸后如果沒有發現符合要求的父級
        function to_null(x){return false}
        the_path = path.findParent(to_null)
        console.log('to_null 最終路徑:\n', the_path)
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  

得到的輸出結果

 

 

當前路徑源碼:
 a = b + 1
to_parent_function_path 最終路徑源碼:
 function f() {
  var b = 123;
  a = b + 1;
}
to_null 最終路徑:
 null

  

 

 

NodePath.find(callback)

@return NodePath | None
此函數與 Path.findParent 基本相同, 但這個判斷包含對 當前Path 的判斷
它會先對 當前Path 進行一次判斷. 如果自身符合條件,那就返回 當前Path,然后才遞歸調用父級進行判斷

例子:當前或父級Path

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    AssignmentExpression(path){
        console.log('當前路徑源碼:\n', path.toString());
        
        function to_path(x){  
            if(x.isAssignmentExpression()){return true}else{return false}
        }
        the_path = path.find(to_path)
        console.log('to_path最終路徑源碼:\n', the_path.toString())

        // 尋找父級
        function to_parent_function_path(x){  // 進行判斷是否是函數聲明節點的判斷函數
            if(x.isFunctionDeclaration()){return true}else{return false}
        }
        the_path = path.find(to_parent_function_path)
        console.log('to_parent_function_path最終路徑源碼:\n', the_path.toString())
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  得到的輸出結果

 

當前路徑源碼:
 a = b + 1
to_path最終路徑源碼:
 a = b + 1
to_parent_function_path最終路徑源碼:
 function f() {
  var b = 123;
  a = b + 1;
}

  

NodePath.getFunctionParent()

@return NodePath | None
得到當前節點的第一個 父級/祖先 函數聲明節點的Path

此方法通過調用 Path.findParent(callback) 傳入內置的判斷函數,來得到對應的結果

例: 尋找 父級/祖先 函數聲明節點的Path

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    AssignmentExpression(path){
        console.log('當前路徑源碼:\n', path.toString());

        the_path = path.getFunctionParent()
        console.log('最終路徑源碼:\n', the_path.toString())
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  

得到對應結果

當前路徑源碼:
 a = b + 1
最終路徑源碼:
 function f() {
  var b = 123;
  a = b + 1;
}

  

NodePath.getStatementParent()

@return NodePath
返回第一個 父級/祖先 聲明節點的Path
聲明節點所包含的節點類型見:Github文檔
若找不到目標,會報錯

例:返回第一個 父級/祖先 聲明節點的 Path

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f2(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    BinaryExpression(path){
        console.log('當前路徑源碼:\n', path.toString());
        the_path = path.getStatementParent()
        console.log('最終路徑源碼:\n', the_path.toString())
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  得到的結果

當前路徑源碼:
 b + 1
最終路徑源碼:
 a = b + 1;

  

NodePath.getAncestry()

@return Array
返回所有 父級/祖先 的Path

例:得到當前Path的所有 父級/祖先 的Path

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f2(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    AssignmentExpression(path){
        console.log('當前路徑源碼:\n', path.toString());
        the_paths = path.getAncestry()

        console.log('返回類型:\n', the_paths instanceof Array)
        console.log('結果路徑源碼:\n', the_paths.join('\n\n'))
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  

 

結果:

當前路徑源碼:
 a = b + 1
返回類型:
 true
結果路徑源碼:
 a = b + 1

a = b + 1;

{
  var b = 123;
  a = b + 1;
}

function f2() {
  var b = 123;
  a = b + 1;
}

function f2() {
  var b = 123;
  a = b + 1;
}

  

NodePath.isDescendant(path)

@return bool
判斷當前 Path 是否是指定 Path 的后代

此方法通過調用 Path.findParent() 來進行判斷,得到結果

例:輩分判斷

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f2(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    AssignmentExpression(path){
        console.log('當前路徑源碼:\n', path.toString());
        console.log('兒子是爸爸的后代:', path.isDescendant(path.parentPath))
        console.log('兒子是爺爺的后代:', path.isDescendant(path.parentPath.parentPath))
        console.log('兒子是孫子的后代:', path.isDescendant(path.get('left')))
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  結果:

當前路徑源碼:
 a = b + 1
兒子是爸爸的后代: true
兒子是爺爺的后代: true
兒子是孫子的后代: false

  

NodePath.isAncestor(path)

@return bool
判斷當前 Path 是否是指定 Path 的后代

此方法是調用 傳入的path的Path.isDescendant() 來進行判斷的

例:判斷是否是后代

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f2(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    AssignmentExpression(path){
        console.log('當前路徑源碼:\n', path.toString());
        console.log('兒子是爸爸的祖先:', path.isAncestor(path.parentPath))
        console.log('兒子是爺爺的祖先:', path.isAncestor(path.parentPath.parentPath))
        console.log('兒子是孫子的祖先:', path.isAncestor(path.get('left')))
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  結果:

當前路徑源碼:
 a = b + 1
兒子是爸爸的祖先: false
兒子是爺爺的祖先: false
兒子是孫子的祖先: true

  

NodePath.inType(**NodeType_str)

@return bool
判斷當前Path對應節點,或其 父/祖先 節點 是否包含特定類型的節點
可以一次性傳入多個類型,只要有一個符合就會返回 true, 否則返回 false

例: 是否包含特定類型的節點

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f2(){
    var b = 123;
    a = b + 1;
}`;

const visitor = {
    AssignmentExpression(path){
        console.log('當前路徑源碼:\n', path.toString());
        _is = path.inType('FunctionDeclaration')
        console.log('父級或自身包含函數聲明節點:', _is);
        _is = path.inType('WithStatement', 'DebuggerStatement')
        console.log('父級或自身包含 with 或 debugger:', path.inType(_is));
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  結果:

當前路徑源碼:
 a = b + 1
父級或自身包含函數聲明節點: true
父級或自身包含 with 或 debugger: false

  

NodePath.getDeepestCommonAncestorFrom(paths, filter)

@return NodePath | 自定義
獲取傳入的Path對應節點的 最大深度共同祖先節點的Path

  • 當 paths不存在length屬性時,報錯
  • 當 paths 長度為0時,返回 null
  • 當 paths 長度為1時,返回唯一的Path
  • 當 paths 大於1如果並不存在共同的祖先節點,報錯
    • 計算 最大深度共同祖先節點 的Path並返回
    • 當傳入一個filter函數,那么返回結果會作為參數進行回調。返回結果變為filter(最大深度共同祖先節點Path:NodePath, 深度:int, 所有path的祖先信息:list);

例:最大深度的共同祖先節點

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f(){
    function f3(){
        function f1(){return 1;}
        function f2(){return 2;}
        return 3;
    }
}`;


let paths = []

const visitor = {
    ReturnStatement(path){
        console.log('路徑源碼:\n', path.toString());
        paths.push(path)
        if (paths.length > 1){
            _is = path.getDeepestCommonAncestorFrom(paths)
            console.log('最大深度的共同祖先節點 源代碼:', _is.toString());
        }
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  

結果:

路徑源碼:
 return 1;
路徑源碼:
 return 2;
最大深度的共同祖先節點 源代碼: {
  function f1() {
    return 1;
  }

  function f2() {
    return 2;
  }

  return 3;
}
路徑源碼:
 return 3;
最大深度的共同祖先節點 源代碼: {
  function f1() {
    return 1;
  }

  function f2() {
    return 2;
  }

  return 3;
}

  

NodePath.getEarliestCommonAncestorFrom(paths)

@return NodePath
獲取paths中最早出現的共同祖先
方法會遍歷計算,共同祖先一旦出現, 則返回,不再繼續計算所有的path

此方法是調用 getDeepestCommonAncestorFrom(paths, filter) 方法,傳入固定的filter函數來實現

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

var jscode = `
function f(){
    function f3(){
        function f1(){return 1;}
        function f2(){return 2;}
        return 3;
    }
}`;


let paths = []

const visitor = {
    ReturnStatement(path){
        console.log('路徑源碼:\n', path.toString());
        paths.push(path)
        if (paths.length > 1){
            _is = path.getEarliestCommonAncestorFrom(paths)
            console.log('最早的共同祖先節點 源代碼:', _is.toString());
        }
    }
}

let ast = parser.parse(jscode);
traverse(ast, visitor);

  

family

主要用於獲取同級/前后 NodePath

 

NodePath.getSibling(key)

@return NodePath

通過父級,獲取同級節點的 NodePath 或 其它內容

  • 如果傳入數字,則嘗試獲取 同級節點 指定位置的 NodePath

  • 如果傳入數字,則嘗試獲取 父級節點 指定位置的 NodePath

  • 也可以傳入一些特殊的key, 獲取一些特殊的內容。

    可以使用 NodePath.listKey屬性 查看可以獲取的key

例: 尋找其它內容

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
function x(){
  console.log('code 1');
  console.log('code 2');
  var a = 1;
  console.log('code 3');
  console.log('code 4');
}
`;

const ast = parser.parse(jscode);
const visitor = {
  VariableDeclaration(path) {
    console.log('當前節點源碼:\n', path.toString());
    console.log('---------------------------------------------');
    console.log('第1個兄弟的源碼', path.getSibling(0).toString());
    console.log('第2個兄弟的源碼', path.getSibling(1).toString());
    console.log('第3個兄弟的源碼', path.getSibling(2).toString());
    console.log('第4個兄弟的源碼', path.getSibling(3).toString());
    console.log('第5個兄弟的源碼', path.getSibling(4).toString());
    console.log(path.listKey)
    console.log('---------------------------------------------');
  }
}
traverse(ast, visitor);

  結果:

當前節點源碼:
 var a = 1;
---------------------------------------------
第1個兄弟的源碼 console.log('code 1');
第2個兄弟的源碼 console.log('code 2');
第3個兄弟的源碼 var a = 1;
第4個兄弟的源碼 console.log('code 3');
第5個兄弟的源碼 console.log('code 4');
body
---------------------------------------------

  

NodePath.getOpposite()

@return Node

獲取相對的對位節點 (left 與 right)

此函數通過調用 NodePath.getSibling(key) , 傳入 當前節點的 left 或 right key實現

例:獲取對位節點

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
  var a = 1 + 9;
`;

const ast = parser.parse(jscode);
const visitor = {
  NumericLiteral(path) {
    console.log('當前節點源碼:\n', path.toString())
    console.log('對應節點源碼:\n', path.getOpposite().toString())
    console.log('----------------')
  }
}

traverse(ast, visitor);

  結果:

當前節點源碼:
 1
對應節點源碼:
 9
----------------
當前節點源碼:
 9
對應節點源碼:
 1
----------------

  

NodePath.getPrevSibling()

@return Node

獲取同級前一個節點的 NodePath

此函數源碼就一句 return this.getSibling(this.key - 1);

const parser = require(js_env + "@babel/parser");
const traverse = require(js_env + "@babel/traverse").default;

const jscode = `
 var a = 1 + 9;
 a = a + a;
 console.log(a);
 console.log(b);
`;

const ast = parser.parse(jscode);
const visitor = {
 ExpressionStatement(path) {
  console.log('當前節點源碼:\n', path.toString())
  console.log('同級前一個節點源碼:\n', path.getPrevSibling().toString())
  console.log('----------------')
 }
}

traverse(ast, visitor);

  結果:

當前節點源碼:
 a = a + a;
同級前一個節點源碼:
 var a = 1 + 9;
----------------
當前節點源碼:
 console.log(a);
同級前一個節點源碼:
 a = a + a;
----------------
當前節點源碼:
 console.log(b);
同級前一個節點源碼:
 console.log(a);
----------------

  

NodePath.getNextSibling()

@return Node

獲取同級后一個節點的 NodePath

此函數源碼就一句 return this.getSibling(this.key + 1);

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
  var a = 1 + 9;
  a = a + a;
  console.log(a);
  console.log(b);
`;

const ast = parser.parse(jscode);
const visitor = {
  ExpressionStatement(path) {
    console.log('當前節點源碼:\n', path.toString())
    console.log('同級后一個節點源碼:\n', path.getNextSibling().toString())
    console.log('----------------')
  }
}

traverse(ast, visitor);

  


輸出結果:

當前節點源碼:
 a = a + a;
同級前一個節點源碼:
 console.log(a);
----------------
當前節點源碼:
 console.log(a);
同級前一個節點源碼:
 console.log(b);
----------------
當前節點源碼:
 console.log(b);
同級前一個節點源碼:
 
----------------

  

NodePath.getAllPrevSiblings()

@return Array

獲取當前節點前的兄弟節點的 NodePath,結果存放在一個數組中返回

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
  var a = 1 + 9;
  a = a + a;
  console.log(a);
  console.log(b);
`;

const ast = parser.parse(jscode);
const visitor = {
  ExpressionStatement(path) {
    console.log('當前節點源碼:\n', path.toString())
    const pre_nodepath = path.getAllPrevSiblings()
    console.log('前面的兄弟節點源碼:')
    for(var nodepath of pre_nodepath){
      console.log(nodepath.toString())
    }
    console.log('----------------')
  }
}

traverse(ast, visitor);

  

輸出結果:

當前節點源碼:
 a = a + a;
前面的兄弟節點源碼:
var a = 1 + 9;
----------------
當前節點源碼:
 console.log(a);
前面的兄弟節點源碼:
a = a + a;
var a = 1 + 9;
----------------
當前節點源碼:
 console.log(b);
前面的兄弟節點源碼:
console.log(a);
a = a + a;
var a = 1 + 9;
----------------

  

NodePath.get(key, context)

@return NodePath

用於獲取子孫節點

如果不傳入 context 參數, 則以當前 path 對應節點為起點

如果傳入,則以傳入的 path 對應節點為起點

如果想要獲取更多層級的子孫,可以用’.’隔開進行獲取

  • 獲取某個單個屬性節點 .名字

  • 獲取某個節點的第 x 個節點 .x

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `function square(n) {
  var a = 1;
  return 1 + 1;
}`;

const ast = parser.parse(jscode);
const visitor = {
  FunctionDeclaration(path) {  // 找到變量聲明節點,刪除
      var p1 = path.get('body')
      console.log('body 子節點源碼:\n', p1.toString())
      var p2 = path.get('body.body.0')
      console.log('body.body.0 子節點源碼:\n', p2.toString())
  }
}

traverse(ast, visitor);

  


輸出結果:

body 子節點源碼:
 {
  var a = 1;
  return 1 + 1;
}
body.body.0 子節點源碼:
 var a = 1;

  


removal

移除相關

NodePath.remove()

@return null
刪除路徑對應的節點

刪除以后,對應的removed標識為會被設定,內容會被設定為只讀
如果再次執行remove方法,則會報錯

例:刪除 path 對應的節點

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;

const jscode = `function square(n) {
  var a = 1;
  return 1 + 1;
}`;

const ast = parser.parse(jscode);
const visitor = {
  VariableDeclaration(path){  // 找到變量聲明節點,刪除
      path.remove()
  }
}

traverse(ast, visitor);
console.log(generator(ast)['code'])

  


得到結果:

function square(n) {
  return 1 + 1;
}

  

scope

此模塊與作用域相關

Scope

和 作用域 相關的內容被定義在了 Scope類 中
這個類定義位於 @babel/traverse/lib/scope/index.js 文件中

Scope屬性

例:輸出一些屬性,一般不會直接使用,但可以留個印象,后面的函數可能會使用屬性

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function squire(i){
    return i * g * i;
}
function i()
{
    var i = 123;
    i += 2;
    return 123;
}
`;
let ast = parser.parse(jscode);
const visitor = {
    VariableDeclaration(path){
        console.log("\n這里是", path.toString())
        console.log('--------------------------------')
        sc = path.scope  // 獲取對應的 Scope對象
        console.log('這個對象是否已經初始化:', sc.inited)
        console.log('uid 屬性', sc.uid)
        console.log('cached 屬性', sc.cached)
        console.log('node 屬性', sc.node)
        console.log('作用域節點:', sc.block)
        console.log('作用對應的path:', sc.path.node == sc.block)
        console.log('labels 屬性', sc.labels)
        console.log('被綁定量 的信息:', sc.bindings)
        console.log('--------------------------------')
    }
}

traverse(ast, visitor);

  


你能夠直接訪問Scope對象的屬性,它本身也提供了一些方法來訪問

Scope方法

Scope.parent

@return Scope | undefined

獲取當前作用域的父級作用域

此方法通過引用其 Scope.path 屬性的 PathNode.findParent()方法 獲取對應PathNode后再次獲取作用域的方式獲取

例:獲取父級作用域

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function squire(i){
    return i * g * i;
}
function i()
{
    var i = 123;
    i += 2;
    return 123;
}
`;
let ast = parser.parse(jscode);
const visitor = {
    VariableDeclaration(path){
        console.log("\n這里是", path.toString())
        console.log('--------------------------------')
        sc = path.scope  // 獲取對應的 Scope對象
        console.log('parent結果:', sc.parent)
    }
}

traverse(ast, visitor);

  


結果

Scope.dump()

return null

輸出到自底向上的 作用域與被綁定量的信息

執行后會得到類似於這樣的輸出信息

 

# FunctionDeclaration
 - i { constant: false, references: 0, violations: 1, kind: 'var' }
# Program
 - squire { constant: true, references: 0, violations: 0, kind: 'hoisted' }
 - i { constant: true, references: 0, violations: 0, kind: 'hoisted' }

  


作用域 以#划分,此處有兩個作用域 FunctionDeclaration 與 Program

被綁定量 以最前方設置-來標識,一般顯示其中的4種信息

  • constant
    量 在聲明后,在作用域內是否為常量
    實際上對應對應量的 Binding 對象的Binding.constant屬性
  • references
    被引用次數
    實際上對應對應量的 Binding 對象的Binding.references屬性
  • violations
    量 被 重新定義/賦值 的次數
    實際上對應對應量的 Binding 對象的Binding.constantViolations的長度。這個屬性被用於記錄變更位置(每次變更都添加內容)
  • kind
    函數聲明類型。常見的有:hoisted提升,var變量, local內部
    實際上對應對應量的 Binding 對象的Binding.kind屬性

實際上這些信息大部分 (以一個被綁定量,一個 Binding 對象的方式)儲存在 Scope.bindings 這個屬性中

例:使用案例

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
function squire(i){
    return i * i * i;
}
function i(){
    var i = 123;
    i += 2;
    return 123;
}
`;
let ast = parser.parse(jscode);
const visitor = {
    "FunctionDeclaration"(path){
        console.log("\n\n這里是函數 ", path.node.id.name + '()')
        path.scope.dump();
    }
}

traverse(ast, visitor);

  


得到結果:

 

這里是函數  squire()
------------------------------------------------------------
# FunctionDeclaration
 - i { constant: true, references: 3, violations: 0, kind: 'param' }
# Program
 - squire { constant: true, references: 0, violations: 0, kind: 'hoisted' }
 - i { constant: true, references: 0, violations: 0, kind: 'hoisted' }
------------------------------------------------------------


這里是函數  i()
------------------------------------------------------------
# FunctionDeclaration
 - i { constant: false, references: 0, violations: 1, kind: 'var' }
# Program
 - squire { constant: true, references: 0, violations: 0, kind: 'hoisted' }
 - i { constant: true, references: 0, violations: 0, kind: 'hoisted' }
------------------------------------------------------------

  

Scope.parentBlock(name)

@return Node

獲取 作用域路徑 的父級
它的源碼就一句 return this.path.parent;

例: 獲取作用域路徑的父級

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
    ReturnStatement(path){
        var n = path.node.argument.name
        console.log("\n這里是", path.toString())
        console.log('結果:', path.scope.parentBlock)
    }
}

traverse(ast, visitor);

  


結果

 

Scope.getBinding(name)

@return Binding

獲取指定 被綁定量 的 Binding對象

如果在 當前作用域 找不到指定的 被綁定量,那么就會遞歸在父級作用域中尋找

例:獲取指定的 被綁定量對象

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
    ReturnStatement(path){
        var n = path.node.argument.name
        console.log("\n這里是", path.toString())
        console.log('被綁定量:', path.scope.getBinding(n))
    }
}

traverse(ast, visitor);

  

Scope.getOwnBinding(name)

@return Binding

傳入一個名稱,從當前的 作用域 中拿到指定的 被綁定量對象Binding
實際上方法的源碼就一句return this.bindings[name];

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function a(){var a=1;return g;}
`;
let ast = parser.parse(jscode);
const visitor = {
    ReturnStatement(path){
        var n = path.node.argument.name
        console.log("\n這里是", path.toString())
        console.log('獲取擋牆定義域里 a的Binding,結果:', path.scope.bindings['a'])
    }
}

traverse(ast, visitor);

  

Scope.getBindingIdentifier(name)

@return Node | void 0
獲取指定的 Binding 的定義節點Node

方法作用域獲取 Binding ,再通過這個 Binding 獲取其定義的節點

這個方法通過 Scope.getBinding(name) 方法獲取 Binding ,所以會存在遞歸向上的情況

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
    ReturnStatement(path){
        var n = path.node.argument.name
        console.log("\n這里是", path.toString())
        console.log(n, '的定義:', path.scope.getBindingIdentifier(n))
    }
}

traverse(ast, visitor);

  


Scope.getOwnBindingIdentifier(name)

@return Node|void 0
獲取指定的 Binding ,並通過這個 Binding 獲取其定義的節點
這個方法只關注 當前作用域,並不會向上尋找

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
    ReturnStatement(path){
        var n = path.node.argument.name
        console.log("\n這里是", path.toString())
        console.log(n, '的定義:', path.scope.getOwnBindingIdentifier(n))
    }
}

traverse(ast, visitor);

  


得到結果:

這里是 return g;
g 的定義: undefined

這里是 return z;
z 的定義: Node {
  type: 'Identifier',
  start: 53,
  end: 54,
  loc: SourceLocation {
    start: Position { line: 4, column: 17 },
    end: Position { line: 4, column: 18 },
    identifierName: 'z'
  },
  name: 'z'
}

  

Scope.hasOwnBinding(name)

@return bool

獲知當前作用域是否有某個被綁定變量得到結果:

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
    ReturnStatement(path){
        var n = path.node.argument.name
        console.log("\n這里是", path.toString())
        console.log('當前作用域有 被綁定變量 z:', path.scope.hasOwnBinding('z'))
        console.log('當前作用域有 被綁定變量 g:', path.scope.hasOwnBinding('g'))
    }
}

traverse(ast, visitor);

  

這里是 return g;
當前作用域有 被綁定變量 z: false
當前作用域有 被綁定變量 g: false

這里是 return z;
當前作用域有 被綁定變量 z: true
當前作用域有 被綁定變量 g: false

  


兩個函數的作用域內都不會有g的綁定,因為它被綁定在更上級作用域中

Scope.hasBinding(name, noGlobals)

@return bool
向上遞歸作用域,獲知是否有某個被綁定變量

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
    ReturnStatement(path){
        console.log("\n這里是", path.toString())
        console.log('作用域有 被綁定變量 z:', path.scope.hasBinding('z'))
        console.log('作用域有 被綁定變量 g:', path.scope.hasBinding('g'))
    }
}

traverse(ast, visitor);

  

 

Binding

Binding 對象用於存儲 被綁定在作用域的量 的信息
你可以在 @babel/traverse/lib/scope/binding.js 查看到它的定義

 

Binding屬性

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

const jscode = `
function a(){
    var a = 1;
    a = a + 1;
    return a;
}
function b(){
    var b = 1;
    var c = 2;
    b = b - c;
    return b;
}
`;
let ast = parser.parse(jscode);
const visitor = {
    BlockStatement(path){
        console.log("\n此塊節點源碼:\n", path.toString())
        console.log('----------------------------------------')
        var bindings = path.scope.bindings
        console.log('作用域內 被綁定量 數量:', Object.keys(bindings).length)

        for(var binding_ in bindings){
            console.log('名字:', binding_)
            binding_ = bindings[binding_];
            console.log('類型:', binding_.kind)
            console.log('定義:', binding_.identifier)
            console.log('是否為常量:', binding_.constant)
            console.log('被修改信息信息記錄', binding_.constantViolations)
            console.log('是否會被引用:', binding_.referenced)
            console.log('被引用次數', binding_.references)
            console.log('被引用信息NodePath記錄', binding_.referencePaths)
        }
    }
}

traverse(ast, visitor);

  

comments

注釋相關

NodePath.addComment(type, content, line)

@return None

添加注釋

實際上只是調用types.addComment 的方法而已

參數:

  • type str 指定添加的注釋方式,如果填入"leading",則添加的注釋會插入已有注釋之前,否則在原有注釋之后

  • content str 注釋內容

  • line bool 插入行注釋還是塊注釋

例:插入注釋

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;

const jscode = `
  var a = 1 + 9;
`;

const ast = parser.parse(jscode);
const visitor = {
  NumericLiteral(path) {
    console.log('當前節點源碼:\n', path.toString());
    path.addComment('trailing', "注釋", false);
  }
}

traverse(ast, visitor);
console.log(generator(ast)['code'])

  

@babel/types

utils

 

Types.shallowEqual(actual, expected)

@return bool

對比函數,expected傳入一個字典進行 keyvalue 遍歷

獲取 actual.key 的值與 value 進行對比

如果有一個不一致,那么返回 false, 否則返回 true

其定義在: @babel/types/lib/validators/generated/index.js

例:判斷節點的name是否為a

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");

const jscode = 'var a=1; var b=1+1;';
let ast = parser.parse(jscode);

const visitor = {
  enter(path){
    console.log('當前節點源碼:', path.toString())
    console.log('其屬性name為a:', t.shallowEqual(path.node, {'name':'a'}))
  }
}

traverse(ast, visitor);

  

Types.isNodeType(node, opts)validators

@return bool

這並不是一個函數,這是一大堆由生成代碼生成的函數,大約有290個

這些函數定義在 @babel/types/lib/validators/generated/index.js

函數邏輯都是類似的

  1. if( 沒有node ) return false

  2. if(node.type == 聲明類型) return false

  3. else if( 沒有opts ) return true

  4. else return types.shallowEqual(node, opts)

例: 判斷節點是否符合判斷

 

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");

const jscode = 'var a=1;var b=1+1;';
let ast = parser.parse(jscode);

const visitor = {
 enter(path){
  console.log('當前節點源碼:', path.toString())
  console.log('是 Identifier', t.isIdentifier(path.node))
  console.log('是 Identifier 且其屬性name為a:', t.isIdentifier(path.node, {'name':'a'}))
 }
}

traverse(ast, visitor);

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM