ESLint 自定义解析器(Custom Parsers)完全指南:从接口规范到发布与配置
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
自定义解析器(Custom Parser)是 ESLint 可扩展体系中最底层的扩展点之一:它负责把源代码转换成 ESLint 规则引擎能够分析的抽象语法树(AST),从而让 ESLint 支持检查非标准 JavaScript 语法、实验性语言特性以及自定义 DSL。本文以 ESLint 官方文档《Custom Parsers》为核心,结合当前仓库(eslint)中lib/languages/js/index.js、lib/services/parser-service.js等源码实现,系统讲解自定义解析器的接口规范、AST 要求、发布流程与配置方式,读完后你将能够独立编写、发布并接入一个可用的自定义解析器。
一、自定义解析器的作用与定位
ESLint 内置的默认解析器是 Espree,它能解析标准 ECMAScript 语法。当你的代码包含 Espree 无法识别的语法时(例如 TypeScript、JSX 之外的实验性特性、或某种自定义语法),就需要一个自定义解析器来接管"源代码 → AST"这一环节。
在 ESLint 的解析流程中,解析器只负责一件事:接收源代码,输出 AST。之后对 AST 的遍历、规则执行、作用域分析、报告等全部由 ESLint 核心完成。这一点可以从源码得到印证——在 lib/languages/js/index.js 的parse()方法中,ESLint 将解析器返回的 AST 与parserServices、visitorKeys、scopeManager一起封装成解析结果,再交给createSourceCode()构建SourceCode对象(见 lib/languages/js/source-code/source-code.js)。
从源码结构看,ESLint 对解析器的调用存在两条路径:若解析器实现了
parseForESLint()则优先调用它,否则退化为调用parse()并自动补全ast字段。这一点会在下文详细展开。
二、创建自定义解析器
2.1 两种接口方法:parse()与parseForESLint()
一个自定义解析器本质上是一个 JavaScript 对象,它必须提供以下两种方法之一:
parse(code, options):只返回 AST 对象,接口最简单;parseForESLint(code, options):返回一个包含ast及若干可选字段的对象,允许解析器进一步定制 ESLint 的行为。
两种方法都要求是对象自身的实例属性(instance / own property),签名约定如下:
| 参数 | 类型 | 说明 |
|---|---|---|
| 第一个参数 | string | 待解析的源代码文本 |
| 第二个参数(可选) | Object | 配置对象,即配置文件中的parserOptions |
第二个参数来自配置文件中的languageOptions.parserOptions(详见 docs/src/use/configure/language-options.md 一节的说明)。
一个最简的自定义解析器示例(记录每次解析耗时):
// customParser.js const espree = require("espree"); // Logs the duration it takes to parse each file. function parse(code, options) { const label = `Parsing file "${options.filePath}"`; console.time(label); const ast = espree.parse(code, options); console.timeEnd(label); return ast; // Only the AST is returned. } module.exports = { parse };2.2parse()的返回值
parse()方法应当直接返回 AST 对象(其结构要求见下文"AST Specification"一节)。
2.3parseForESLint()的返回值
parseForESLint()方法应当返回一个对象,其中ast为必填属性,services、scopeManager、visitorKeys为可选属性:
ast:必须包含 AST 对象(要求同parse()返回的 AST)。services:可以存放任意解析器相关的服务(例如节点级别的类型检查器)。该值会被暴露给规则,规则通过context.sourceCode.parserServices访问。默认值为空对象{}。scopeManager:可以是自定义的ScopeManager对象。自定义解析器可以为实验性/增强语法提供定制的作用域分析;默认使用 eslint-scope 创建的ScopeManager对象。scopeManager支持自 ESLint v4.14.0 起提供。支持scopeManager的 ESLint 版本会在parserOptions中提供eslintScopeManager: true属性,可用于特性检测。- 自 ESLint v10.0.0 起,
ScopeManager必须能自动解析代码中声明的全局变量引用,并提供实例方法addGlobals(names: string[]),该方法在全局作用域中按给定名称创建变量并解析指向它们的引用(该方法的接口说明见 docs/src/extend/scope-manager-interface.md)。
visitorKeys:用于定制 AST 遍历方式的对象。对象的键是 AST 节点的类型,每个值是一个数组,列出应当被遍历的节点属性名。默认值为 eslint-visitor-keys 的KEYS。visitorKeys支持自 ESLint v4.14.0 起提供。支持visitorKeys的 ESLint 版本会在parserOptions中提供eslintVisitorKeys: true属性,可用于特性检测。
特性检测提示:如果你的自定义解析器需要兼容旧版 ESLint,请优先依赖parserOptions.eslintScopeManager和parserOptions.eslintVisitorKeys这两个布尔标记来做能力探测,而不是直接检测方法是否存在。
2.4 ESLint 核心如何调用解析器(源码印证)
在 lib/languages/js/index.js 的parse()方法中,ESLint 组装传给解析器的parserOptions时,会强制注入以下字段(部分字段值来自languageOptions,解析器可直接读取):
const parserOptions = Object.assign( { ecmaVersion, sourceType }, languageOptions.parserOptions, { loc: true, range: true, tokens: true, comment: true, eslintVisitorKeys: true, eslintScopeManager: true, filePath, }, );随后按如下逻辑调用解析器并归一化结果:
const parseResult = typeof parser.parseForESLint === "function" ? parser.parseForESLint(textToParse, parserOptions) : { ast: parser.parse(textToParse, parserOptions) }; const { ast, services: parserServices = {}, visitorKeys = evk.KEYS, scopeManager } = parseResult;由此可以看到三个关键实现事实:
- 优先调用
parseForESLint():只要解析器实现了该方法,ESLint 就会走扩展路径;否则退回parse()并自动包装成{ ast }。 services、visitorKeys、scopeManager均有默认兜底:services缺省为{},visitorKeys缺省为eslint-visitor-keys的KEYS,scopeManager缺省为空(之后在createSourceCode()中由eslint-scope重新分析生成,见 lib/languages/js/index.js 中的analyzeScope())。- 解析错误不抛出:
parse()内部捕获解析异常并返回{ ok: false, errors: [...] },交由上层 lib/services/parser-service.js 转换为带Parsing error:前缀的致命错误消息。
此外,解析器接收到的parserOptions中还包含filePath(当前文件路径),因此像上文示例那样在parse()里读取options.filePath做按文件定制是完全可行的。
2.5 在解析器中声明元数据(meta)
为了便于调试和更高效地缓存自定义解析器,官方强烈建议在解析器对象的根级提供一个meta对象,声明name与version:
// preferred location of name and version module.exports = { meta: { name: "eslint-parser-custom", version: "1.2.3", }, };meta.name应当与 npm 上的包名一致;meta.version应当与 npm 上的包版本一致。
最稳妥的做法是直接从你自己的package.json中读取这两个字段注入meta,避免版本号维护不同步。
三、AST 规范(AST Specification)
自定义解析器生成的 AST 必须基于 ESTree 标准,同时还需要附带若干 ESLint 强制的额外信息,否则 ESLint 无法正确执行规则。
3.1 所有节点的通用要求(All Nodes)
所有节点都必须具备以下属性:
range(number[]):由两个数字组成的数组。两个数字都是基于 0 的索引,表示源代码字符数组中的位置;第一个是节点起始位置,第二个是节点结束位置。必须满足code.slice(node.range[0], node.range[1])恰好等于该节点的文本。注意:该范围不包含节点周围可能存在的空格/括号。loc(SourceLocation):不能为null。虽然 ESTree 规范将loc定义为可空(nullable),但 ESLint 强制要求该属性存在。SourceLocation#source属性可以为undefined,ESLint 不使用该属性。
所有节点的parent属性必须可重写(rewritable)。在规则访问 AST 之前,ESLint 会在遍历过程中为每个节点写入其父节点引用,因此解析器不能把parent冻结或设置成不可写。
3.2Program节点的要求
Program节点必须提供tokens和comments两个属性,两者都是下述Token接口的数组:
interface Token { type: string; loc: SourceLocation; // See the "All Nodes" section for details of the `range` property. range: [number, number]; value: string; }tokens(Token[]):影响程序行为的令牌数组。令牌之间可以存在任意空白,因此规则通过检查Token#range来判断令牌之间的空白。该数组必须按Token#range[0]升序排列。comments(Token[]):注释令牌数组,同样必须按Token#range[0]升序排列。
所有令牌与注释的range索引不得相互重叠。
3.3Literal节点的要求
Literal节点必须包含raw属性:
raw(string):该字面量的源代码文本,即code.slice(node.range[0], node.range[1])。
四、将自定义解析器打包并发布到 npm
要发布你的自定义解析器,按以下步骤操作:
- 按照上文"创建自定义解析器"一节编写解析器实现;
- 为自定义解析器创建 npm 包;
- 在
package.json中,将main字段设置为导出自定义解析器的文件路径; - 发布 npm 包。
发布完成后,在项目中安装该包(以eslint-parser-myparser为例):
npm install eslint-parser-myparser --save-dev # 或使用 pnpm / yarn pnpm add -D eslint-parser-myparser yarn add -D eslint-parser-myparser4.1 在 flat config 中配置解析器
在eslint.config.js中通过languageOptions.parser属性接入自定义解析器:
// eslint.config.js const { defineConfig } = require("eslint/config"); const myparser = require("eslint-parser-myparser"); module.exports = defineConfig([ { languageOptions: { parser: myparser, }, // ... rest of configuration }, ]);4.2 在 legacy(.eslintrc)配置中配置解析器
使用传统配置格式时,parser属性写成字符串形式:
// .eslintrc.js module.exports = { parser: "eslint-parser-myparser", // ... rest of configuration };关于解析器配置的更完整说明(包括parser与parserOptions的配合方式、ecmaVersion/sourceType自动透传规则等),可继续阅读 docs/src/use/configure/parser.md。
使用提示:在 flat config 中,
languageOptions.parserOptions用于向解析器透传专属选项;ESLint 同时还会把languageOptions.ecmaVersion与languageOptions.sourceType传给所有解析器。若需要给解析器传递与languageOptions不同的取值,可在parserOptions中单独覆盖,这些值对解析器而言优先级更高。
五、完整示例:带 parserServices 的自定义解析器
下面是官方文档给出的一个简单但完整的自定义解析器,它为规则暴露了一个context.sourceCode.parserServices.foo()服务方法:
// awesome-custom-parser.js var espree = require("espree"); function parseForESLint(code, options) { return { ast: espree.parse(code, options), services: { foo: function () { console.log("foo"); }, }, scopeManager: null, visitorKeys: null, }; } module.exports = { parseForESLint };接入到 flat config:
// eslint.config.js const { defineConfig } = require("eslint/config"); module.exports = defineConfig([ { languageOptions: { parser: require("./path/to/awesome-custom-parser"), }, }, ]);接入到 legacy 配置:
// .eslintrc.json { "parser": "./path/to/awesome-custom-parser.js" }在这个示例中,scopeManager和visitorKeys显式传null,ESLint 会自动回退到默认行为:scopeManager由eslint-scope基于 AST 重新分析生成(源码见 lib/languages/js/index.js 的analyzeScope()),visitorKeys则使用eslint-visitor-keys的KEYS。规则中访问parserServices的实现位置在 lib/languages/js/source-code/source-code.js(this.parserServices = parserServices || {})。
对于更复杂的解析器实现,可以参考@typescript-eslint/parser的源码:它同时运用了parseForESLint返回的ast、services(提供类型信息给类型感知规则)、scopeManager(TypeScript 作用域分析)与visitorKeys(TS 特有节点遍历),是目前生态中自定义解析器最完整的参照实现。
六、调试与常见陷阱
- AST 合法性:ESLint 的
SourceCode构造时会执行validate(ast)校验(见 lib/languages/js/source-code/source-code.js),缺失range、loc为null、Program缺少tokens/comments都会导致校验失败。写解析器时应优先保证这四项基础要求。 - shebang 处理:ESLint 在调用解析器前会把 shebang 行(如
#!/usr/bin/env node)替换为普通注释(//...)再交给解析器,并在后续恢复为Shebang注释类型(见 lib/languages/js/index.js 与 lib/languages/js/source-code/source-code.js),因此解析器通常不需要自行处理 shebang。 - 解析错误处理:
parse()/parseForESLint()抛出异常即可,ESLint 会捕获并转换为Parsing error: <message>的致命错误;不要自行吞掉异常。 parent引用:不要在解析器内部为节点填充parent属性,ESLint 遍历时会统一设置,解析器只需保证该属性可写。
七、相关文档导航
- 解析器配置进阶(含第三方解析器列表与
parserOptions详解):docs/src/use/configure/parser.md parserOptions(languageOptions.parserOptions)的完整说明:docs/src/use/configure/language-options.mdScopeManager接口规范(含addGlobals等方法的详细语义):docs/src/extend/scope-manager-interface.md- ESLint 可扩展点总览(解析器、处理器、规则、格式化器、共享配置):docs/src/extend/index.md
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考