LoginSignup
2
2

More than 1 year has passed since last update.

Typescriptの抽象構文木を眺める

Last updated at Posted at 2022-12-22

抽象構文木とは

  • Abstract Syntax Tree(AST)のこと
  • コードをパースして木構造の集合として扱えるようにしたもの
  • eslintやbabelなどで利用されている

TypescriptにおけるASTの立ち位置

  • Typescriptのコンパイラであるtscは内部でソースコードからASTを構築し、Typescriptを理解している

実際のASTを見てみる

今回は、eslintのカスタムプラグインを自作する準備としてASTに慣れておこう、という目的があるので、typescript-eslintのASTパーサであるtypescript-estreeを使ってASTを抽出します。
tscが実際に用いるASTはTypescript Complier APIで出力することができるので、興味のある方はComplierAPIのリポジトリをご覧ください。

import { parse } from '@typescript-eslint/typescript-estree';
import { readFile } from './file_utils';


// 文字列としてtypescriptのソースコードを読みASTにパースする
const sampleCode = readFile('./src/sample.ts')
if (sampleCode) {
const ast = parse(sampleCode, {
  loc: false,
  range: false,
});

  console.log(JSON.stringify(ast, null, 2))
}

Sample1

最も単純なコード

ソースコード

console.log('Hello World!')

AST

{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "name": "log"
          },
          "computed": false,
          "optional": false
        },
        "arguments": [
          {
            "type": "Literal",
            "value": "Hello World!",
            "raw": "'Hello World!'"
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "script"
}

consoleというMemberExpressionのlogというpropertyがHello World!というLiteralのargumentsを渡されて評価された。ということがそのままJSON形式で表現されている。

Sample2

変数定義

const hoge = 'hoge'

let fuga = 'fuga'

var piyo = 'piyo'

AST

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "hoge"
          },
          "init": {
            "type": "Literal",
            "value": "hoge",
            "raw": "'hoge'"
          }
        }
      ],
      "kind": "const"
    },
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "fuga"
          },
          "init": {
            "type": "Literal",
            "value": "fuga",
            "raw": "'fuga'"
          }
        }
      ],
      "kind": "let"
    },
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "piyo"
          },
          "init": {
            "type": "Literal",
            "value": "piyo",
            "raw": "'piyo'"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "script"
}

変数定義の違いはkindで表現され、それぞれがどのようなステートメントで定義されたかが表現されている。

Sample3

class定義を伴うよくあるソースコード

class Hoge {
  name!: string
  age!: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

class Fuga extends Hoge {
  constructor(hoge: Hoge) {
    super(hoge.name, hoge.age)
  }

