Introduction
Introduction【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculumThis file should flag 3 errors due to the Lesson overview, Knowledge check, and Additional resources sections not containing unordered lists.Lesson overviewThis section contains a general overview of topics that you will learn in this lesson.Custom sectionText contentAssignmentAssignment contentKnowledge checkThe following questions are an opportunity to reflect on key topics in this lesson. If you cant answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge.Additional resourcesThis section contains helpful links to related content. It isnt required, so consider it supplemental.从文件自身注释即可看出设计意图它应当报出 3 个错误原因是 Lesson overview、Knowledge check、Additional resources 三个小节没有包含无序列表。 ### 2.2 测试断言精确到行号的错误 该测试用例在 [TOP003.test.js](https://link.gitcode.com/i/8d8551c34478d2c4a3ba7f815efc414d) 中对应如下断言 js it(Flags when a section does not have a required unordered list, async () { const filePath ./missing_list.md; const errorPath join(pathInRepo, filePath); const lintErrors await getLintErrors(filePath); assert.deepEqual(lintErrors, [ ${errorPath}:7 error ${expected.name} ${expected.description} [Must include an unordered list of lesson overviews in the lesson overview section], ${errorPath}:23 error ${expected.name} ${expected.description} [Must include an unordered list of knowledge checks in the knowledge check section], ${errorPath}:27 error ${expected.name} ${expected.description} [Must include an unordered list of additional resources in the additional resources section], ]); });对照文件行号L7是### Lesson overview之后的第一个内容行默认内容文本所在行报错Must include an unordered list of lesson overviews in the lesson overview sectionL23是### Knowledge check之后的第一个内容行报错Must include an unordered list of knowledge checks in the knowledge check sectionL27是### Additional resources之后的第一个内容行报错Must include an unordered list of additional resources in the additional resources section。注意两个细节报错定位在小节内第一个内容 token所在行而不是小节标题行。这是源码中tokensAfterFirstContent[0] || tokensAfterHeading[0]的取值逻辑见 TOP003_defaultSectionContent.js列表名称做了复数化处理源码中listItemsName section (section.endsWith(s) ? : s)于是lesson overview→lesson overviews、knowledge check→knowledge checks、additional resources→additional resources本身以 s 结尾不再追加。2.3 修复方式手动补一个无序列表missing_list.md没有对应的自动修复用例fixLintErrors只覆盖ordered_list.md、incorrect_content.md、content_around_list.md三个文件见 TOP003.test.js。要人工修复只需在每个小节标题与默认内容之后补充一个无序列表例如将Lesson overview小节改为### Lesson overview This section contains a general overview of topics that you will learn in this lesson. - LO item.对照valid.mdtests/valid.md可知符合规范的小节结构是标题 → 空行 → 默认内容 → 空行 → 无序列表项。三、规则源码结构从 token 流到错误集合3.1 整体执行流程TOP003 的主函数定义在 TOP003_defaultSectionContent.jsfunction: function TOP003(params, onError) { const { tokens } params.parsers.markdownit; const headingTokenIndices tokens .filter((token) token.type heading_open) .map((headingToken) tokens.indexOf(headingToken)); const totalErrors []; headingTokenIndices.forEach((tokenIndexValue, arrIndex, tokenIndicesArr) { const headingContent tokens[tokenIndexValue].line .replace(/\#\s/g, ) .toLowerCase(); if (!Object.values(sectionsWithDefaultContent).includes(headingContent)) { return; } // ...按小节类型分发检查 }); totalErrors.forEach((error) { onError(error); }); }执行链路可概括为四步取 token 流从params.parsers.markdownit取出 markdown-it 解析后的完整 token 数组定位所有标题筛选heading_open类型 token记录其在 token 数组中的下标匹配目标小节将标题行内容去掉#前缀并转小写与sectionsWithDefaultContent中的四个值比对不匹配如Introduction、Custom section直接跳过按小节类型分发对目标小节取从该标题到下一个标题之间的 token 切片若小节为空则报cannot be empty否则按类型进入getListSectionErrorsLesson overview / Knowledge check / Additional resources或getAssignmentSectionErrorsAssignment。3.2 目标小节与默认内容的集中定义规则顶部集中维护了两张表TOP003_defaultSectionContent.jsconst sectionsWithDefaultContent { lessonOverview: lesson overview, assignment: assignment, knowledgeCheck: knowledge check, additionalResources: additional resources, }; const listSectionsDefaultContent { [sectionsWithDefaultContent.lessonOverview]: This section contains a general overview of topics that you will learn in this lesson., [sectionsWithDefaultContent.knowledgeCheck]: The following questions are an opportunity to reflect on key topics in this lesson. If you cant answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge., [sectionsWithDefaultContent.additionalResources]: This section contains helpful links to related content. It isnt required, so consider it supplemental., };这里有两个容易被忽略的细节assignment小节没有默认文本它只做 div 包装器检查不参与默认内容匹配因为不同课程的 Assignment 内容差异很大无法统一三个列表小节的默认文本在valid.md与missing_list.md中逐字一致必须完全匹配任何措辞改动哪怕只是把isnt改成is not都会触发默认内容不正确类错误该分支的判例见 tests/incorrect_content.md 与对应的 fixed_incorrect_content.md。四、getListSectionErrors列表小节的六类检查getListSectionErrorsTOP003_defaultSectionContent.js是 TOP003 中最复杂的函数对每个列表小节依次执行以下六类检查累积返回错误数组。4.1 检查一禁止嵌套列表const listItemTokens tokensAfterHeading.filter( (token) token.type list_item_open, ); const nestedListItemTokens listItemTokens.filter( (token) token.level 1, ); nestedListItemTokens.forEach((nestedListItemToken) { listSectionErrors.push( createErrorObject( nestedListItemToken.lineNumber, The ${section} section must not contain nested lists., ), ); });实现要点取标题之后所有list_item_opentoken凡是token.level 1markdown-it 中嵌套层级大于 1即视为嵌套列表项。该分支不提供自动修复因为仅取消缩进未必能解决问题可能需要整体删除某些列表项。测试样例 tests/nested_list.md 在 L10 和 L36 分别构造了 Lesson overview 与 Additional resources 的嵌套列表项对应断言见 TOP003.test.js[The lesson overview section must not contain nested lists.] [The additional resources section must not contain nested lists.]4.2 检查二禁止有序列表const orderedListItemRegex /^\d\.\s*/; const orderedListItemTokens listItemTokens.filter((token) orderedListItemRegex.test(token.line), ); orderedListItemTokens.forEach((orderedListItemToken) { listSectionErrors.push( createErrorObject( orderedListItemToken.lineNumber, The ${section} section must not include any ordered lists., { lineNumber: orderedListItemToken.lineNumber, deleteCount: orderedListItemToken.line.match(orderedListItemRegex)[0].length, insertText: - , }, ), ); });实现要点用正则^\d\.\s*匹配以数字加点号开头的列表项如1. KC item命中即报错并附带自动修复删除数字编号前缀deleteCount为匹配到的数字编号长度替换为-从而把有序列表转换为无序列表。源码注释特别提到TOP003_defaultSectionContent.js该正则只对顶层列表项做测试因为嵌套列表项在 token 对象中的line前面带有缩进空格^\d\.锚点会失配——这是有意为之避免与禁止嵌套列表的检查产生重复报错。测试样例 tests/ordered_list.md 在 L27、L28 构造了两个有序列表项其 lint 断言TOP003.test.js会同时报出两类错误[The knowledge check section must not include any ordered lists.] [Must include an unordered list of knowledge checks in the knowledge check section]而 fix 测试Converts ordered lists to unordered listsTOP003.test.js验证npm run lint -- --format修复后输出与 tests/fixed_ordered_list.md 完全一致。4.3 检查三默认内容必须紧跟标题const defaultContentOpenTokenIndex tokensAfterHeading.findIndex( (token) token.line listSectionsDefaultContent[section], ); if (defaultContentOpenTokenIndex 0) { listSectionErrors.push( createErrorObject( defaultContentToken.lineNumber, Expected default section content to come immediately after the ${section} heading., ), ); }实现要点在标题之后的 token 中查找与默认内容文本逐字相等的行若它不是第一个内容 tokenindex 0说明标题与默认内容之间插入了其他内容报错 Expected default section content to come immediately after the {section} heading.。4.4 检查四默认内容缺失或被替换if (defaultContentOpenTokenIndex -1) { const sectionStartsWithList tokensAfterHeading[0].line.startsWith(- ); const errorDetail sectionStartsWithList ? Expect default content to precede unordered list of ${listItemsName}: ${listSectionsDefaultContent[section]} : Expected: ${listSectionsDefaultContent[section]}; Actual: ${tokensAfterHeading[0].line}; let replacementText listSectionsDefaultContent[section]; if (sectionStartsWithList) { replacementText \n\n${tokensAfterHeading[0].line}; } // ... }实现要点当整个小节都找不到默认内容文本时若小节以列表开头报错信息提示期望默认内容位于 {listItemsName} 无序列表之前修复策略是把默认内容插入到列表之前replacementText 默认内容 \n\n 原列表首行若小节以其他文本开头则报错Expected: 默认内容; Actual: 实际首行文本修复策略是整体替换首个内容 token。incorrect_content.md的 L7 正是默认内容被错误文本替换的判例其断言TOP003.test.js为[Expected: This section contains a general overview of topics that you will learn in this lesson.; Actual: This section has the wrong text following the heading that should flag an error.]而incorrect_content.md的 L25 则是小节以列表开头、缺少默认内容的判例[Expect default content to precede unordered list of knowledge checks: The following questions are an opportunity to reflect on key topics in this lesson. ...]对应的 fix 测试Inserts/replaces missing or incorrect default section contentTOP003.test.js验证修复结果与 tests/fixed_incorrect_content.md 逐字节一致——在该文件中Knowledge check 小节被修复为默认内容 空行 列表项的结构。4.5 检查五默认内容之后只允许无序列表if ( defaultContentOpenTokenIndex 0 tokensAfterFirstContent.length !tokensAfterFirstContent[0].type.endsWith(_list_open) ) { listSectionErrors.push( createErrorObject( tokensAfterFirstContent[0].lineNumber, Only an unordered list of ${listItemsName} can follow the default content., { lineNumber: ..., deleteCount: WHOLE_LINE }, ), ); }实现要点当默认内容位于小节开头index 0且其后还有内容时紧跟默认内容的必须是无序列表若是段落等其他 token 类型则报错 Only an unordered list of {listItemsName} can follow the default content.并整行删除该非法内容deleteCount: WHOLE_LINE即 -1。4.6 检查六列表之后禁止追加内容const lastBulletListCloseIndex sectionTokens.findLastIndex( (token) token.type bullet_list_close, ); if ( bulletListOpenTokenIndex ! -1 lastBulletListCloseIndex ! sectionTokens.length - 1 ) { const tokensAfterBulletListClose sectionTokens.slice(lastBulletListCloseIndex 1); listSectionErrors.push( createErrorObject( tokensAfterBulletListClose[0].lineNumber, There should be no additional content after the unordered list of ${listItemsName}, { lineNumber: ..., deleteCount: WHOLE_LINE }, ), ); }实现要点找出小节内最后一个bullet_list_closetoken如果其后还有内容即它不是小节的最后一个 token则报错并整行删除多余内容。检查五与检查六合在一起共同保证 Lesson overview、Knowledge check、Additional resources 三个小节的规范形态是唯一且确定的标题 → 默认内容 → 无序列表 → 小节结束。这两个检查的判例集中在 tests/content_around_list.mdL9 在列表前插入文本、L13 在列表后插入文本断言见 TOP003.test.js[Only an unordered list of lesson overviews can follow the default content.] [There should be no additional content after the unordered list of lesson overviews]对应的 fix 测试Removes flagged content around default section listsTOP003.test.js验证修复结果与 tests/fixed_content_around_list.md 一致即删除列表前后的多余内容。4.7 缺少列表主错误的判定逻辑回到missing_list.md触发的主错误其判定位于 TOP003_defaultSectionContent.jsconst tokensAfterFirstContent tokensAfterHeading.slice( tokensAfterHeading.findIndex( (token, _index, arr) token.type arr[0].type.replace(_open, _close), ) 1, ); const bulletListOpenTokenIndex sectionTokens.findIndex( (token) token.type bullet_list_open, ); if ( (defaultContentOpenTokenIndex 0 !tokensAfterFirstContent.length) || bulletListOpenTokenIndex -1 ) { const tokenLineNumber ( tokensAfterFirstContent[0] || tokensAfterHeading[0] ).lineNumber; const errorDetail Must include an unordered list of ${listItemsName} in the ${section} section; listSectionErrors.push(createErrorObject(tokenLineNumber, errorDetail)); }逻辑拆解tokensAfterFirstContent是第一个内容 token 闭合之后的剩余 token若默认内容恰好在标题后第一项且其后没有内容说明只有默认内容、没有列表bulletListOpenTokenIndex -1表示整个小节内不存在任何无序列表bullet_list_opentoken这是missing_list.md中三个小节共同的情形只要满足任一条件就报 Must include an unordered list of {listItemsName} in the {section} section错误定位行取tokensAfterFirstContent[0]有后续内容时或tokensAfterHeading[0]无后续内容时即默认内容所在行。这正是missing_list.md中 L7、L23、L27 三个报错点位的由来默认内容行就是标题之后的第一个内容 token 所在行且小节内没有bullet_list_open因此三条错误全部落位在默认内容文本行。五、getAssignmentSectionErrorsAssignment 的 div 包装器检查getAssignmentSectionErrorsTOP003_defaultSectionContent.js只做一件事确认 Assignment 小节内存在带正确属性的 HTML divconst divBlockTokens sectionTokens.filter( (token) token.type html_block token.content.startsWith(div), ); const hasAssignmentDiv divBlockTokens.some( (token) token.content.includes(classlesson-content__panel) token.content.includes(markdown1), ); if (!divBlockTokens || !hasAssignmentDiv) { assignmentErrors.push( createErrorObject( sectionTokens[0].lineNumber, Assignment sections must include an HTML div element with classlesson-content__panel and markdown1 attributes, ), ); }要点该规则依赖 markdown-it 将div ....../div解析为html_blocktoken仓库中的合法写法参见 tests/valid.md L17-L21即div classlesson-content__panel markdown1包裹的块判定条件非常严格classlesson-content__panel与markdown1两个属性必须同时存在报错文案为 Assignment sections must include an HTML div element with classlesson-content__panel and markdown1 attributes错误定位在小节标题行该分支没有自动修复——TOP003 的 docs 也明确指出并非所有 Assignment 内容都必须包在这个 div 里但它必须存在于该小节中it must at least exist in this section。判例 tests/missing_wrapper.md 在 L17 直接书写普通段落文本缺少 div 包装器断言见 TOP003.test.js。六、空小节与不适用内容的边界6.1 空小节单独报错主函数中有一段独立于上述检查的分支TOP003_defaultSectionContent.jsconst isSectionEmpty tokensBetweenHeadings.at(-1).type heading_close; if (isSectionEmpty) { totalErrors.push( createErrorObject( tokensBetweenHeadings[0].lineNumber, The ${headingContent} section cannot be empty, ), ); }实现要点若标题到下一标题之间的最后一个 token 是heading_close说明该小节没有任何内容直接报 The {heading} section cannot be empty不再进入后续细分检查。判例 tests/empty_section.md 构造了三个空小节断言见 TOP003.test.js[The lesson overview section cannot be empty] [The knowledge check section cannot be empty] [The additional resources section cannot be empty]6.2 非目标小节完全不受影响Introduction、Custom section等不在sectionsWithDefaultContent中的小节会被return提前跳过。这也是missing_list.md刻意保留### Custom sectionL9-L10的原因证明规则只作用于四个内置小节普通小节无论写什么都不会被 TOP003 报错。6.3 合规样例valid.md 的结构模板tests/valid.md 是零错误的黄金标准对应断言Does not flag any errors if no violationsTOP003.test.js其结构可直接作为课程作者与贡献者的模板### Introduction Text content ### Lesson overview This section contains a general overview of topics that you will learn in this lesson. - LO item. ### Custom section Text content ### Assignment div classlesson-content__panel markdown1 Assignment content /div ### Knowledge check The following questions are an opportunity to reflect on key topics in this lesson. If you cant answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. - KC item对照可见四个内置小节各自的合规形态Lesson overview默认内容 - LO item.无序列表Assignmentdiv classlesson-content__panel markdown1包裹内容Knowledge check默认内容 - KC item无序列表在 valid.md 中该小节后无 Additional resources但该小节本身就是文档结尾列表之后无多余内容因此通过检查。七、运行与验证方式7.1 手动运行 lint / fix在仓库根目录执行# 对单个文件做 lint 检查返回退出码非 0 表示存在违规 npm run lint -- markdownlint/TOP003_defaultSectionContent/tests/missing_list.md # 以 --format 模式输出修复后的内容不落盘供测试比对 npm run lint -- --format # 自动修复所有可修复问题如有序列表转无序、默认内容替换/插入、删除列表前后多余内容 npm run fixnpm run lint实际执行markdownlint-cli2见 package.json 的 scripts测试工具 test_utils/lint.js 封装了npm run lint -- file并将stderr按行拆分返回错误数组test_utils/fix.js 则以npm run lint -- --format拿到修复后的内容并剥离 markdownlint-cli2 的横幅输出需要说明仓库是只读的以上命令用于本地查看与验证对仓库内容的任何修改请通过贡献流程见 CONTRIBUTING.md进行。7.2 运行规则测试使用 Node 内置测试运行器执行 TOP003 的全部 lint 与 fix 用例npm test【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考