ブログ

フロントエンドツール開発入門:OXC Parserでコード構造を理解する

AST(抽象構文木)をゼロから学び、OXC Parserを使ってJavaScriptコードを解析し、最初のコード分析ツールを構築しましょう。

LibDoc Team 2026年3月6日 OXC 連載 59 分で読める
#oxc #parser #ast #コード解析 #rust

フロントエンドツール開発入門:OXC Parserでコード構造を理解する

ESLintはどうやってコードの問題を発見するのか?BabelはどうやってES6をES5に変換するのか?Prettierはどうやってコードを再フォーマットするのか?

答えはすべて同じ核心技術に集約されます — AST(抽象構文木)。そしてParser(パーサー)は、コードをASTに変換するツールです。

今日は、OXC Parserを使ってこの魔法のベールを取り払います。

ASTとは?

分かりやすい例えで理解する

宝の地図があり、「大きな木から出発し、北に100歩進み、次に東に50歩進み、掘る」と書いてあると想像してください。

コンピュータはこの文章を理解できる構造に変換する必要があります:

命令
├── 出発点: 大きな木
├── 手順
│   ├── 手順1: 北に100歩進む
│   └── 手順2: 東に50歩進む
└── 動作: 掘る

ASTはコードの「ツリー状の地図」です。コード文字列をツリーに変換し、各ノードがコード内の要素を表します。

シンプルな例

このコードを見てください:

const greeting = "Hello, World!";

このAST構造は大体このようになります:

Program (プログラム)
└── VariableDeclaration (変数宣言)
    ├── kind: "const"
    └── declarations
        └── VariableDeclarator
            ├── id: Identifier (識別子)
            │   └── name: "greeting"
            └── init: Literal (リテラル)
                └── value: "Hello, World!"

なぜフロントエンドツールにASTが必要なのか?

コードを「理解」する必要があるすべてのツールがASTを必要とします:

ツール用途ASTの役割
ESLintコードチェックASTを走査して問題パターンを発見
Babelコード変換ASTノードを変更して新しいコードを生成
PrettierコードフォーマットASTに基づいて再フォーマット
TypeScript型チェックAST上で型推論を実行
WebpackコードバンドルAST内の依存関係を分析

Parserの役割

Parser(パーサー)のワークフロー:

コード文字列 → 字句解析 → 構文解析 → AST

ブラウザも使用している

ブラウザがJavaScriptを実行する際:

  1. 解析フェーズ:コード文字列をASTに変換
  2. コンパイルフェーズ:ASTをバイトコードにコンパイル
  3. 実行フェーズ:バイトコードを実行

Babel、ESLintも使用している

  • @babel/parser:Babelのパーサー
  • @typescript-eslint/parser:ESLintのTypeScriptパーサー
  • @oxc/parser:OXCの高性能パーサー

OXC Parserの特徴:

  • 究極の速度:@babel/parserより20-50倍速い
  • 完全サポート:JavaScript、TypeScript、JSX
  • 高い互換性:出力形式はESTreeと互換

実践:Parserでコードを解析

実際に試してみましょう!

OXC Parserのインストール

# プロジェクト内インストール
npm install @oxc-parser

# またはpnpmを使用
pnpm add @oxc-parser

最もシンプルな例

parse-demo.js を作成:

// parse-demo.js
import { parseSync } from "@oxc-parser";

const code = `const greeting = "Hello, OXC!";`;

const ast = parseSync(code, {
  lang: "js",
});

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

実行:

node parse-demo.js

出力:

{
  "type": "Program",
  "start": 0,
  "end": 32,
  "body": [
    {
      "type": "VariableDeclaration",
      "kind": "const",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "greeting"
          },
          "init": {
            "type": "Literal",
            "value": "Hello, OXC!"
          }
        }
      ]
    }
  ]
}

非同期解析

大きなファイルには非同期メソッドが推奨されます:

import { parseAsync } from "@oxc-parser";

const code = `// 大量のコード...`;

const ast = await parseAsync(code, {
  lang: "js",
});

AST出力をステップバイステップで理解

もう少し複雑な例を分析しましょう:

function add(a, b) {
  return a + b;
}

解析後のAST構造:

