ESLint と AST 入門:ESTree/Token/SourceCode を 10 分で把握
ESLint のカスタムルールを作成しようとして、「AST」「ESTree」「Token」といった用語に戸惑った経験はありませんか。 これらは ESLint がコードを解析する上で欠かせない基礎概念ですが、公式ドキュメントを読んでもなかなか全体像が掴みづらいものです。
本記事では、ESLint の内部で活躍する AST(抽象構文木)の基本から、ESTree 仕様、Token、SourceCode オブジェクトまでを、初心者の方にもわかりやすく解説します。 10 分程度で読み終わる内容にまとめましたので、ESLint のカスタムルール作成に挑戦したい方は、ぜひ最後までお付き合いください。
背景
ESLint がコードを解析する仕組み
ESLint は、JavaScript や TypeScript のコードをチェックするための静的解析ツールです。 私たちが書いたコードを「文字列」として読み込むだけでは、その構造や意味を理解できません。
そこで ESLint は、コードを**AST(Abstract Syntax Tree:抽象構文木)**という木構造のデータに変換してから解析を行います。 AST に変換することで、コードの各要素(変数宣言、関数呼び出し、条件分岐など)を体系的に扱えるようになるのです。
AST とは何か
AST は、プログラムのソースコードを木構造で表現したものです。
たとえば const x = 5 + 3; というコードは、以下のような構造で表されます。
- ルートノード(プログラム全体)
- 変数宣言ノード
- 変数名
x - 初期値(二項演算)
- 左辺
5 - 演算子
+ - 右辺
3
- 左辺
- 変数名
- 変数宣言ノード
この木構造により、ESLint は「この変数は宣言されているか」「この演算子は正しく使われているか」といったチェックを機械的に行えます。
下図は、ESLint がソースコードを AST に変換し、ルールで検証する基本的なフローを示しています。
mermaidflowchart LR
source["ソースコード<br/>(文字列)"] -->|パース| ast["AST<br/>(木構造)"]
ast -->|トラバース| rules["ESLintルール<br/>(検証)"]
rules -->|違反検出| output["エラー/警告"]
このフローを理解することで、ESLint がどのようにしてコードの問題を見つけ出しているかがわかります。
ESTree という標準仕様
JavaScript の AST にはさまざまな表現方法がありますが、ESLint ではESTreeという仕様を採用しています。 ESTree は JavaScript AST の事実上の標準で、各ノードの型や構造が明確に定義されているのです。
たとえば、変数宣言は VariableDeclaration ノード、関数宣言は FunctionDeclaration ノードといった具合に、すべての構文要素に対応するノードタイプが決められています。
この統一された仕様のおかげで、ESLint のルール作成者は一貫した方法でコードを解析できるようになりました。
課題
カスタムルール作成の壁
ESLint には多くの組み込みルールがありますが、プロジェクト固有のコーディング規約をチェックしたい場合は、カスタムルールを作成する必要があります。 しかし、いざカスタムルールを書こうとすると、以下のような疑問に直面するでしょう。
- 「どのノードタイプを監視すればいいのか」
- 「ノードの構造はどうなっているのか」
- 「トークンとノードの違いは何か」
- 「SourceCode オブジェクトで何ができるのか」
これらの疑問に答えるには、AST の基礎知識が不可欠です。
ドキュメントの情報量の多さ
ESLint の公式ドキュメントや ESTree の仕様書は非常に詳細ですが、初心者にとっては情報量が多すぎて、どこから手をつければよいかわかりにくい面があります。 また、用語の定義が散らばっていて、全体像を掴むのに時間がかかるという課題もあるのです。
下図は、カスタムルール作成時に理解が必要な要素とその関係性を示しています。
mermaidflowchart TB
rule["カスタムルール"] -->|監視| node["ASTノード<br/>(ESTree)"]
rule -->|参照| token["Token<br/>(字句要素)"]
rule -->|利用| sourcecode["SourceCode<br/>(ヘルパー)"]
node -->|構成| identifier["Identifier"]
node -->|構成| literal["Literal"]
node -->|構成| expression["Expression"]
sourcecode -->|提供| api["コメント取得<br/>スコープ解析<br/>テキスト取得"]
この図からわかるように、カスタムルールを作成するには、ノード・トークン・SourceCode という 3 つの要素を理解する必要があります。
実践的な学習リソースの不足
AST や ESTree について解説した記事は多くありますが、「ESLint のカスタムルール作成」という実践的な観点からまとめられた日本語リソースは限られています。 特に、Token や SourceCode といった ESLint 固有の API について、初心者向けに体系的に説明している記事は少ないのが現状です。
解決策
ESTree:AST ノードの型定義
ESTree は、JavaScript の AST 構造を定義した仕様です。 この仕様に従うことで、異なるパーサー(Espree、Babel、TypeScript ESLint など)が生成する AST が共通のインターフェースを持つようになります。
主要なノードタイプ
ESTree には、以下のような主要なノードタイプが定義されています。
| # | ノードタイプ | 説明 | 例 |
|---|---|---|---|
| 1 | Program | プログラム全体のルートノード | - |
| 2 | VariableDeclaration | 変数宣言 | const x = 1; |
| 3 | FunctionDeclaration | 関数宣言 | function foo() {} |
| 4 | Identifier | 識別子(変数名など) | x, foo |
| 5 | Literal | リテラル値 | "hello", 42, true |
| 6 | BinaryExpression | 二項演算 | a + b |
| 7 | CallExpression | 関数呼び出し | foo() |
| 8 | MemberExpression | メンバーアクセス | obj.prop |
ノードの共通構造
すべてのノードは、以下の共通プロパティを持っています。
typescript// すべてのノードが持つ基本的なプロパティ
interface BaseNode {
type: string; // ノードタイプ(例: "Identifier", "Literal")
loc?: SourceLocation; // ソースコード上の位置情報
range?: [number, number]; // 開始位置と終了位置(文字インデックス)
}
type プロパティは必須で、ノードの種類を示します。
loc と range はオプションですが、ESLint ではこれらを使ってエラーの発生箇所を特定します。
typescript// SourceLocation の構造
interface SourceLocation {
start: Position; // 開始位置
end: Position; // 終了位置
}
interface Position {
line: number; // 行番号(1から始まる)
column: number; // 列番号(0から始まる)
}
位置情報により、「この問題は何行目の何文字目で発生した」という詳細なレポートが可能になります。
下図は、AST ノードの階層構造を示しています。
mermaidflowchart TB
program["Program"] -->|body| varDecl["VariableDeclaration"]
varDecl -->|declarations| varDeclr["VariableDeclarator"]
varDeclr -->|id| ident["Identifier<br/>(変数名)"]
varDeclr -->|init| binary["BinaryExpression"]
binary -->|left| lit1["Literal<br/>(5)"]
binary -->|operator| op["+"]
binary -->|right| lit2["Literal<br/>(3)"]
この図は const x = 5 + 3; というコードの AST 構造を表しています。
ルートの Program ノードから始まり、変数宣言、識別子、二項演算といった各要素がツリー構造で表現されていることがわかります。
Token:字句単位の要素
AST ノードが構文構造を表すのに対し、Token は字句単位の要素を表します。 Token は、ソースコードをパースする際に最初に生成される単位で、キーワード、識別子、演算子、括弧、文字列などが該当します。
Token の種類
ESLint では、以下のような Token タイプが定義されています。
| # | トークンタイプ | 説明 | 例 |
|---|---|---|---|
| 1 | Keyword | JavaScript のキーワード | const, function, if |
| 2 | Identifier | 識別子 | myVariable, foo |
| 3 | Punctuator | 句読点・演算子 | {, }, +, ; |
| 4 | String | 文字列リテラル | "hello", 'world' |
| 5 | Numeric | 数値リテラル | 42, 3.14 |
| 6 | Template | テンプレートリテラル | `hello ${name}` |
Token の構造
各 Token は、以下のような構造を持っています。
typescript// Tokenの基本構造
interface Token {
type: string; // トークンタイプ
value: string; // トークンの文字列値
range: [number, number]; // 開始・終了位置
loc: SourceLocation; // 行・列の位置情報
}
たとえば、const x = 5; というコードは以下のような Token に分解されます。
typescript// "const x = 5;" のToken例
[
{ type: "Keyword", value: "const", range: [0, 5], loc: {...} },
{ type: "Identifier", value: "x", range: [6, 7], loc: {...} },
{ type: "Punctuator", value: "=", range: [8, 9], loc: {...} },
{ type: "Numeric", value: "5", range: [10, 11], loc: {...} },
{ type: "Punctuator", value: ";", range: [11, 12], loc: {...} }
]
このように、ソースコードが字句単位で分解されることで、「セミコロンの有無」や「演算子の前後のスペース」といった細かいスタイルチェックが可能になります。
SourceCode:ESLint のヘルパーオブジェクト
SourceCode は、ESLint がルール作成者に提供するヘルパーオブジェクトです。 AST ノードや Token に直接アクセスするだけでなく、便利なメソッドを通じてソースコードの情報を取得できます。
SourceCode の主要メソッド
SourceCode オブジェクトには、以下のような便利なメソッドが用意されています。
| # | メソッド | 説明 | 戻り値 |
|---|---|---|---|
| 1 | getText(node?) | ノードまたはソースコード全体のテキストを取得 | string |
| 2 | getTokens(node) | ノードに含まれるすべての Token を取得 | Token[] |
| 3 | getFirstToken(node) | ノードの最初の Token を取得 | Token |
| 4 | getLastToken(node) | ノードの最後の Token を取得 | Token |
| 5 | getCommentsBefore(nodeOrToken) | 指定要素の前のコメントを取得 | Comment[] |
| 6 | getCommentsAfter(nodeOrToken) | 指定要素の後のコメントを取得 | Comment[] |
| 7 | getScope(node) | ノードのスコープ情報を取得 | Scope |
SourceCode の活用例
以下は、SourceCode を使って関数名を取得する例です。
typescript// SourceCodeを使った関数名の取得
function getFunctionName(node, sourceCode) {
// FunctionDeclarationノードの場合
if (node.type === 'FunctionDeclaration') {
// idプロパティから関数名を取得
const nameNode = node.id;
return sourceCode.getText(nameNode);
}
return null;
}
getText() メソッドを使うことで、ノードに対応するソースコードの文字列を簡単に取得できます。
次に、Token を使ってセミコロンの有無をチェックする例を見てみましょう。
typescript// Tokenを使ったセミコロンチェック
function hasSemicolon(node, sourceCode) {
// ノードの最後のTokenを取得
const lastToken = sourceCode.getLastToken(node);
// セミコロンかどうかをチェック
return lastToken && lastToken.value === ';';
}
getLastToken() メソッドにより、ノードの末尾にセミコロンがあるかどうかを判定できます。
さらに、コメントを取得する例も見てみましょう。
typescript// コメントの取得
function hasCommentBefore(node, sourceCode) {
// ノードの前にあるコメントを取得
const comments = sourceCode.getCommentsBefore(node);
// コメントが存在するかチェック
return comments.length > 0;
}
getCommentsBefore() を使えば、特定のノードの前に書かれたコメントを取得できます。
これは「特定の関数には必ずコメントを書く」といったルールを実装する際に役立ちます。
具体例
実際のコードとその AST 構造
ここでは、具体的な JavaScript コードがどのような AST 構造になるかを見ていきます。
サンプルコード
以下のシンプルな変数宣言を例にします。
javascript// 変数宣言の例
const greeting = 'Hello';
このコードは、定数 greeting を宣言し、文字列 "Hello" を代入しています。
AST 構造の詳細
上記のコードは、以下のような JSON 形式の AST に変換されます。
json{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "greeting"
},
"init": {
"type": "Literal",
"value": "Hello",
"raw": "\"Hello\""
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
この JSON 構造を見ると、以下のことがわかります。
- ルートは
Programノード body配列にVariableDeclarationが含まれる- 変数宣言の
kindは"const" declarations配列にVariableDeclaratorが含まれる- 変数名は
Identifierノードで表現され、nameプロパティに"greeting"が格納される - 初期値は
Literalノードで、valueに"Hello"が入る
下図は、このコードの AST 構造を視覚的に表しています。
mermaidflowchart TB
prog["Program"] --|body[0]|--> vardecl["VariableDeclaration<br />(kind: const)"]
vardecl --|declarations[0]|--> declr["VariableDeclarator"]
declr --|id|--> id["Identifier<br />(name: greeting)"]
declr --|init|--> lit["Literal<br />(value: Hello)"]
このように、シンプルなコードでも複数のノードが階層的に組み合わさって構造が形成されます。
カスタムルールの実装例
実際に ESLint のカスタムルールを作成してみましょう。
ここでは、「console.log を使用禁止にするルール」を例にします。
ルールの骨組み
ESLint のカスタムルールは、以下のような構造で定義します。
javascript// カスタムルールの基本構造
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'console.logの使用を禁止する',
},
},
create(context) {
// ルールの実装
return {};
},
};
meta オブジェクトにはルールのメタ情報を記述し、create 関数でルールのロジックを実装します。
ノードの監視
console.log は CallExpression ノードとして表現されます。
この CallExpression を監視し、callee が console.log であるかをチェックします。
javascript// CallExpressionノードを監視
create(context) {
return {
CallExpression(node) {
// このノードが関数呼び出しの場合に実行される
}
};
}
create 関数が返すオブジェクトのキーにノードタイプを指定することで、そのノードが検出されたときに関数が実行されます。
console.log の判定
次に、CallExpression が console.log であるかを判定します。
javascript// console.logかどうかを判定
create(context) {
return {
CallExpression(node) {
const callee = node.callee;
// MemberExpression (obj.prop形式) かチェック
if (callee.type === 'MemberExpression') {
const object = callee.object;
const property = callee.property;
// objectがconsole、propertyがlogかチェック
if (
object.type === 'Identifier' &&
object.name === 'console' &&
property.type === 'Identifier' &&
property.name === 'log'
) {
// console.logが見つかった場合の処理
}
}
}
};
}
callee が MemberExpression の場合、object と property を調べて console.log であるかを確認します。
エラーレポート
console.log が見つかったら、context.report() でエラーを報告します。
javascript// エラーを報告
if (
object.type === 'Identifier' &&
object.name === 'console' &&
property.type === 'Identifier' &&
property.name === 'log'
) {
context.report({
node: node,
message: 'console.logの使用は禁止されています',
});
}
context.report() に node と message を渡すことで、ESLint がエラーメッセージを出力します。
完成版のルール
すべてをまとめると、以下のようなカスタムルールが完成します。
javascript// 完成版:console.log禁止ルール
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'console.logの使用を禁止する',
},
},
create(context) {
return {
CallExpression(node) {
const callee = node.callee;
if (callee.type === 'MemberExpression') {
const object = callee.object;
const property = callee.property;
if (
object.type === 'Identifier' &&
object.name === 'console' &&
property.type === 'Identifier' &&
property.name === 'log'
) {
context.report({
node: node,
message:
'console.logの使用は禁止されています',
});
}
}
},
};
},
};
このルールを .eslintrc.js で有効化すれば、コード内の console.log がすべて検出されます。
SourceCode を活用した高度な例
次に、SourceCode オブジェクトを活用したより高度な例を見てみましょう。 「関数の前にコメントがない場合に警告する」というルールを作成します。
ルールの骨組みとノード監視
まず、FunctionDeclaration ノードを監視します。
javascript// 関数宣言を監視
create(context) {
const sourceCode = context.getSourceCode();
return {
FunctionDeclaration(node) {
// 関数宣言が見つかったときの処理
}
};
}
context.getSourceCode() で SourceCode オブジェクトを取得します。
コメントの確認
SourceCode の getCommentsBefore() メソッドを使って、関数の前にコメントがあるかをチェックします。
javascript// コメントの有無をチェック
FunctionDeclaration(node) {
// 関数の前のコメントを取得
const comments = sourceCode.getCommentsBefore(node);
// コメントがない場合
if (comments.length === 0) {
context.report({
node: node,
message: '関数の前にコメントを記述してください'
});
}
}
comments.length が 0 の場合、コメントが存在しないためエラーを報告します。
完成版のルール
完成版は以下のようになります。
javascript// 完成版:関数コメント必須ルール
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: '関数の前にコメントを必須にする',
},
},
create(context) {
const sourceCode = context.getSourceCode();
return {
FunctionDeclaration(node) {
const comments = sourceCode.getCommentsBefore(node);
if (comments.length === 0) {
context.report({
node: node,
message: '関数の前にコメントを記述してください',
});
}
},
};
},
};
このルールにより、コメントのない関数を検出できます。
AST を可視化する便利ツール
AST の構造を理解するには、実際にコードをパースして可視化してみるのが一番です。 以下のようなオンラインツールを使うと、手軽に AST を確認できます。
AST Explorer
AST Explorer は、最も有名な AST 可視化ツールです。 左側にコードを入力すると、右側にリアルタイムで AST が表示されます。
使い方は以下のとおりです。
- ブラウザで https://astexplorer.net/ にアクセス
- 左上の「Parser」で
espreeを選択(ESLint と同じパーサー) - 左側のエディタにコードを入力
- 右側に AST が JSON 形式で表示される
このツールを使えば、「このコードはどんなノードになるのか」を即座に確認できます。 カスタムルールを作成する際は、まず AST Explorer で対象のコードをパースして、どのノードを監視すべきかを調べると効率的です。
まとめ
本記事では、ESLint のカスタムルール作成に欠かせない、AST、ESTree、Token、SourceCode の基礎を解説しました。 これらの概念を理解することで、ESLint がどのようにコードを解析しているかが明確になったかと思います。
改めて要点をまとめると、以下のようになります。
- AST(抽象構文木) は、ソースコードを木構造で表現したもので、ESLint の解析の基盤です
- ESTree は、JavaScript AST の標準仕様で、ノードタイプや構造が明確に定義されています
- Token は字句単位の要素で、キーワード、識別子、演算子などが含まれます
- SourceCode は、ESLint が提供するヘルパーオブジェクトで、テキスト取得やコメント取得などの便利なメソッドがあります
カスタムルールを作成する際は、まず対象のコードがどのような AST 構造になるかを AST Explorer で確認し、適切なノードタイプを監視することが重要です。 そして、SourceCode オブジェクトのメソッドを活用することで、より柔軟で強力なルールを実装できます。
今回学んだ知識を活かして、ぜひプロジェクト固有の ESLint カスタムルールを作成してみてください。 コードの品質向上や、チーム内のコーディング規約の統一に大いに役立つはずです。
関連リンク
articleESLint と AST 入門:ESTree/Token/SourceCode を 10 分で把握
articleESLint 変更管理と段階リリース:CI のフェイルセーフ&ロールバック手順
articleESLint でアーキテクチャ境界を守る:層間 import を規制する設計パターン
articleESLint no-restricted-* 活用レシピ集:API 禁止・依存制限・危険パターン封じ込め
articleESLint × Vitest/Playwright:テスト環境のグローバルと型を正しく設定
articleESLint パーサ比較:espree と @typescript-eslint/parser の互換性と速度
articlegpt-oss 推論パラメータ早見表:temperature・top_p・repetition_penalty...その他まとめ
articleLangChain を使わない判断基準:素の API/関数呼び出しで十分なケースと見極めポイント
articleJotai エコシステム最前線:公式&コミュニティ拡張の地図と選び方
articleGPT-5 監査可能な生成系:プロンプト/ツール実行/出力のトレーサビリティ設計
articleFlutter の描画性能を検証:リスト 1 万件・画像大量・アニメ多用の実測レポート
articleJest が得意/不得意な領域を整理:単体・契約・統合・E2E の住み分け最新指針
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来