ARTICLE DETAIL

资讯详情

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

The Odin Project 课程仓库的 TOP010 自定义 markdownlint 规则:有序列表惰性编号的强制与自动修复

The Odin Project 课程仓库的 TOP010 自定义 markdownlint 规则:有序列表惰性编号的强制与自动修复 The Odin Project 课程仓库的 TOP010 自定义 markdownlint 规则有序列表惰性编号的强制与自动修复【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum在 The Odin Project 的开源课程仓库curriculum中上千篇 Markdown 课程文档的质量由一套自研的 markdownlint 自定义规则体系TOP001TOP013统一把关。本篇文章以其中负责有序列表编号风格的 TOP010 规则为核心结合其规则实现、测试夹具与自动修复工具链完整剖析惰性编号lazy numbering这一 Markdown 写作规范在真实开源项目中的落地方式。读完本文你将理解该规则的触发条件、错误报告格式、自动修复原理并掌握在类似文档工程中复用它或编写同类 lint 规则的方法。一、背景课程仓库的自定义 lint 规则体系curriculum是一个开源的全栈 Web 开发课程仓库其 Markdown 文档数以千计。为了在多人协作下保证文档风格统一仓库在通用 markdownlint 之上通过 .markdownlint-cli2.jsonc 注册了 13 条项目自研规则customRules配置段每条规则独立成目录位于markdownlint/下TOP001descriptiveLinkTextLabels链接文本可描述性TOP002noCodeInHeadings标题内禁止代码TOP003defaultSectionContent默认章节内容TOP004lessonHeadings课程标题结构TOP005blanksAroundMultilineHtmlTags多行 HTML 标签空行TOP006fullFencedCodeLanguage围栏代码块必须带语言TOP007useMarkdownLinks必须使用 Markdown 链接TOP008useBackticksForFencedCodeBlocks代码块使用反引号TOP009lessonOverviewItemsSentenceStructure概述条目句式TOP010useLazyNumbering有序列表惰性编号本文主题TOP011headingIndentation标题缩进TOP012headingLevels标题层级TOP013descriptiveHeadings标题可描述性每条规则的说明文档集中在 markdownlint/docs 目录其中 markdownlint/docs/TOP010.md 专门说明了 TOP010 的规范与设计动机。本文聚焦 TOP010其余规则可作为同类参考。二、什么是惰性编号Lazy NumberingMarkdown 中的有序列表允许两种编号写法显式编号手动维护每个列表项的数字前缀如1.、2.、3.惰性编号lazy numbering所有列表项统一写作1.由渲染器在展示时自动递增编号。curriculum仓库对惰性编号的约定是有序列表的每个列表项都必须以1.作为前缀不允许出现2.、3.等其他数字。原因很直接显式编号在插入、删除或重排列表项时需要人工同步修改序号极易产生编号断裂或跳号而惰性编号在源码层面完全忽略序号差异把编号交给渲染器从源头消除了这一整类错误。TOP010 规则对该规范的定义见 markdownlint/TOP010_useLazyNumbering/TOP010_useLazyNumbering.js如下module.exports { names: [TOP010, lazy-numbering-for-ordered-lists], description: Ordered lists must always use 1. as a prefix (lazy numbering), information: new URL( https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP010.md ), tags: [ol], parser: markdownit, // ... };names规则别名数组第一个TOP010是短名第二个lazy-numbering-for-ordered-lists是语义化长名二者在错误报告中均可见description一句话概括规则意图会直接出现在 lint 错误消息中tags[ol]即该规则仅作用于有序列表ordered listparser: markdownit声明该规则基于 markdownit 解析器提供的 token 流工作而非基于正则的简单行匹配。三、规则实现原理逐段解析 TOP010 源码TOP010 的核心逻辑非常精简完整实现见 markdownlint/TOP010_useLazyNumbering/TOP010_useLazyNumbering.js可以拆成三个部分理解。3.1 匹配前缀数字的正则// https://regexr.com/80oan to test this regex const digit /^\s*\d/;该正则匹配行首的任意空白后紧跟一个或多个数字即捕获有序列表项的数字前缀部分。注意它只关心数字本身不关心点号.之后的列表内容缩进空白也被纳入匹配以兼容嵌套列表如 4 空格缩进的子项。3.2 遍历 token 并判定违规params.parsers.markdownit.tokens.forEach((token) { if ( token.tag li digit.test(token.line) token.info ! 1 ) { // ...构造错误 } });规则遍历 markdownit 解析出的全部 token命中违规需同时满足三个条件token.tag li该 token 是有序或无序列表项列表项统称为lidigit.test(token.line)该项所在行的行首存在数字前缀即排除- item这类无序列表项它们的行首是-无法通过数字正则token.info ! 1markdownit 会将列表项的数字前缀记录在token.info中当且仅当它不是1时才判定为违规。由于条件 2 已经过滤掉无序列表项token.info ! 1实际只针对有序列表项生效——这正是只保留1.这一规范的精确落地。3.3 错误报告与自动修复信息const lineNumber token.lineNumber; const tokenLine token.line.split(.); const lazyNumbering tokenLine[0].replace(/\d/, 1); onError({ lineNumber: lineNumber, detail: \n Expected: ${lazyNumbering}\n Actual: ${tokenLine[0]}\n, fixInfo: { lineNumber: lineNumber, deleteCount: tokenLine[0].length, insertText: lazyNumbering, }, });违规项的报错包含三类关键信息位置lineNumber直接指向违规行详情Expected期望值与Actual实际值对比。lazyNumbering由token.line.split(.)[0]取数字前缀部分后用replace(/\d/, 1)把数字统一替换成1。因此对2. Item Two而言Actual是2Expected是1对缩进的2. Child而言Actual是2保留缩进Expected是1修复指令fixInfodeleteCount指定要删除的字符数即原数字前缀的长度保留缩进insertText指定插入的替换文本。markdownlint-cli2 的--fix模式正是依据这份 fixInfo 完成自动改写这也是该自定义规则相对 markdownlint 内置规则 MD029 的核心差异详见第六节。四、测试夹具剖析test.md 中哪些行会被标记markdownlint/TOP010_useLazyNumbering/tests/test.md是专为 TOP010 准备的 lint 测试夹具文件头明确写道This file should flag with TOP010 errors, and no other linting errors.它被设计为只触发 TOP010 错误、不触发其他任何 lint 错误的隔离样本从而保证测试断言不受其他规则干扰。整份文件是一篇仿真的课程文档包含Introduction、Lesson overview、Assignment、Knowledge check、Additional resources等课程标准章节其中故意混排了合规与违规的有序列表行号内容是否符合惰性编号结果211. A RESOURCE OR EXERCISE ITEM是不报错251. Item One是不报错262. Item Two否报错期望1实际2271. Child of Item Two是嵌套不报错282. Child of Item Two否嵌套报错期望1实际2293. Item Three否报错期望1实际331–35第二组列表全部为1.是不报错371. *foo*是不报错382. *Bar*否报错期望1实际240–41-无序列表项不适用非有序不报错这份夹具刻意覆盖了多种边界情况嵌套列表中的违规子项第 28 行、连续多级违规第 2829 行、列表项内带行内强调第 38 行的*Bar*、以及已全部使用惰性编号的正确列表第 3135 行——后者用于验证规则不会对1.前缀误报也不会因为列表项包含 Markdown 语法而漏判。对照测试夹具 markdownlint/TOP010_useLazyNumbering/tests/fixed_test.md可以看到修复后的版本第 26、28、29、38 行的编号全部被改写为1.其余内容逐字不变。两份文件的差异恰好精确对应规则 fixInfo 的删除与插入范围。五、测试验证错误断言与自动修复用例仓库的测试使用 Node.js 内置测试运行器node --testTOP010 的测试定义在 markdownlint/TOP010_useLazyNumbering/tests/TOP010.test.js 中。5.1 Lint 断言逐行精确匹配错误assert.deepEqual(lintErrors, [ ${errorPath}:26 error ${expected.name} ${expected.description} [ Expected: 1 Actual: 2 ], ${errorPath}:28 error ${expected.name} ${expected.description} [ Expected: 1 Actual: 2 ], ${errorPath}:29 error ${expected.name} ${expected.description} [ Expected: 1 Actual: 3 ], ${errorPath}:38 error ${expected.name} ${expected.description} [ Expected: 1 Actual: 2 ], ]);测试用assert.deepEqual逐条比对错误输出精确到行号、期望值与实际值。从这组断言可以反推 TOP010 的实际报错格式文件路径:行号 error TOP010/lazy-numbering-for-ordered-lists 描述 [ Expected: 期望前缀 Actual: 实际前缀 ]注意第 28 行的断言嵌套子项的Expected与Actual都保留了 4 个空格的缩进印证了 3.3 节中数字前缀含缩进的处理方式。5.2 修复断言输出必须与 fixed_test.md 完全一致const fixedFileContents await fixLintErrors(./test.md); const correctFile await readFile(join(__dirname, ./fixed_test.md)); assert.equal(fixedFileContents, correctFile.toString());修复测试的做法是对test.md执行一次带自动修复的 lint把修复后的全文与fixed_test.md逐字节比对。由于 markdownlint/TOP010_useLazyNumbering/tests/TOP010.test.js 使用assert.equal做严格相等断言任何多余的空格、换行差异都会导致测试失败——这保证了修复脚本是最小改写绝不触碰编号以外的内容。5.3 测试工具链的配合两条测试分别复用了仓库的两个测试工具模块markdownlint/test_utils/lint.js先校验文件存在再执行npm run lint -- 文件绝对路径若命令失败非零退出码则把stderr按行拆分后返回错误数组。lint 有错时 markdownlint-cli2 恰好以非零码退出据此即可判定存在错误markdownlint/test_utils/fix.js通过spawnSync执行npm run lint -- --format把文件内容通过标准输入传入随后剥离 markdownlint-cli2 的启动输出噪音返回修复后的纯文本。两者组合起来形成lint 断言 fix 比对的双层测试闭环先证明规则能精准报错再证明修复脚本能产出与人工修订版完全一致的文档。六、设计动机为什么不用内置规则 MD029通用 markdownlint 本身就有针对有序列表编号的规则 MD029ol-prefix。curriculum之所以另起炉灶写 TOP010原因记录在 markdownlint/docs/TOP010.md 的 Rationale 一节Markdown lints MD029 rule already covers this check, but does not include fix information, therefore can only be used to raise errors for manual fixing. This custom rule enforces the same style but includes fix information that can be used alongside our fix scripts.也就是说MD029 只能报错提示不携带 fixInfo无法驱动自动修复TOP010 在强制相同风格的同时为每个错误附带完整的deleteCount/insertText修复信息从而无缝接入仓库的npm run fix脚本。这一点在仓库配置中也有明确体现——.markdownlint-cli2.jsonc 中MD029被显式关闭// ol-prefix // Enforces lazy numbering for ordered lists // MD029 Disabled and overridden by TOP010 rule MD029: false,而customRules列表第 102 行注册了./markdownlint/TOP010_useLazyNumbering/TOP010_useLazyNumbering.js完成规则交接。这组配置与源码共同印证了自定义规则替换内置规则的完整链路。七、实际使用在课程文档中运行与修复TOP010 随仓库的 npm 脚本开箱即用脚本定义在 package.jsonscripts: { lint: markdownlint-cli2, fix: markdownlint-cli2 --fix, test: node --test }使用方式在仓库根目录执行# 1. 对单个文件做 lint 检查查看 TOP010 错误 npx markdownlint-cli2 markdownlint/TOP010_useLazyNumbering/tests/test.md # 2. 自动修复所有违规项将 2./3. 前缀改写为 1. npm run fix -- markdownlint/TOP010_useLazyNumbering/tests/test.md # 3. 运行整个规则测试套件 npm test在.markdownlint-cli2.jsonc未做excludes调整的情况下npm run lint/npm run fix会扫描仓库全部 Markdown 文件其中自然包含 TOP010 对每个有序列表的检查。实践建议是编写课程文档时所有有序列表一律写1.让渲染器决定显示编号批量维护历史文档直接执行npm run fix由 fixInfo 驱动的修复会一次性把所有非1前缀统一为1.且保证除编号前缀外不产生任何改动在 CI 中将npm run lint作为质量门禁任何提交了2.、3.前缀的有序列表都会被 TOP010 拦截。八、从 TOP010 看自定义 markdownlint 规则的通用范式TOP010 的整个实现可以作为编写自定义 markdownlint 规则的最小完整范例其可复用的模式包括元信息names双别名、description、information指向规则文档 URL、tags便于分组过滤、parser声明 token 流来源判定逻辑遍历params.parsers.markdownit.tokens用token.tag圈定目标元素类型用token.info/token.line做精确语义判断而不是整行正则匹配错误对象lineNumberdetailExpected/Actual 对比fixInfodeleteCount与insertText精确描述删除插入范围三者齐备才可被--fix消费测试闭环一份仅触发本规则的夹具如test.md、一份人工修订的期望产物如fixed_test.md、外加 lint 逐行断言与 fix 全文比对两组用例配置集成在.markdownlint-cli2.jsonc的customRules数组中注册必要时用MD029: false之类的显式关闭让位。九、总结TOP010lazy-numbering-for-ordered-lists是curriculum仓库文档质量体系中的一个精小但完整的工程样本它以有序列表统一使用1.惰性编号这一简单规范为切入点通过 markdownit token 级的精确判定实现只报该报的错通过 fixInfo 驱动的自动修复实现只改该改的字并通过test.md与fixed_test.md的成对夹具把 lint 与 fix 两条路径都锁定在测试中。对于任何以 Markdown 为内容载体的开源项目这套规范文档 规则实现 隔离夹具 双断言测试 CLI 集成的组合都值得直接借鉴。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表