import { parseSync } from "@oxc-parser";

const code = `
function add(a, b) {
  return a + b;
}
`;

const ast = parseSync(code, { lang: "js" });

トップレベル構造:Program

ASTのルートノードは Program です:

{
  type: "Program",
  body: [...],  // すべてのトップレベル文を含む
  sourceType: "script"
}

関数宣言:FunctionDeclaration

{
  type: "FunctionDeclaration",
  id: {
    type: "Identifier",
    name: "add"          // 関数名
  },
  params: [             // パラメータリスト
    { type: "Identifier", name: "a" },
    { type: "Identifier", name: "b" }
  ],
  body: {               // 関数本体
    type: "BlockStatement",
    body: [...]
  }
}

Return文:ReturnStatement

{
  type: "ReturnStatement",
  argument: {           // 戻り値式
    type: "BinaryExpression",
    operator: "+",
    left: { type: "Identifier", name: "a" },
    right: { type: "Identifier", name: "b" }
  }
}

一般的なノードタイプ

ノードタイプ説明
Identifier識別子(変数名、関数名)foo, bar
Literalリテラル123, "hello", true
VariableDeclaration変数宣言const x = 1;
FunctionDeclaration関数宣言function foo() {}
CallExpression関数呼び出しfoo()
BinaryExpression二項演算a + b
MemberExpressionメンバーアクセスobj.prop
ArrowFunctionExpressionアロー関数(x) => x * 2

実用プロジェクト:コード内の関数数をカウント

Parserを使って実用的なツールを作成しましょう — コード内で定義された関数の数をカウントします。

カウントスクリプトの作成

count-functions.js を作成:

// count-functions.js
import { parseSync } from "@oxc-parser";
import fs from "fs";

function countFunctions(code) {
  const ast = parseSync(code, { lang: "js" });

  let functionCount = 0;
  let arrowFunctionCount = 0;

  // ASTを再帰的に走査
  function traverse(node) {
    if (!node || typeof node !== "object") return;

    // ノードタイプをチェック
    if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") {
      functionCount++;
    }
    if (node.type === "ArrowFunctionExpression") {
      arrowFunctionCount++;
    }

    // すべての子ノードを再帰的に走査
    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);

  return {
    regularFunctions: functionCount,
    arrowFunctions: arrowFunctionCount,
    total: functionCount + arrowFunctionCount,
  };
}

// テスト
const testCode = `
function add(a, b) {
  return a + b;
}

const multiply = function(a, b) {
  return a * b;
};

const subtract = (a, b) => a - b;

const numbers = [1, 2, 3].map(n => n * 2);
`;

const result = countFunctions(testCode);
console.log("関数統計結果:", result);

実行結果:

関数統計結果: {
  regularFunctions: 2,
  arrowFunctions: 2,
  total: 4
}

より実用的なバージョン:ファイル分析

// analyze-file.js
import { parseSync } from "@oxc-parser";
import fs from "fs";

function analyzeFile(filePath) {
  const code = fs.readFileSync(filePath, "utf-8");
  const ast = parseSync(code, {
    lang: filePath.endsWith(".ts") ? "ts" : "js",
  });

  const stats = {
    functions: 0,
    classes: 0,
    imports: 0,
    exports: 0,
    variables: 0,
  };

  function traverse(node) {
    if (!node || typeof node !== "object") return;

    switch (node.type) {
      case "FunctionDeclaration":
      case "FunctionExpression":
      case "ArrowFunctionExpression":
        stats.functions++;
        break;
      case "ClassDeclaration":
        stats.classes++;
        break;
      case "ImportDeclaration":
        stats.imports++;
        break;
      case "ExportNamedDeclaration":
      case "ExportDefaultDeclaration":
        stats.exports++;
        break;
      case "VariableDeclaration":
        stats.variables += node.declarations.length;
        break;
    }

    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);
  return stats;
}

// 使用例
const stats = analyzeFile("./src/index.js");
console.log("ファイル分析結果:", stats);

TypeScriptの解析

OXC ParserはTypeScriptを十分にサポートしています:

import { parseSync } from "@oxc-parser";

const tsCode = `
interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return \`Hello, \${user.name}!\`;
}
`;

