來了,前面一篇文章的相關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
,則會設置ParenthesizedExpression
AST節點 -
errorRecovery
默認情況下,如果Babel
發現一些 不正常的代碼 就會拋出錯誤
把這個設置為true
,則會在保存解析錯誤的同時繼續解析代碼,錯誤的記錄將被保存在 最終生成的AST的errors
屬性中
注意,那些嚴重的錯誤依然會終止解析 -
plugins
記錄希望啟動的插件的數組 -
sourceType
代碼的解析方式,你可以填入"script"
(默認),"module"
或"unambiguous"
如果設置為”unambiguous”,那么系統會根據ES6語法中的imports
和export
來判斷是"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 parser
與ESTree
的不同之處
- 用
StringLiteral
,NumericLiteral
,BooleanLiteral
,NullLiteral
,RegExpLiteral
取代Literal
- 用
ObjectProperty
和ObjectMethod
取代Property
- 用
MethodDefinition
取代ClassMethod
Program
andBlockStatement
包含的directives
用Directive
和DirectiveLiteral
來填充FunctionExpression
中的ClassMethod
,ObjectProperty
,ObjectMethod
屬性被引入到main方法
節點中
@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
傳入一個字典進行 key
, value
遍歷
獲取 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
函數邏輯都是類似的
-
if( 沒有node ) return
false
-
if(
node.type
==聲明類型
) returnfalse
-
else if( 沒有opts ) return
true
-
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);