Assignment
【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
Valid div due to each tag being surrounded by blank lines.
这里的<div>与</div>各自前后均为空行,完全满足规则要求,因此不会触发任何 TOP005 错误。这也是课程正文中最常见的"提示框/面板"写法——lesson-content__panel配合markdown="1"让markdown-it在 div 内部继续解析 Markdown 内容。
豁免场景二:单行 HTML 标签(非多行,天然合法)
规则只针对"单独占据一行、且仅包含一个开/闭标签"的多行 HTML 标签。如果标签是单行内联形式,则不在检查范围内:
<div>Valid single-line div</div>更复杂一点的行内混排同样合法:
<div>Valid single-line div</div>Might even have other <span>paragraph</span> content with it.原因在于规则判定依赖的正则^<(?!!)\/?[^>]*>$(见 规则源码)要求"整行 trim 后恰好是一个完整的标签"(以<开头、>结尾、中间不含>、且非<!--注释开头)。上述两行的 trim 结果都不满足"整行只有标签"的条件,因此被跳过。
豁免场景三:markdown 代码块内的 HTML——唯一的内容例外
ignored_tags.md揭示了一个重要细节:代码块分隔符(```)本身就是合法的"包围物"。规则源码中用于校验前后行的正则blankCodeBlockRegex = /^$|^{3,4}.$/表明:一行只要为空行(^$)或以 3~4 个反引号开头(^{3,4}.$`),即视为合法包围。
正因如此,markdown围栏代码块内的多行 HTML 标签也被放行——此时这些标签是"被讲解的示例",而非真实页面标记:
```markdown <div> The only exception to blank lines is a code block delimiter. </div> ```注意这里的措辞:"The only exception to blank lines is a code block delimiter"——即空行的唯一替代品就是代码块分隔符。也就是说,对于markdown代码块,虽然内部的<div>并不满足"两侧都是空行",但只要两侧是"空行或代码块围栏行",就仍然合法。这正是 TOP005.md 文档 中示例所表达的行为:第二个开标签"被围栏分隔符和空行包围,两者均为合法"。
豁免场景四:html / jsx / erb / ejs / ruby / javascript 围栏代码块
这是ignored_tags.md中占比最大的豁免类型:当多行 HTML 标签出现在特定语言围栏中时,规则整体跳过,不再要求空行。测试夹具逐一展示了六种语言:
```html <div> <p> Does not flag when used in an HTML example </p> </div> ``````jsx <p> Also does not flag when used in JSX code blocks </p> ``````erb <%= if language.isErb? %> <p>Also does not flag when used in erb code blocks</p> <% end %> ``````ejs <% if (isEjs) { %> <p>Also does not flag when used in ejs code blocks</p> <% } %> ``````ruby if ruby? html_fragment = <<~HTML <p>Does not flag when used in ruby code blocks</p> HTML end ``````javascript const htmlString = ` <p>Does not flag when used in JavaScript code blocks, e.g. template literals.</p> `; ```这一行为直接映射到 规则源码 中的豁免清单:
const IGNORED_FENCE_TYPES = ["html", "jsx", "erb", "ejs", "ruby", "javascript"];源码注释也解释了意图:"HTML code in HTML/JSX code blocks should not be flagged. We only want to flag HTML tags we use for actual markup, or md code block examples of such."——即规则只针对真实页面标记(以及markdown代码块中作为示例的此类标记),而对html/jsx等代码块中的 HTML 一概放行。ruby、javascript、erb、ejs被纳入清单,是因为这些语言的字符串、模板、Heredoc 中经常会内嵌 HTML 片段,误报会造成严重噪音。
实现细节:行区间过滤
豁免并非逐行判断,而是按行区间整体排除。规则通过markdown-it的 token 流过滤出围栏 token 及其map(起始行、结束行区间),再判断目标行是否落入任一被忽略的围栏区间内:
const ignoredFencesLineRanges = params.parsers.markdownit.tokens .filter((token) => { return token.type === "fence" && IGNORED_FENCE_TYPES.includes(token.info); }) .map((token) => token.map); const isWithinIgnoredFence = (lineNumber) => { return ignoredFencesLineRanges.some( (range) => range[0] < lineNumber && lineNumber < range[1] ); };在遍历到每个"孤立 HTML 标签行"时,若isWithinIgnoredFence(lineNumber)为真则直接return跳过。这意味着只要标签行位于上述六类围栏的内部(不含围栏分隔行本身),无论其前后是否为空行,都不会报错。
豁免场景五:markdownlint 行级忽略指令
ignored_tags.md还展示了另一层"硬豁免"机制——markdownlint 自带的忽略指令:
<!-- markdownlint-disable-next-line --> ### `Will not flag ignore comments which require being directly followed by the line to ignore`markdownlint-disable-next-line是 markdownlint 官方提供的行级开关,紧跟其后的那一行即使违反规则也不会被报告。测试夹具用它来说明:即便标题行包含代码(在正常规则下可能触发其他 lint 规则,如"标题内禁代码"),只要前置了该注释,就会被静默忽略。这属于规则系统层面的豁免,与 TOP005 的判定逻辑正交,但同样在"零报错"夹具中扮演了关键角色。
正反对照:flagged_tags.md中的命中场景
理解豁免之后,再看反例 flagged_tags.md 能帮你建立完整的判定边界。该夹具刻意构造了若干必报错场景,例如:
- 开标签后紧跟非空行(无空行也无围栏行);
- 连续两个 HTML 块之间没有空行("chained HTML blocks");
markdown代码块内,闭标签上方既非空行也非围栏分隔行;- 即使标签带有缩进(如
<p>)也照报不误——规则基于trim()后判定,缩进不影响命中。
而对应的修复结果 fixed_flagged_tags.md 展示了自动修复的全部形态:在缺失处补上空行,使每个孤立标签两侧都满足"空行或代码块分隔符"。
底层原理:为什么强制空行如此重要
规则如此苛刻,根源在于markdown-it对 HTML 块的解析方式。TOP005 的 Rationale(见 markdownlint/docs/TOP005.md)明确指出:在遇到空行之前,HTML 开标签之后的所有内容会被合并进同一个html_blocktoken,其中的文本不会被拆分为独立的 Markdown 元素 token,因此无法触发任何基于 token 的 lint 规则。
这意味着下面这段 Markdown 即使"满是错误"也不会被任何规则捕获:
<div class="lesson-note" markdown="1"> #### This title should trigger the "blanks around headings" rule 1. [this should trigger the "descriptive links" rule](#rationale) 2. this should trigger the "lazy list numbering" rule </div>标题规则、描述性链接规则、惰性列表编号规则全部失效。而一旦强制 HTML 标签被空行或代码块分隔符包围,html_block就被切碎成独立 token,内部内容得以被逐条规则正常解析与报错。因此 TOP005 的定位并非"排版洁癖",而是守护整个 lint 体系正确性的地基——它保证维护者在课程正文的 HTML 面板中不会漏掉任何真正的格式错误。
自动化验证:测试如何证明"零报错"
ignored_tags.md的"零报错"身份由 TOP005.test.js 中的用例固化:
it("Does not flag when no rule violations", async () => { const filePath = "./ignored_tags.md"; const lintErrors = await getLintErrors(filePath); assert.deepEqual(lintErrors, []); });该用例通过 test_utils/lint.js 提供的getLintErrors执行npm run lint -- "<文件路径>",断言返回的错误数组为空。与此同时,flagged_tags.md用例则断言了 13 条精确的错误输出(每条都含文件行号、规则名、描述与具体上下文),例如:
...tests/flagged_tags.md:19 error TOP005/blanks-around-multiline-html-tags Multiline HTML tags should be surrounded by blank lines or code block delimiters [Expected a blank line or a code block delimiter (```) after the tag] [Context: "<div class="lesson-content__panel" markdown="1">"]修复方向则由Fix分组验证:fixLintErrors("./flagged_tags.md")的输出必须与 fixed_flagged_tags.md 逐字节一致(见 test_utils/fix.js 的实现,它通过npm run lint -- --format输出修复后内容并去除 CLI 噪音)。
整套测试基于 Node 内置测试运行器(node --test),在仓库根目录执行npm run test即可运行(见 package.json 中的scripts.test)。
判定边界速查表
| 场景 | 是否触发 TOP005 | 依据 |
|---|---|---|
| 多行 HTML 标签两侧均有空行 | 否 | ignored_tags.md |
单行内联标签(<div>...</div>或混排文本) | 否 | 行级正则^<(?!!)\/?[^>]*>$不命中 |
markdown围栏内标签两侧为"空行或围栏行" | 否 | blankCodeBlockRegex = /^$|^{3,4}.*$/` |
markdown围栏内标签一侧既非空行也非围栏行 | 是 | flagged_tags.md |
html/jsx/erb/ejs/ruby/javascript围栏内的标签 | 否 | IGNORED_FENCE_TYPES行区间豁免 |
| 连续 HTML 块之间无空行 | 是 | 前后行校验失败(含"下一行仍是 HTML 标签"的相邻特判) |
| 带缩进的多行标签 | 是 | 判定基于trim()后内容,缩进不影响 |
markdownlint-disable-next-line后的行 | 否 | markdownlint 行级忽略指令 |
在仓库中亲手验证
仓库是只读的,你可以通过以下方式在本机复现验证(需先按 package.json 安装依赖,项目使用markdownlint-cli2):
# 安装依赖 npm install # 运行全部测试(含 TOP005 的零报错与修复断言) npm run test # 单独对零报错夹具做 lint(预期无任何输出) npm run lint -- markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/ignored_tags.md # 对反例夹具做 lint(预期输出 13 条 TOP005 错误) npm run lint -- markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/flagged_tags.md【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考