const ast = parseSync(tsCode, {
  lang: "ts",
  // 型情報を保持
  astType: "ts",
});

console.log(ast);

JSX / TSXの解析

import { parseSync } from "@oxc-parser";

const jsxCode = `
function Button({ onClick, children }) {
  return (
    <button className="btn" onClick={onClick}>
      {children}
    </button>
  );
}
`;

const ast = parseSync(jsxCode, {
  lang: "jsx",
});

パフォーマンスの優位性

OXC Parserと@babel/parserのパフォーマンスを比較しましょう。

テストコード

// benchmark.js
import { parseSync as oxcParse } from "@oxc-parser";
import { parse as babelParse } from "@babel/parser";

// 大量のテストコードを生成
function generateCode(lines) {
  let code = "";
  for (let i = 0; i < lines; i++) {
    code += `const variable${i} = ${i};\n`;
    code += `function function${i}() { return variable${i} * 2; }\n`;
  }
  return code;
}

const largeCode = generateCode(1000); // 2000行のコード

// OXC Parserをテスト
console.time("OXC Parser");
for (let i = 0; i < 10; i++) {
  oxcParse(largeCode, { lang: "js" });
}
console.timeEnd("OXC Parser");

// Babel Parserをテスト
console.time("Babel Parser");
for (let i = 0; i < 10; i++) {
  babelParse(largeCode, {
    sourceType: "module",
  });
}
console.timeEnd("Babel Parser");

テスト結果

OXC Parser: 45ms
Babel Parser: 890ms

OXC ParserはBabel Parserより約20倍速い!

パフォーマンス比較表

コード規模@babel/parser@oxc/parser向上倍率
100行15ms1ms15x
1000行89ms4ms22x
10000行890ms45ms20x

実際の応用シーン

1. カスタムESLintルール

// console.logの使用を検出
function noConsoleLog(code) {
  const ast = parseSync(code, { lang: "js" });
  const violations = [];

  function traverse(node) {
    if (!node || typeof node !== "object") return;

    if (
      node.type === "CallExpression" &&
      node.callee?.object?.name === "console" &&
      node.callee?.property?.name === "log"
    ) {
      violations.push({
        line: node.loc?.start?.line,
        message: "console.logの使用を避けてください",
      });
    }

    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);
  return violations;
}

2. コード統計ツール

// コード複雑度を計算
function calculateComplexity(code) {
  const ast = parseSync(code, { lang: "js" });
  let complexity = 1; // 基礎複雑度

  function traverse(node) {
    if (!node || typeof node !== "object") return;

    // 複雑度を増加させる構造
    if (["IfStatement", "ForStatement", "WhileStatement", "SwitchCase", "CatchClause"].includes(node.type)) {
      complexity++;
    }
    if (node.type === "ConditionalExpression") {
      complexity++;
    }
    if (node.type === "LogicalExpression") {
      complexity++;
    }

    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);
  return complexity;
}

3. コード生成ツール

// すべてのエクスポート関数名を抽出
function extractExports(code) {
  const ast = parseSync(code, { lang: "js" });
  const exports = [];

  for (const node of ast.body) {
    if (node.type === "ExportNamedDeclaration") {
      if (node.declaration?.type === "FunctionDeclaration") {
        exports.push(node.declaration.id.name);
      }
    }
    if (node.type === "ExportDefaultDeclaration") {
      if (node.declaration.type === "Identifier") {
        exports.push(`default: ${node.declaration.name}`);
      }
    }
  }

  return exports;
}

まとめ

この記事ではOXC Parserの核心概念と使い方を紹介しました:

内容説明
ASTコードのツリー表現、フロントエンドツールの基礎
Parserコード文字列をASTに変換
ノードタイプIdentifier、Literal、FunctionDeclarationなど
ASTの走査すべてのノードを再帰的に走査して分析

Parserの核心価値:コードをプログラムが「理解」し「操作」できるようにする。

次のステップ

ParserのAPIオプションをさらに知りたいですか?**OXC日本語ドキュメント - Parserセクション**をご覧ください。

次回の記事では、OXC Transformer を学びます — TypeScriptをJavaScriptにコンパイルする方法!


💡 関連記事