Tree-sitter 简明教程
本文将介绍使用 Tree-sitter 解析 JavaScript 代码的基础知识,以及如何用它解析 Express API 路由(包括中间件)。
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 用于多种任务,例如:
- 构建语法感知的编辑器
- 分析代码
- 实现 linter 或格式化工具
- 从代码中提取信息
2.2 安装 Tree-sitter
在开始使用 Tree-sitter 之前,让我们先安装必要的包:
- 首先,确保你已安装
node.js。 - 然后,使用 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());
说明:
- 我们创建一个
Parser对象,并使用setLanguage()将其语言设置为 JavaScript。 - 我们定义一个简单的 JavaScript 函数
greet,它接受一个参数name并返回问候消息。 - 我们解析代码并得到一个语法树对象。树的
rootNode表示抽象语法树(AST)的根节点。 - 最后,我们使用
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 方法(GET、POST 等)、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
}
}
说明:
- 我们定义了一组 Express API 路由,其中一些使用了中间件。
Query构造函数中定义的query使用 Tree-sitter 查询语言查找call_expression节点,这些节点对应app.get()、app.post()、app.use()等方法调用。captures函数收集查询结果,使我们能够提取 HTTP 方法、路由路径以及路由上附加的任何中间件。- 最后,我们打印每条路由及其关联的中间件。
预期输出:
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
汇智网翻译整理,转载请标明出处