ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ESLint 自定义格式化器(Custom Formatter)完整指南:编写、参数传递、终端适配与 npm 分发

ESLint 自定义格式化器(Custom Formatter)完整指南:编写、参数传递、终端适配与 npm 分发 ESLint 自定义格式化器Custom Formatter完整指南编写、参数传递、终端适配与 npm 分发【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint自定义格式化器Custom Formatter是 ESLint 官方推荐的结果输出扩展点让你可以把 lint 结果渲染成任意形态——无论是特定文件格式、自定义展示风格还是面向某个工具的优化输出。本文以 ESLint 仓库的官方文档 docs/src/extend/custom-formatters.md 为骨架结合 lib/eslint/eslint.js、lib/cli-engine/formatters 等源码实现完整讲解格式化器的函数契约、results/context两个参数、参数传递技巧、终端输出规范以及打包分发流程。读完本文你将能够独立编写、调试并发布一个生产可用的自定义格式化器。自定义格式化器定位与使用方式格式化器的职责是消费 lint 结果、产出展示文本。ESLint 内置了 stylish、json、json-with-metadata、html 等格式化器可以直接通过-f参数使用而当内置格式化器无法满足你的输出需求时就可以编写自定义格式化器。自定义格式化器的使用有两种典型形态直接放进项目将格式化器文件放在项目目录内通过相对路径引用打包成 npm 包以eslint-formatter-*命名发布供多个项目共享。无论哪种形态格式化器的本质都是一个接收results和context并返回字符串的函数。创建自定义格式化器函数即格式化器同步格式化器每个格式化器就是一个普通函数接收results对象和context作为参数返回一个字符串。例如内置的 JSON 格式化器 在仓库中的实现正是如此见 lib/cli-engine/formatters/json.js//my-awesome-formatter.js module.exports function (results, context) { return JSON.stringify(results, null, 2); };从源码可以看到内置 JSON 格式化器甚至没有使用context直接对results做序列化——这说明context是可选的格式化器只需要返回字符串即可。异步格式化器从 ESLint v8.4.0 起格式化器也可以是一个async 函数便于在格式化前执行异步任务如读取额外文件、请求远程数据等//my-awesome-formatter.js module.exports async function (results) { const formatted await asyncTask(); return formatted; };通过命令行运行自定义格式化器使用-f或--format命令行参数 即可让 ESLint 使用自定义格式化器eslint -f ./my-awesome-formatter.js src/关键规则引用项目内自定义格式化器时路径必须以句点.开头例如./my-awesome-formatter.js或../formatters/my-awesome-formatter.js。这一点在 lib/eslint/eslint.js 的loadFormatter解析逻辑中有直接体现格式化器名称只要包含/且不以包命名空间开头就会被当作相对cwd的文件路径解析否则会被当作 npm 包名或内置格式化器名称处理详见下文loadFormatter 解析流程一节。results参数一份 LintResult 数组results是传入格式化器的数组其中每个元素对应一个被检查文件的LintResult对象包含该文件的所有消息与统计信息。以下是一个典型输出两条消息分别命中curly与no-process-exit规则[ { filePath: /path/to/a/file.js, messages: [ { ruleId: curly, severity: 2, message: Expected { after if condition., line: 2, column: 1, }, { ruleId: no-process-exit, severity: 2, message: Dont use process.exit(); throw an error instead., line: 3, column: 1, }, ], errorCount: 2, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: var err doStuff();\nif (err) console.log(failed tests: err);\nprocess.exit(1);\n, }, { filePath: /path/to/Gruntfile.js, messages: [], errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, }, ];使用要点messages数组中的每条消息都包含ruleId、severity1为警告2为错误、message、line、column等字段无问题的文件也会作为一条LintResult出现在results中messages为空、各计数为0编写聚合逻辑时必须兼容这种情况注意在仓库源码 lib/eslint/eslint.js 中results在传给格式化器之前会先通过compareResultsByFilePath见 lib/eslint/eslint.js按filePath排序因此不同格式化器调用之间文件输出顺序是确定且一致的。context参数运行环境与附加数据格式化器函数的第二个参数是context对象提供当前运行环境信息与规则元数据。其属性如下属性类型说明colorboolean可选设置--color时为true设置--no-color时为false两者都未设置时该属性不出现cwdstring当前工作目录来源于 ESLint 类的cwd构造选项maxWarningsExceededobject可选当设置了--max-warnings且警告数超过上限时出现包含maxWarnings上限值与foundWarnings实际警告数两个属性rulesMetaobject各规则的meta属性值规则定义、文档链接、fixable、schema 等例如如果只运行了no-extra-semi规则context可能长这样{ cwd: /path/to/cwd, maxWarningsExceeded: { maxWarnings: 5, foundWarnings: 6 }, rulesMeta: { no-extra-semi: { type: suggestion, docs: { description: disallow unnecessary semicolons, recommended: true, url: https://eslint.org/docs/rules/no-extra-semi }, fixable: code, schema: [], messages: { unexpected: Unnecessary semicolon. } } }, }从源码看这个context对象由 lib/eslint/eslint.js 中的format()方法组装它把resultsMeta含maxWarningsExceeded等信息与cwd合并并把rulesMeta实现为惰性 getter——只有在格式化器真正访问context.rulesMeta时才调用eslint.getRulesMetaForResults(results)见 lib/eslint/eslint.js去解析规则元数据。因此没有使用rulesMeta的简单格式化器不会承担额外的元数据解析开销。注意兼容遗留环境如果 lint 是通过已废弃的CLIEngine类执行的context参数的值可能有所不同因为其取值由 API 使用者自行决定。若你的格式化器需要支持遗留环境请务必先校验context是否符合预期。向格式化器传递参数格式化器函数本身只接收results与context两个参数不会收到额外参数。但通过下面两种方式依然可以把附加数据传入自定义格式化器。方式一使用环境变量自定义格式化器运行在 Node.js 进程中天然可以读取环境变量据此改变自身行为。以下示例用FORMATTER_SKIP_WARNINGS环境变量控制是否在输出中省略警告module.exports function (results) { var skipWarnings process.env.FORMATTER_SKIP_WARNINGS true; var results results || []; var summary results.reduce( function (seq, current) { current.messages.forEach(function (msg) { var logMessage { filePath: current.filePath, ruleId: msg.ruleId, message: msg.message, line: msg.line, column: msg.column, }; if (msg.severity 1) { logMessage.type warning; seq.warnings.push(logMessage); } if (msg.severity 2) { logMessage.type error; seq.errors.push(logMessage); } }); return seq; }, { errors: [], warnings: [], }, ); if (summary.errors.length 0 || summary.warnings.length 0) { var warnings !skipWarnings ? summary.warnings : []; // skip the warnings in that case var lines summary.errors .concat(warnings) .map(function (msg) { return ( \n msg.type msg.ruleId \n msg.filePath : msg.line : msg.column ); }) .join(\n); return lines \n; } };带环境变量运行FORMATTER_SKIP_WARNINGStrue eslint -f ./my-awesome-formatter.js src/输出示例警告已被跳过error space-infix-ops src/configs/bundler.js:6:8 error semi src/configs/bundler.js:6:10方式二复杂参数传递——JSON 管道推荐如果自定义格式化器模式提供的灵活性仍不够最佳方案是使用内置的 JSON 格式化器 输出原始 JSON再通过管道pipe交给第二个程序处理eslint -f json src/ | your-program-that-reads-JSON --option其中your-program-that-reads-JSON可以自由处理 ESLint 结果的原始 JSON再输出自己的格式你还可以向该程序传递任意数量的命令行参数来定制输出。这种方案把参数传递问题彻底交给下游程序天然支持最复杂的定制需求。面向终端的输出格式file:line:column现代终端如 iTerm2、Guake 等常见终端模拟器内置了点击自动打开文件的能力但前提是输出符合特定格式。绝大多数终端支持的标准格式是file:line:column因此如果你的格式化器面向终端交互场景应尽量让每条消息输出包含filePath:line:column结构前文环境变量示例中的输出正是这种格式这样用户在终端点击文件名即可直接跳转到对应代码位置。打包并分发自定义格式化器自定义格式化器可以通过 npm 包分发供其他项目直接安装使用。命名约定与使用创建一个 npm 包名称格式必须为eslint-formatter-*其中*是格式化器名例如eslint-formatter-awesome其他项目安装该包后使用-f或--format参数时只需写包名的后半部分eslint -f awesome src/之所以可以省略eslint-formatter-前缀是因为loadFormatter在解析时会对不含/的名称调用normalizePackageName(name, eslint-formatter)自动补全前缀见 lib/eslint/eslint.js。package.json 编写建议main字段必须指向实现自定义格式化器的 JavaScript 文件即格式化器函数的导出位置keywords字段建议加入以下关键词方便用户检索到你的格式化器eslinteslint-formattereslintformatter完整示例示例一汇总Summary格式化器只报告错误与警告总数的极简格式化器module.exports function (results, context) { // accumulate the errors and warnings var summary results.reduce( function (seq, current) { seq.errors current.errorCount; seq.warnings current.warningCount; return seq; }, { errors: 0, warnings: 0 }, ); if (summary.errors 0 || summary.warnings 0) { return ( Errors: summary.errors , Warnings: summary.warnings \n ); } return ; };运行eslint -f ./my-awesome-formatter.js src/输出Errors: 2, Warnings: 4可以看到它直接聚合LintResult上的errorCount与warningCount字段实现了 O(n) 的汇总能力当无任何问题时返回空字符串。示例二详细Detailed格式化器更复杂的报告形态——逐条列出消息并通过context.rulesMeta附加规则文档 URLmodule.exports function (results, context) { var results results || []; var summary results.reduce( function (seq, current) { current.messages.forEach(function (msg) { var logMessage { filePath: current.filePath, ruleId: msg.ruleId, ruleUrl: context.rulesMeta[msg.ruleId].docs.url, message: msg.message, line: msg.line, column: msg.column, }; if (msg.severity 1) { logMessage.type warning; seq.warnings.push(logMessage); } if (msg.severity 2) { logMessage.type error; seq.errors.push(logMessage); } }); return seq; }, { errors: [], warnings: [], }, ); if (summary.errors.length 0 || summary.warnings.length 0) { var lines summary.errors .concat(summary.warnings) .map(function (msg) { return ( \n msg.type msg.ruleId (msg.ruleUrl ? ( msg.ruleUrl ) : ) \n msg.filePath : msg.line : msg.column ); }) .join(\n); return lines \n; } };运行eslint -f ./my-awesome-formatter.js src/输出error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops) src/configs/bundler.js:6:8 error semi (https://eslint.org/docs/rules/semi) src/configs/bundler.js:6:10 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) src/configs/bundler.js:5:6 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) src/configs/bundler.js:6:6 warning no-shadow (https://eslint.org/docs/rules/no-shadow) src/configs/bundler.js:65:32 warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) src/configs/clean.js:3:6注意这个示例中ruleUrl取自context.rulesMeta[msg.ruleId].docs.url——这正是前文提到的rulesMeta惰性 getter 的典型消费场景同时该格式化器通过msg.severity区分warning1与error2并保持file:line:column结构以便终端点击跳转。深入原理loadFormatter 的解析流程理解了接口之后再来看 ESLint 到底如何把一个名称/路径变成可调用的格式化器函数。核心实现在 lib/eslint/eslint.js 的loadFormatter方法中解析优先级如下反斜杠归一化将\替换为/保证 Windows 路径兼容含/的名称 → 本地文件若名称包含/且非 npm 包命名空间则视为文件路径基于cwd用path.resolve解析例如./my-awesome-formatter.js否则 → npm 包通过normalizePackageName(name, eslint-formatter)自动补全eslint-formatter-前缀并尝试解析对应包这就是-f awesome能命中eslint-formatter-awesome的原因回退 → 内置格式化器若 npm 解析失败则回退到lib/cli-engine/formatters/name.js目录查找内置实现并标记为isBuiltInFormatter若内置格式化器已被移除会给出npm install -D eslint-formatter-name的安装提示类型校验加载结果必须是函数否则抛出TypeError: Formatter must be a function, but got a type.之后 ESLint 返回一个{ format(results, resultsMeta) }对象见 lib/eslint/eslint.jsformat()内部完成三件事按filePath排序results、组装contextresultsMetacwd 惰性rulesMeta、调用你的格式化器函数。CLI 侧的调用链则在 lib/cli.jsformatter await engine.loadFormatter(format); // ... const output await formatter.format(results, resultsMeta);也就是说无论你通过-f传的是相对路径、npm 包名还是内置名称最终都会收敛到同一条loadFormatter → format的调用链上。内置格式化器一览仓库内置格式化器位于 lib/cli-engine/formatters其清单与说明见 lib/cli-engine/formatters/formatters-meta.json名称说明stylish人类可读输出默认格式化器json输出 JSON 序列化的 lint 结果适合程序化消费json-with-metadata在json基础上额外附带规则元数据results与metadata分属两个属性html输出 HTML适合在浏览器中可视化展示内置json格式化器实现极为精简lib/cli-engine/formatters/json.jsmodule.exports function (results) { return JSON.stringify(results); };而json-with-metadatalib/cli-engine/formatters/json-with-metadata.js则把第二个参数即组装后的context其中包含rulesMeta作为metadata一并输出module.exports function (results, data) { return JSON.stringify({ results, metadata: data, }); };这为你编写自定义格式化器提供了最好的最小实现参照——一个格式化器可以简单到只有一行JSON.stringify也可以复杂到像stylish那样进行多列对齐的终端排版。小结自定义格式化器的核心脉络可以归纳为五步写一个函数module.exports function (results, context) { return …; }支持 async通过-f运行本地文件路径必须以.开头./my-awesome-formatter.jsnpm 包名则省略eslint-formatter-前缀用好两个参数results是 LintResult 数组含messages、errorCount、warningCount等context提供cwd、maxWarningsExceeded与惰性计算的rulesMeta按需传参简单场景用环境变量复杂场景用eslint -f json src/ | your-program管道方案面向终端优化与分发输出遵循file:line:column便于点击跳转需要共享时发布为eslint-formatter-*包并配置好main与keywords。如需继续深入建议阅读本仓库中的 自定义规则Custom Rules文档理解rulesMeta的来源、Node.js API 文档了解LintResult类型与ESLint类cwd选项以及 命令行接口文档-f/--format、--color、--max-warnings等完整选项语义。【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表