  greeting() {
    return `I'm ${this.name}.`
  }
}

const fuga = new Fuga({ name: 'fuga', age: 12 })


console.log(fuga.greeting())

AST

{
  "type": "Program",
  "body": [
    {
      "type": "ClassDeclaration",
      "id": {
        "type": "Identifier",
        "name": "Hoge"
      },
      "body": {
        "type": "ClassBody",
        "body": [
          {
            "type": "PropertyDefinition",
            "key": {
              "type": "Identifier",
              "name": "name"
            },
            "value": null,
            "computed": false,
            "static": false,
            "declare": false,
            "override": false,
            "typeAnnotation": {
              "type": "TSTypeAnnotation",
              "typeAnnotation": {
                "type": "TSStringKeyword"
              }
            },
            "definite": true
          },
          {
            "type": "PropertyDefinition",
            "key": {
              "type": "Identifier",
              "name": "age"
            },
            "value": null,
            "computed": false,
            "static": false,
            "declare": false,
            "override": false,
            "typeAnnotation": {
              "type": "TSTypeAnnotation",
              "typeAnnotation": {
                "type": "TSNumberKeyword"
              }
            }
          },
          {
            "type": "MethodDefinition",
            "key": {
              "type": "Identifier",
              "name": "constructor"
            },
            "value": {
              "type": "FunctionExpression",
              "id": null,
              "params": [
                {
                  "type": "Identifier",
                  "name": "name",
                  "typeAnnotation": {
                    "type": "TSTypeAnnotation",
                    "typeAnnotation": {
                      "type": "TSStringKeyword"
                    }
                  }
                },
                {
                  "type": "Identifier",
                  "name": "age",
                  "typeAnnotation": {
                    "type": "TSTypeAnnotation",
                    "typeAnnotation": {
                      "type": "TSNumberKeyword"
                    }
                  }
                }
              ],
              "generator": false,
              "expression": false,
              "async": false,
              "body": {
                "type": "BlockStatement",
                "body": [
                  {
                    "type": "ExpressionStatement",
                    "expression": {
                      "type": "AssignmentExpression",
                      "operator": "=",
                      "left": {
                        "type": "MemberExpression",
                        "object": {
                          "type": "ThisExpression"
                        },
                        "property": {
                          "type": "Identifier",
                          "name": "name"
                        },
                        "computed": false,
                        "optional": false
                      },
                      "right": {
                        "type": "Identifier",
                        "name": "name"
                      }
                    }
                  },
                  {
                    "type": "ExpressionStatement",
                    "expression": {
                      "type": "AssignmentExpression",
                      "operator": "=",
                      "left": {
                        "type": "MemberExpression",
                        "object": {
                          "type": "ThisExpression"
                        },
                        "property": {
                          "type": "Identifier",
                          "name": "age"
                        },
                        "computed": false,
                        "optional": false
                      },
                      "right": {
                        "type": "Identifier",
                        "name": "age"
                      }
                    }
                  }
                ]
              }
            },
            "computed": false,
            "static": false,
            "kind": "constructor",
            "override": false
          }
        ]
      },
      "superClass": null
    },
    {
      "type": "ClassDeclaration",
      "id": {
        "type": "Identifier",
        "name": "Fuga"
      },
      "body": {
        "type": "ClassBody",
        "body": [
          {
            "type": "MethodDefinition",
            "key": {
              "type": "Identifier",
              "name": "constructor"
            },
            "value": {
              "type": "FunctionExpression",
              "id": null,
              "params": [
                {
                  "type": "Identifier",
                  "name": "hoge",
                  "typeAnnotation": {
                    "type": "TSTypeAnnotation",
                    "typeAnnotation": {
                      "type": "TSTypeReference",
                      "typeName": {
                        "type": "Identifier",
                        "name": "Hoge"
                      }
                    }
                  }
                }
              ],
              "generator": false,
              "expression": false,
              "async": false,
              "body": {
                "type": "BlockStatement",
                "body": [
                  {
                    "type": "ExpressionStatement",
                    "expression": {
                      "type": "CallExpression",
                      "callee": {
                        "type": "Super"
                      },
                      "arguments": [
                        {
                          "type": "MemberExpression",
                          "object": {
                            "type": "Identifier",
                            "name": "hoge"
                          },
                          "property": {
                            "type": "Identifier",
                            "name": "name"
                          },
                          "computed": false,
                          "optional": false
                        },
                        {
                          "type": "MemberExpression",
                          "object": {
                            "type": "Identifier",
                            "name": "hoge"
                          },
                          "property": {
                            "type": "Identifier",
                            "name": "age"
                          },
                          "computed": false,
                          "optional": false
                        }
                      ],
                      "optional": false
                    }
                  }
                ]
              }
            },
            "computed": false,
            "static": false,
            "kind": "constructor",
            "override": false
          },
          {
            "type": "MethodDefinition",
            "key": {
              "type": "Identifier",
              "name": "greeting"
            },
            "value": {
              "type": "FunctionExpression",
              "id": null,
              "generator": false,
              "expression": false,
              "async": false,
              "body": {
                "type": "BlockStatement",
                "body": [
                  {
                    "type": "ReturnStatement",
                    "argument": {
                      "type": "TemplateLiteral",
                      "quasis": [
                        {
                          "type": "TemplateElement",
                          "value": {
                            "raw": "I'm ",
                            "cooked": "I'm "
                          },
                          "tail": false
                        },
                        {
                          "type": "TemplateElement",
                          "value": {
                            "raw": ".",
                            "cooked": "."
                          },
                          "tail": true
                        }
                      ],
                      "expressions": [
                        {
                          "type": "MemberExpression",
                          "object": {
                            "type": "ThisExpression"
                          },
                          "property": {
                            "type": "Identifier",
                            "name": "name"
                          },
                          "computed": false,
                          "optional": false
                        }
                      ]
                    }
                  }
                ]
              },
              "params": []
            },
            "computed": false,
            "static": false,
            "kind": "method",
            "override": false
          }
        ]
      },
      "superClass": {
        "type": "Identifier",
        "name": "Hoge"
      }
    },
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "fuga"
          },
          "init": {
            "type": "NewExpression",
            "callee": {
              "type": "Identifier",
              "name": "Fuga"
            },
            "arguments": [
              {
                "type": "ObjectExpression",
                "properties": [
                  {
                    "type": "Property",
                    "key": {
                      "type": "Identifier",
                      "name": "name"
                    },
                    "value": {
                      "type": "Literal",
                      "value": "fuga",
                      "raw": "'fuga'"
                    },
                    "computed": false,
                    "method": false,
                    "shorthand": false,
                    "kind": "init"
                  },
                  {
                    "type": "Property",
                    "key": {
                      "type": "Identifier",
                      "name": "age"
                    },
                    "value": {
                      "type": "Literal",
                      "value": 12,
                      "raw": "12"
                    },
                    "computed": false,
                    "method": false,
                    "shorthand": false,
                    "kind": "init"
                  }
                ]
              }
            ]
          }
        }
      ],
      "kind": "const"
    },
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "name": "log"
          },
          "computed": false,
          "optional": false
        },
        "arguments": [
          {
            "type": "CallExpression",
            "callee": {
              "type": "MemberExpression",
              "object": {
                "type": "Identifier",
                "name": "fuga"
              },
              "property": {
                "type": "Identifier",
                "name": "greeting"
              },
              "computed": false,
              "optional": false
            },
            "arguments": [],
            "optional": false
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "script"
}

まず初めに、Hogeというクラスが定義されている。二つのメンバー変数nameとageの違いは、変数名であるPropertyDefinition.key.nameと型情報であるtypeAnnotationがTSStringKeywordとTSNumberKeywordである点。それから、definiteが付いているかどうかという点。
次に、Fugaというクラス定義を見てみると、constructorの引数のtypeAnnotationがHogeになっている。ユーザー定義型であってもしっかりと型情報が載ってきている。
また、superClassがHogeであるという情報も載ってきていて、継承関係も表現できている。

Sample4

typescript-eslint/no-use-before-defineで警告が出るパターン

console.log(hoge)

var hoge = 'hoge'

AST

{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "name": "log"
          },
          "computed": false,
          "optional": false
        },
        "arguments": [
          {
            "type": "Identifier",
            "name": "hoge"
          }
        ],
        "optional": false
      }
    },
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "hoge"
          },
          "init": {
            "type": "Literal",
            "value": "hoge",
            "raw": "'hoge'"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "script"
}

console.logのargumentsが、Sample1のASTとは異なり、"type": "Literal"ではなく、"type": "Identifier"となっているため、これはユーザー定義の変数であることがわかる。ASTのbodyは配列になっていて、その順番はソースコードの記載順序に一致しているため、"name": "hoge"を持った何らかの変数がconsole.logの呼び出しよりも若いインデックスで出てこなければ、定義されていない変数を呼び出しているということがわかる。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2