TOP004 课程标题结构规范:curriculum 仓库如何用 markdownlint 强制统一课程文档骨架
【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
本篇技术指南围绕cu/curriculum仓库中 markdownlint 自定义规则TOP004(lesson-headings)展开,以该规则的一组测试样例文档(如 valid_no_additional_resources.md)为切入点,系统讲解课程、项目、指南三类文档必须遵循的标题结构模板、通配符语义、例外文件清单,以及规则在 TOP004_lessonHeadings.js 中的逐段匹配实现。读完本文,你将掌握在该开源课程仓库中编写任意新课程文档时"标题怎么排、哪些小节必填、Additional resources 何时该省略"的完整规范,并能独立读懂 TOP004 的测试与报错信息。
一、背景:为什么需要强制标题结构
cu/curriculum是一个用于学习 Web 开发的开源课程仓库,其根目录 package.json 中声明了三个脚本:lint(markdownlint-cli2)、fix(自动修复)与test(node 原生测试)。全部课程文档以 Markdown 编写,分布在foundations/、javascript/、ruby/、react/、ruby_on_rails/等目录下,文档类型包括课程(lesson)、项目(project)与指南(guide)。
当课程数量达到数百篇时,如果没有统一规范,各文档会出现标题层级混乱、必备小节缺失、Additional resources 空置等问题。TOP004 规则的官方说明 markdownlint/docs/TOP004.md 明确给出了其设计动机:
强制一致的标题结构可以提高可读性、组织性与导航性。通过规定必需的标题结构,作者可以确保文档遵循标准化格式,包含所有必要的小节,从而方便读者定位具体信息。
因此 TOP004 的定位是"基于上下文不可自动修复"的结构校验规则(TOP004.md 中标注 "Not fixable due to being context-based"),它只负责报错,不负责改错——标题语义只能由作者人工判断。
二、三种文档类型的三套标题模板
规则实现 TOP004_lessonHeadings.js 中定义了三套命名常量,分别对应课程、项目、指南:
const HEADINGS = { lesson: [ "### Introduction", "### Lesson overview", "*", "### Assignment", "*", "### Knowledge check", "?", ], project: ["### Introduction", "*", "### Assignment", "*"], guide: ["### Guide: *", "*"], };选择哪一套模板,取决于目标文件的文件名与所在路径(TOP004_lessonHeadings.js):
- 文件名以
project_开头 → 使用project模板(注意projections.md这类以project开头的普通词汇不会被误判); - 路径中包含
_guides/目录段 → 使用guide模板; - 其余全部 → 使用
lesson模板。
2.1 课程(lesson)模板:五段必填 + 一段可选
依据 TOP004.md 的官方说明,课程文档的标题骨架为:
### Introduction ### Lesson overview ### A custom heading * (Wildcard: Any heading at any level) ### Assignment * (Wildcard: Any heading at any level) ### Knowledge check ### Additional resources (optional)逐项解读:
### Introduction:课程开场,交代本课目标与背景;### Lesson overview:课程总览,通常以列表形式给出本课将学到的知识点;- 第一个
*:允许出现任意数量、任意层级的自定义小节标题(如### Custom section、#### Subsection),只要位置在 Lesson overview 与 Assignment 之间即可; ### Assignment:作业区,规定必须用<div class="lesson-content__panel" markdown="1">包裹正文;- 第二个
*:Assignment 之后、Knowledge check 之前同样允许任意自定义标题(如#### Extra credit); ### Knowledge check:知识自测区;?:可选的### Additional resources——只有当确有资源时该小节才应存在;若为空则整节不得出现。
在代码中,模板末尾的?被注释为 "Allow single wildcard heading"(TOP004_lessonHeadings.js):当匹配到?时直接放行,即该位置之后是否还有标题都不再追究。
2.2 项目(project)模板:两段必填 + 两处通配
### Introduction * (Wildcard: Any heading at any level) ### Assignment * (Wildcard: Any heading at any level)项目文档没有 Lesson overview 与 Knowledge check,因为项目的重点是从头实现一个完整应用。### Assignment同样是必填的,且按惯例同样以lesson-content__panel包裹。仓库中 project_valid.md 展示了合法项目文档的写法——在 Assignment 的 panel 内还可以嵌套#### Extra credit子节。
2.3 指南(guide)模板:首标题必须带前缀
### Guide: * (Any descriptive title) * (Wildcard: Any heading at any level)位于*_guides/目录(例如foundations/installations/installation_guides/、nodeJS/express/installation_guides/)下的参考文档自动使用 guide 模板:第一个标题必须以### Guide:开头,后接任意描述性标题,此后任意标题结构均合法。合法样例见 guide_valid.md(首行### Guide: Virtual Machine installation,其后自由展开### Step 1、#### Step 1.1等);若首标题写成### Installation Guide之类的其他形式,会被报错Expected: heading starting with "### Guide: "; Actual: ### Installation Guide。
三、通配符*的精确语义
三套模板中都出现了*,其行为由两个正则决定(TOP004_lessonHeadings.js):
// 匹配整体通配项,如模板中的 "*" const wildcardRegex = new RegExp(/^(#*\s)?\*$/); // 匹配前缀通配项,如模板中的 "### Guide: *" const prefixWildcardRegex = new RegExp(/^(###\s)(.+):\s\*$/);wildcardRegex对应"任意标题文本、任意标题级别";prefixWildcardRegex则要求### 前缀:之后跟任意描述。匹配逻辑的关键在 TOP004_lessonHeadings.js:当期望值是通配符时,规则进入matchAny状态——随后的每一个标题都会被"消费",直到命中下一个具体期望标题(如### Assignment)为止;若一直未命中,则通过i--让游标回退,继续消费更多标题。这意味着:
- 通配区域可以有零个、一个或多个标题;
- 通配区域内的标题级别不限(
##、####均可); - 模板中的具体标题(Introduction、Assignment、Knowledge check)必须逐字逐级匹配(区分大小写),例如把
### Introduction写成## Introduction或### introduction都会报错。
forEachHeading辅助函数(TOP004_lessonHeadings.js)遍历 markdown-it 解析出的 token 流,在每个heading_open与inline配对处取出标题文本与行号,从而获得"第几行出现哪个级别的什么标题"这一完整信息,供逐段比对使用。
四、本篇主角:无 Additional resources 的合法课程文档
valid_no_additional_resources.md 是 TOP004 的合法测试样例之一,它完整展示了"没有附加资源"时课程文档的标准骨架:
### Introduction # 必填 ### Lesson overview # 必填,总览 + LO 列表 ### Custom section # 通配区:任意自定义小节 ### Assignment # 必填,且正文须包在 panel div 中 #### Assignment subsection # 通配区:panel 内可嵌套子节 ### Knowledge check # 必填,自测问题列表几个值得注意的细节:
- 标题均为
###(H3)级别——课程文档不使用#/##作为章节标题,整篇文档以小节为单位平铺; - Assignment 内容被
<div class="lesson-content__panel" markdown="1">包裹(valid_no_additional_resources.md),markdown="1"确保 div 内的 Markdown 语法仍被解析,这一约定在该规则的其他样例(如 valid_with_additional_resources.md)中保持一致; - 文件末尾没有 Additional resources 小节——这正是本样例与 valid_with_additional_resources.md 的唯一结构差异:后者在 Knowledge check 之后补上了
### Additional resources及- AR item列表。两份文件都通过 TOP004 校验,说明 Additional resources 是"可选节",其取舍依据是内容而非格式; - Knowledge check 的引导语是固定文案:"The following questions are an opportunity to reflect on key topics in this lesson...",配合
- KC item列表,形成规范的自测区形态。
五、错误路径:哪些写法会被 TOP004 拦截
规则与测试共同勾勒出三条典型的失败路径,便于在编写文档时反向规避。
5.1 必填小节缺失
missing_heading.md 在 Introduction 之后直接写了### Custom section,跳过了### Lesson overview。测试 TOP004.test.js 断言其报错为:
<path>:5 error TOP004/lesson-headings Required heading structure [Expected: ### Lesson overview; Actual: ### Custom section]即"期望下一个标题是### Lesson overview,实际遇到的是### Custom section"。这是逐段比对在第一个失配点立即停止的结果。
5.2 文件末尾缺少关键小节
project_invalid.md 只有 Introduction 和两个自定义小节,始终没有出现### Assignment。规则在文件读完后的收尾校验阶段(TOP004_lessonHeadings.js)计算missingExpectedHeadingCount = requiredHeadings.length - i,发现游标停留在### Assignment上,于是报出:
[Missing heading (case sensitive): ### Assignment] [Context: "### Assignment"]注意这里的判断条件missingExpectedHeadingCount > 1 || isLastRequiredHeadingSpecific隐含了一个行为:如果文档在 Assignment 的期望位置之前就结束,且剩余缺失的恰好只有通配项(例如只缺末尾的*),规则并不会报错——因为通配项允许零个标题。因此对于 lesson 模板,### Knowledge check是必须的(它是具体标题且是最后一个必填项),而### Additional resources缺失完全合法。
5.3 匹配到第一个错误即停止
实现中hasError标志(TOP004_lessonHeadings.js)确保规则只报告每个文件的第一处结构错误。注释给出了原因:一处标题错位(如多写、漏写一个标题)会连带导致后续所有标题整体错位,产生噪声式报错。因此调试时应优先修复第一条 TOP004 错误,再重新 lint。
5.4 例外文件:完全不校验
TOP004_lessonHeadings.js 硬编码了五个豁免文件名:
const exceptedLessons = [ "how_this_course_will_work.md", "conclusion.md", "conclusion_full_stack_javascript.md", "conclusion_ruby_on_rails.md", "actioncable_lesson.md", ];这些文件对应课程开篇说明(How This Course Will Work)、课程结语(Conclusion)以及特殊专题(ActionCable),其内容形态不适合套用统一标题骨架。判断逻辑在 TOP004_lessonHeadings.js:if (exceptedLessons.includes(fileName)) return;,直接跳过。测试 TOP004.test.js 用 conclusion.md 与 how_this_course_will_work.md 验证了豁免生效。
六、测试与运行方式:规则如何被验证
6.1 测试组织
TOP004 的测试位于 TOP004_lessonHeadings/tests/TOP004.test.js,使用 Node 内置的node:test与node:assert/strict,不需要额外测试框架。测试共覆盖五个维度:
- 规则元数据:
names为["TOP004", "lesson-headings"]、description为"Required heading structure"、information指向官方文档地址(TOP004.test.js); - 合法课程文档:带/不带 Additional resources 两份均不报错;
- 非法课程文档:missing_heading 精确报错;
- 项目文档:project_invalid 报缺失 Assignment,project_valid 不报错;
- 例外文档与指南文档:conclusion / how_this_course_will_work 豁免;guide_invalid 报前缀错误,guide_valid 放行。
6.2 测试如何驱动真实 lint
测试工具 lint.js 并不直接调用规则函数,而是通过子进程真实执行npm run lint -- <file>(即markdownlint-cli2),然后解析 stderr 输出:
- 退出码为 0 → 返回空数组(无错误);
- 退出码非 0 → 将 stderr 按行拆分作为错误列表。
这意味着每条测试断言都是对完整 markdownlint 管道的端到端验证,而非对单个函数的单元测试,从而保证规则在真实 lint 环境中的行为与断言一致。
6.3 本地复现与运行
在仓库根目录可执行:
# 对单个测试文件运行 lint,观察 TOP004 是否报错 npm run lint -- markdownlint/TOP004_lessonHeadings/tests/valid_no_additional_resources.md npm run lint -- markdownlint/TOP004_lessonHeadings/tests/missing_heading.md # 运行全部 markdownlint 规则的测试(含 TOP004) npm test对valid_no_additional_resources.md执行 lint 不会产生任何 TOP004 报错;对missing_heading.md执行则会得到与测试断言一致的[Expected: ### Lesson overview; Actual: ### Custom section]输出。需要说明的是,运行前提是仓库已安装依赖(npm install,依赖清单见 package.json 中的markdownlint-cli2)。
七、编写课程文档的实操清单
综合上述规范,在cu/curriculum仓库中新增一篇课程文档时,可按以下清单自检(以最常见的 lesson 类型为例):
- 标题全部使用
###级别; - 第一个小节必须是
### Introduction; - 第二个小节必须是
### Lesson overview,其后以- LO item.列表给出学习目标; - 在总览与作业之间,可自由添加任意数量的自定义小节(任意标题级别);
- 必须出现
### Assignment,且内容包裹于<div class="lesson-content__panel" markdown="1">内,panel 中允许嵌套####子节; - 作业之后必须出现
### Knowledge check,搭配固定引导语与- KC item列表; ### Additional resources仅在确有补充链接时保留,否则整节省略(模板中它为可选?);- 避免把文件命名为
how_this_course_will_work.md、conclusion.md等豁免清单之外的敏感名——这些名字会跳过校验,属于刻意为之的例外; - 若文件位于
_guides/目录,首标题必须写成### Guide: <描述性标题>的形式; - 若文件名以
project_开头,只需### Introduction+ 自定义小节 +### Assignment+ 自定义小节,无需 Lesson overview 与 Knowledge check。
遵循这份骨架,既能让读者在任意课程文档中获得一致的信息查找路径(Introduction → 总览 → 正文 → 作业 → 自测),也能让 TOP004 在持续集成中保持零告警。
【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考