AST syntax tree addition, deletion, modification and query

AST is the abbreviation of Abstract Syntax Tree, namely "Abstract Syntax Tree" It represents the syntax structure of programming language in the form of tree When webpack packages JS code, webpack will add some code on the basis of our original code. For example, when we package JS code, we can convert high-level code into low-level code through ast syntax tree
AST online address generation
babel plug-in to view the usage address

The AST generation process consists of source code - > lexical analysis - > syntax analysis - > abstract syntax tree
for example
let a = 1 + 2
lexical analysis

  • From left to right, one character is read into the source program one by one, from which a "word" and "symbol" are recognized. What is read out is ordinary characters, and there is no function of any programming language
  • Save the analysis results in a lexical unit array
wordwordSymbolnumberSymbolnumber
leta=1+2
[
  {"type": "word", value: "let"},
  {"type": "word", value: "a"},
  {"type": "Punctuator", value: "="},
  {"type": "Numberic", value: "1"},
  {"type": "Punctuator", value: "+"},
  {"type": "Numberic", value: "2"},
]

Then enter lexical analysis
Grammar analysis
Combine word sequences into various grammatical phrases

keywordidentifier Assignment Operators Literal Binary operatorLiteral
leta=1+2
[{
  "type": "VariableDecLaration", 
  "content": {
    {"type": "kind", "value": "let"},  // kind indicates what type of declaration it is
    {"type": "Identifier", "value": "a"},  // Identifier means identifier
    {"type": "init", "value": "="},  // An expression that represents the initial value
    {"type": "Literal", "value": "1"},  // Literal means a literal quantity
    {"type": "operator", "value": "+"},  // The operator representation is a binary operator
    {"type": "Literal", "value": "2"},
  } 
}]

Abstract syntax tree

 "program": {
    "type": "Program",
    "start": 0,
    "end": 13,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 1,
        "column": 13
      }
    },
    "sourceType": "module",
    "interpreter": null,
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 0,
        "end": 13,
        "loc": {
          "start": {
            "line": 1,
            "column": 0
          },
          "end": {
            "line": 1,
            "column": 13
          }
        },
        "declarations": [  // Here is an array, indicating that multiple variables can be declared at the same time
          {
            "type": "VariableDeclarator",
            "start": 4,
            "end": 13,
            "loc": {
              "start": {
                "line": 1,
                "column": 4
              },
              "end": {
                "line": 1,
                "column": 13
              }
            },
            "id": {
              "type": "Identifier",
              "start": 4,
              "end": 5,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 4
                },
                "end": {
                  "line": 1,
                  "column": 5
                },
                "identifierName": "a"
              },
              "name": "a"
            },
            "init": {
              "type": "BinaryExpression",
              "start": 8,
              "end": 13,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 8
                },
                "end": {
                  "line": 1,
                  "column": 13
                }
              },
              "left": {
                "type": "NumericLiteral",
                "start": 8,
                "end": 9,
                "loc": {
                  "start": {
                    "line": 1,
                    "column": 8
                  },
                  "end": {
                    "line": 1,
                    "column": 9
                  }
                },
                "extra": {
                  "rawValue": 1,
                  "raw": "1"
                },
                "value": 1
              },
              "operator": "+",
              "right": {
                "type": "NumericLiteral",
                "start": 12,
                "end": 13,
                "loc": {
                  "start": {
                    "line": 1,
                    "column": 12
                  },
                  "end": {
                    "line": 1,
                    "column": 13
                  }
                },
                "extra": {
                  "rawValue": 2,
                  "raw": "2"
                },
                "value": 2
              }
            }
          }
        ],
        "kind": "let"
      }
    ],
    "directives": []
  }

Program example

package.json dependency

    "@babel/generator": "^7.11.6",  // Convert AST structure
    "@babel/parser": "^7.11.5",  // Disassemble AST tree structure
    "@babel/traverse": "^7.11.5", 
    "@babel/types": "^7.11.5",

@Generate AST from babel/parse

import * as parser from '@babel/parser'

const code = `let a = 1 + 2`
const ast = parser.parse(code)
console.log(ast)

Modify syntax tree

Traverse the nodes through the traverse module of babel. After finding the corresponding nodes, convert the syntax tree into code through the generator module in babel

import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'

const code = `let val = 1 + 2`
const ast = parser.parse(code)
console.log(ast)

// Traverse method can traverse all syntax tree nodes
traverse(ast, {
  enter(path) {  // This path will find all node s
    if (path.node.type == 'Identifier') {
      path.node.name = 'modify'
      path.stop()
    }
  }
})

const ret = generator(ast)
console.log(ret)

Here, val will be changed to modify

Create syntax tree

Create a syntax tree node through the @ babel/types module, and then push it into the body to manually create the syntax tree,

import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'
import * as t from '@babel/types'

let code = ``
let ast = parser.parse(code)

let left = t.NumericLiteral(1)
let right = t.NumericLiteral(2)
let init = t.binaryExpression("+", left, right)

let id = t.identifier("add")
let variable = t.variableDeclarator(id, init)
let declaration = t.variableDeclaration('let', [variable])
ast.program.body.push(declaration)


// Convert to code 
let genCode = generator(ast)
console.log(genCode.code)
Delete syntax tree node

The core of deleting syntax nodes is to traverse and find all nodes first, and complete it through @ babel/traverse. After finding each node, you can complete the operation of adding, deleting, modifying and querying through specific methods

  • Common properties of NodePath
    • Node: get the current node
    • Parent: parent node
    • parentPath: parent path
    • Scope: scope
    • context: context
  • Common methods of NodePath
    • get: current node
    • findParent: search parent node
    • getSibling: get sibling node
    • replaceWith: replace this node with the AST node
    • replaceWithMultiple: replace this node with multiple AST nodes
    • insertBefore: inserts a node before it
    • insertAfter: inserts a node after the node
    • remove: delete node
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default  
const generator = require('@babel/generator').default

let code = `
  console.log('jake')
  let sum = 1 + 2 
  let minus = 2 - 1
  console.log("tom")
`
let ast = parser.parse(code)
console.log(ast)

// traverse traverses the syntax tree
traverse(ast, {
  Identifier(path) {
    if (path.node.name == 'sum') {
      path.parentPath.remove()
    }
  }
})

console.log(generator(ast))

After deleting sum

Keywords: Front-end AST

Added by vtroubled on Fri, 04 Feb 2022 04:25:05 +0200