Tree-sitter 简明教程

本文将介绍使用 Tree-sitter 解析 JavaScript 代码的基础知识,以及如何用它解析 Express API 路由(包括中间件)。

Tree-sitter 简明教程
AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | PLC在线仿真器 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo

Tree-sitter 是一个强大的解析器生成工具,能够帮助你构建高效、增量的编程语言解析器。

无论你是构建代码分析器、代码检查工具(linter),还是编辑器,Tree-sitter 都能让你深入代码内部,借助其语法树获取结构化的语言语法洞察。

本文将介绍使用 Tree-sitter 解析 JavaScript 代码的基础知识,以及如何用它解析 Express API 路由(包括中间件)。

读完本文,你将能够轻松构建自己的自定义代码分析工具。

1、Tree-sitter 可以用来做什么?

Tree-sitter 为以结构化方式分析和处理代码开辟了无限可能。以下是 Tree-sitter 的一些常见应用场景:

1.1 语法高亮

Tree-sitter 被广泛用于 Visual Studio Code、Atom 等代码编辑器中的语法高亮。通过将代码解析为语法树,Tree-sitter 可以帮助识别语言构造(如关键字、变量和函数)并应用不同的颜色,从而提升代码可读性和开发体验。

1.2 代码导航与重构

Tree-sitter 让工具能够理解代码结构,使导航和重构变得更加容易。借助语法树,你可以查找函数定义、跟踪变量使用情况,甚至可以在整个代码库中高精度地执行自动化重构,例如重命名变量或函数。

1.3 静态分析与代码检查

Tree-sitter 有助于编写自定义代码检查工具。这些工具可以在不执行代码的情况下分析代码,检测常见问题或强制执行风格规范。Tree-sitter 生成的语法树允许进行比传统基于正则表达式的 linter 更深入的分析,从而可以检查未使用变量、不可达代码等复杂模式。

1.4 代码补全与自动完成

Tree-sitter 可用于为 IDE 和代码编辑器提供自动补全功能。通过分析代码的语法树,它可以根据当前上下文预测下一个合法 token 或函数签名,从而提升编码体验。

1.5 代码格式化与格式检查

Tree-sitter 让编写自动格式化代码的工具变得更加容易。通过将代码解析为语法树,你可以以一致的格式重建代码,并以编程方式应用缩进规则和格式化规范。

1.6 自定义代码检查器与分析器

你可以使用 Tree-sitter 编写针对特定代码库或编程语言的自定义分析工具。例如,你可以编写一个自定义检查器,检查所有函数声明是否具有适当的文档注释,或代码是否使用了已弃用的 API 方法。

1.7 文档生成

Tree-sitter 可用于自动化文档生成:通过分析函数签名、注释和代码结构,直接从源代码生成最新的 API 文档,既节省时间又保证一致性。

1.8 构建自定义 IDE 功能

如果你正在构建自己的集成开发环境(IDE)或插件,Tree-sitter 可以帮助你添加上下文感知的自动补全、错误检测、内联文档等强大功能。借助语法树,你的 IDE 可以提供更智能的代码建议和实时错误检测。

9. 解析非编程语言

虽然 Tree-sitter 常用于编程语言,但它也可以适配解析其他结构化文本格式,如 JSON、Markdown,甚至是领域特定语言(DSL)。这使它成为构建需要理解自定义格式(超越典型编程语言)的工具时的多面手。

2、基础设置

2.1 什么是 Tree-sitter?

Tree-sitter 是一个解析库,旨在从各种编程语言的源代码中高效生成具体的语法树。

这些语法树为你提供关于代码结构的详细信息,从而实现更强大的分析和操作。

你可以将 Tree-sitter 用于多种任务,例如:

  1. 构建语法感知的编辑器
  2. 分析代码
  3. 实现 linter 或格式化工具
  4. 从代码中提取信息

2.2 安装 Tree-sitter

在开始使用 Tree-sitter 之前,让我们先安装必要的包:

  1. 首先,确保你已安装 node.js
  2. 然后,使用 npm 安装 Tree-sitter:
npm install tree-sitter tree-sitter-javascript

tree-sitter-javascript 包为 JavaScript 代码提供了解析器。

2.3 解析基本 JavaScript 代码

现在,让我们创建一个基本示例,使用 Tree-sitter 解析一个简单的 JavaScript 函数。

const Parser = require("tree-sitter");
const JavaScript = require("tree-sitter-javascript");

// Create a parser
const parser = new Parser();
parser.setLanguage(JavaScript);

// Sample code to parse
const code = `
function greet(name) {
  return 'Hello, ' + name + '!';
}
`;

// Parse it
const tree = parser.parse(code);

// Print the syntax tree
console.log(tree.rootNode.toString());

说明:

  1. 我们创建一个 Parser 对象,并使用 setLanguage() 将其语言设置为 JavaScript。
  2. 我们定义一个简单的 JavaScript 函数 greet,它接受一个参数 name 并返回问候消息。
  3. 我们解析代码并得到一个语法树对象。树的 rootNode 表示抽象语法树(AST)的根节点。
  4. 最后,我们使用 tree.rootNode.toString() 打印语法树,得到 AST 的字符串表示形式。

示例输出:

(program
  (function_declaration
    name: (identifier)
    parameters: (formal_parameters (identifier))
    body: (statement_block
      (return_statement
        (binary_expression
          left: (binary_expression
            left: (string (string_fragment))
            right: (identifier))
          right: (string (string_fragment)))))))

该输出展示了 greet 函数的树形结构。

树中的每个节点都代表一种语法构造,你可以遍历这个结构来分析代码的特定部分。

3、解析带中间件的 Express API 路由

在介绍了解析 JavaScript 代码的基础知识之后,让我们进入一个更高级的示例:解析 Express.js API 路由及其中间件函数。

在这个示例中,我们将使用一个使用中间件的小型 Express API,并提取相关信息,例如 HTTP 方法(GETPOST 等)、API 路由以及所使用的中间件。

示例 Express API 代码:

const express = require('express');
const app = express();
const router = express.Router();

const authenticateMiddleware = (req, res, next) => { ... };
const logRequestMiddleware = (req, res, next) => { next(); };

app.use('/api', authenticateMiddleware);
app.get('/api/users', authenticateMiddleware, (req, res) => {
  res.send('User list');
});

router.post('/api/login', logRequestMiddleware, loginHandler);

3.1 解析 Express API

现在,让我们使用 Tree-sitter 编写一个解析器来分析上述 Express API 代码,并提取路由和中间件。

const Parser = require("tree-sitter");
const JavaScript = require("tree-sitter-javascript");
const { Query } = require("tree-sitter");

const parser = new Parser();
parser.setLanguage(JavaScript);

// Sample Express code with middleware
const code = `
const express = require('express');

// Example Express API Code ...
`;

const tree = parser.parse(code);
const root = tree.rootNode;
console.log(tree.rootNode.toString()); // 👈 Debug: print AST tree

// Corrected query to capture routes and middleware
const query = new Query(
  JavaScript,
  `
(call_expression
  function: (member_expression
    object: (identifier) @router.obj
    property: (property_identifier) @http.method)
  arguments: (arguments
    (string) @route.path
    (_)* @middleware.name))
`
);

const captures = query.captures(root);

let currentAPI = {};
for (const { name, node } of captures) {
  if (name === "router.obj") {
    currentAPI.router = node.text;
  } else if (name === "http.method") {
    currentAPI.method = node.text.toUpperCase();
  } else if (name === "route.path") {
    currentAPI.path = node.text;
  } else if (name === "middleware.name") {
    if (!currentAPI.middleware) {
      currentAPI.middleware = [];
    }
    currentAPI.middleware.push(node.text);
  }

  // Once all data is captured, print the result
  if (currentAPI.path && currentAPI.method) {
    const middlewareList = currentAPI.middleware
      ? currentAPI.middleware.join(", ")
      : "No Middleware";
    console.log(
      `${currentAPI.method} ${currentAPI.path} (${currentAPI.router}) with Middleware: ${middlewareList}`
    );
    currentAPI = {}; // Reset for next match
  }
}

说明:

  1. 我们定义了一组 Express API 路由,其中一些使用了中间件。
  2. Query 构造函数中定义的 query 使用 Tree-sitter 查询语言查找 call_expression 节点,这些节点对应 app.get()app.post()app.use() 等方法调用。
  3. captures 函数收集查询结果,使我们能够提取 HTTP 方法、路由路径以及路由上附加的任何中间件。
  4. 最后,我们打印每条路由及其关联的中间件。

预期输出:

lovestaco@i3nux-mint:~/pers/tree-sitter-ex$ node parseAPI.js 
(program
  (lexical_declaration
    (variable_declarator
      name: (identifier)
      value: (call_expression
        function: (identifier)
        arguments: (arguments
          (string (string_fragment))))))

  (lexical_declaration
    (variable_declarator
      name: (identifier)
      value: (call_expression
        function: (identifier)
        arguments: (arguments))))

  (lexical_declaration
    (variable_declarator
      name: (identifier)
      value: (call_expression
        function: (member_expression
          object: (identifier)
          property: (property_identifier))
        arguments: (arguments))))

  (lexical_declaration
    (variable_declarator
      name: (identifier)
      value: (arrow_function
        parameters: (formal_parameters
          (identifier)
          (identifier)
          (identifier))
        body: (statement_block (ERROR)))))

  (lexical_declaration
    (variable_declarator
      name: (identifier)
      value: (arrow_function
        parameters: (formal_parameters
          (identifier)
          (identifier)
          (identifier))
        body: (statement_block
          (expression_statement
            (call_expression
              function: (identifier)
              arguments: (arguments)))))))

  (expression_statement
    (call_expression
      function: (member_expression
        object: (identifier)
        property: (property_identifier))
      arguments: (arguments
        (string (string_fragment))
        (identifier))))

  (expression_statement
    (call_expression
      function: (member_expression
        object: (identifier)
        property: (property_identifier))
      arguments: (arguments
        (string (string_fragment))
        (identifier)
        (arrow_function
          parameters: (formal_parameters
            (identifier)
            (identifier))
          body: (statement_block
            (expression_statement
              (call_expression
                function: (member_expression
                  object: (identifier)
                  property: (property_identifier))
                arguments: (arguments
                  (string (string_fragment))))))))))

  (expression_statement
    (call_expression
      function: (member_expression
        object: (identifier)
        property: (property_identifier))
      arguments: (arguments
        (string (string_fragment))
        (identifier)
        (identifier)))))

输出结果:

USE '/api' (app) with Middleware: No Middleware
GET '/api/users' (app) with Middleware: authenticateMiddleware
SEND 'User list' (res) with Middleware: authenticateMiddleware
POST '/api/login' (router) with Middleware: No Middleware

4、结束语

Tree-sitter 是构建解析器和分析代码结构的多功能高效工具。

通过使用 Tree-sitter 强大的语法树和查询能力,你可以从代码中提取深入见解,自动化代码检查或文档生成等任务,甚至可以构建高级代码分析工具。


原文链接:Getting Started with Tree-sitter: Syntax Trees and Express API Parsing

汇智网翻译整理,转载请标明出处