
marked 的 pedantic 列表项文本解析list_item_text测试用例源码级剖析【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked导读本文围绕 marked 仓库中test/specs/new/list_item_text.md这份回归测试用例展开深入讲解 marked 在pedantic: true原始 John Gruber 宽松 Markdown 规范模式下如何解析外层列表项内部嵌套列表 后续文本段落这类边界输入。读完本文你将掌握该测试的输入、期望输出与 pedantic 模式的关联列表项文本item.text在 Tokenizer.ts 中的提取与缩进归一化流程pedantic选项对块级与行内语法的整体影响见 Lexer.ts 与 rules.ts以及如何用测试运行器复现与验证该用例。一、测试用例全貌输入、前置条件与期望输出1.1 输入与前置元数据test/specs/new/list_item_text.md全文如下--- pedantic: true --- * item1 * item2 text文件由两部分组成YAML front matter声明本用例仅在pedantic: true条件下生效。marked 的测试运行器 run-specs-tests 的解析入口 会读取该元数据把pedantic: true合并进Marked实例的选项除显式声明的选项外其余选项如gfm保持默认值。这与original/目录下所有用例统一使用{ gfm: false, pedantic: true }见 run-spec-tests.js的约定一致说明 pedantic 是原始规范回归组的标志性选项。正文 Markdown一段以两个空格缩进开头的内容结构为* item1 ← 外层无序列表项项目符号缩进 2 空格 ← 空行 * item2 ← 内层嵌套列表项缩进 4 空格 ← 空行 text ← 外层列表项内、嵌套列表之后的文本行缩进回到 2 空格1.2 期望输出同名期望文件test/specs/new/list_item_text.htmlullipitem1/p ulliitem2 /li/ul ptext/p /li/ul从中可读出三条关键断言整个输入被解析为单一外层ul且不含缩进代码块——尽管text与item2的源码缩进恰好满足4 空格代码块的视觉特征pedantic 模式下它们仍属于列表项的延续内容外层列表项是松散列表项item1、text各自被p包裹pitem1/p、ptext/p因为两个空行使列表变 loose对应源码list.loose判定逻辑见下文第三节内层列表liitem2 /li被渲染在第一个p之后即item2属于外层项的子块内容而非新开一个顶层列表。也就是说这份用例锁定的行为是pedantic 模式下嵌套列表后的同缩进文本行必须继续留在外层列表项内部作为独立的段落块输出。二、pedantic 模式选项定义与语法集切换2.1 选项定义与默认值pedantic是 MarkedOptions.ts 中声明的布尔选项默认值为false见 defaults.ts// src/MarkedOptions.ts pedantic?: boolean;启用后marked 不再追求 CommonMark/GFM 的规范兼容而是模拟 John Gruber 原始 Markdown 的宽松行为rules.ts 注释 明确标注Pedantic grammar (original John Grubers loose markdown specification)。2.2 语法集的切换逻辑pedantic对词法分析的影响集中在 Lexer.ts 构造函数三套块级/行内语法规则按优先级切换——pedantic优先于gfmif (this.options.pedantic) { rules.block block.pedantic; rules.inline inline.pedantic; } else if (this.options.gfm) { rules.block block.gfm; rules.inline this.options.breaks ? inline.breaks : inline.gfm; }与列表解析直接相关的差异包括block.pedanticrules.ts基于blockNormal扩展但fences被替换为noopTest围栏代码块不支持、def/heading/lheading/paragraph均使用宽松版本。其中paragraph规则把|list、|fences、|html从可中断段落清单中移除意味着 pedantic 下更多行会被吞并进列表上下文block.normalrules.ts与block.pedantic是同源基类本用例未开启gfm时列表、标题等主要块规则并无差异预处理差异在 Lexer.blockTokens 中pedantic 模式额外执行src.replace(other.tabCharGlobal, ).replace(other.spaceLine, )即把所有制表符展开为 4 空格、并删除纯空格行这为后续列表缩进计算提供了稳定基础。三、源码级解析流程list_item_text在 Tokenizer 中的行走路径外层listtoken 的产生位于 Tokenizer.list()我们按执行顺序还原本用例的处理。3.1 匹配首个项目符号并计算缩进list()首先用this.rules.block.list匹配到* item1bull *非有序isordered false。pedantic 分支L273-L275把后续项目符号正则放宽为[*-]任意项目符号都算同列表而非默认模式下仅接受相同的*。这是 pedantic 列表语义的第一个差异点。随后进入关键的第一项缩进计算L296-L311let line expandTabs(cap[2].split(\n, 1)[0], cap[1].length); ... if (this.options.pedantic) { indent 2; // ← 固定缩进基准为 2 itemContents line.trimStart(); // ← 去掉前导空白取内容 }在 pedantic 分支中外层项的缩进基准被硬编码为 2indent 2item1的内容直接trimStart()得到item1。而在非 pedantic 分支缩进由首个非空格字符位置动态计算L307-L310因此本用例只有开启pedantic: true才能得到缩进基准 2从而让后续text2 空格缩进恰好等于外层项的延续缩进。3.2 子行归属判定text为何留在列表项内解析完* item1后循环检查后续行L327-L401逐行做六大提前终止测试if (fencesBeginRegex.test(nextLine)) break; // 围栏代码块 if (headingBeginRegex.test(nextLine)) break; // ATX 标题 if (htmlBeginRegex.test(nextLine)) break; // HTML 块 if (blockquoteBeginRegex.test(nextLine)) break; // 引用块 if (nextBulletRegex.test(nextLine)) break; // 新项目符号 if (hrRegex.test(nextLine)) break; // 水平分割线这些正则均由indent2动态生成例如nextBulletRegexrules.ts为^ {0,2}(?:[*-]|\d{1,9}[.)])...即只有缩进不超过 2 空格的项目符号才被视为新项。本用例的三行后续内容逐行分析* item24 空格缩进不匹配nextBulletRegex缩进 4 2因此它不是新列表项但它的缩进 ≥indent2走继续并入列表项分支L371-L372把nextLineWithoutTabs.slice(indent)追加进itemContents。这里 pedantic 有一个独特处理先执行listReplaceNesting替换L334-L336即用正则^ {1,4}(?( {4})*[^ ])rules.ts把1~4 空格起始、后面紧跟 4 空格倍数缩进的行重新对齐为 2 空格再按indent切片。这正是 pedantic 模式下嵌套列表得以在itemContents中形成、并被下一轮递归blockTokens识别为子ul的关键空行blankLine置位但itemContents仍追加空行作为段落分隔与 loose 判定依据text2 空格缩进indent2≥nextLineWithoutTabs.search(nonSpaceChar)2同样满足并入条件追加为第二段文本text。3.3 最终itemContents与递归子解析三项内容拼接后第一项的外层item.text即 Tokenizer.ts L418 的itemContents为item1 * item2 text随后在 L438-L448 的第一轮子 tokenize 中this.lexer.blockTokens(item.text, [])递归解析这份内容得到三个子块段落item1→ 嵌套list* item2→ 段落text。同时由于子 token 序列中存在space类型的空行 token且spacers.some(t this.rules.other.anyLine.test(t.raw))判定其含完整换行L444-L447list.loose被置为true随后 L496-L505 将所有子项loose置位、并把text类型子 token 升级为paragraph——这正是期望输出中pitem1/p、ptext/p的来源。3.4 渲染输出Parser 对listtoken 调用 Renderer.list()遍历token.items调用 Renderer.listitem()li${this.parser.parse(item.tokens)}/li最终把上述三个子块逐一渲染拼出与期望文件一致的输出ullipitem1/p ulliitem2 /li/ul ptext/p /li/ul四、对照实验非 pedantic 模式下的差异为印证pedantic在该用例中的决定性作用可把同样的输入放到默认pedantic: false模式下对比。依据 Tokenizer.ts L307-L310 的默认分支缩进改为由首个非空格字符位置计算* item1的实际缩进为 2但indent计算变为indent line.search(nonSpaceChar)再加项目符号长度更关键的是 L334-L339 不再执行listReplaceNesting重对齐* item2与text的归属判定会遵循 CommonMark 缩进语义发生变化item2可能因 4 空格缩进被当作外层项的缩进代码块或产生不同嵌套输出结构将与list_item_text.html不一致。这解释了为什么该用例必须在 front matter 中强制pedantic: true——它本身就是一份锁定 pedantic 列表缩进语义的回归测试。五、测试体系中的定位与复现方法5.1 目录定位该用例位于test/specs/new/目录。按 run-spec-tests.js 的加载逻辑new/目录的用例使用无额外默认选项的parse函数仅由每个用例自身的 front matter 决定选项与original/固定pedantic: true、gfm/、commonmark/组成五组规范测试。5.2 运行命令仓库 package.jsontest 脚本提供了完整的规范测试入口# 仅运行规范测试含 new/ 目录 npm run test:specs # 或仅运行规范测试的 only 标记用例 npm run test:specs:onlytest:specs实际执行node --test --test-reporterspec test/run-spec-tests.js。若要为 new/ 组新增或修改用例可运行npm run test:updatenode test/update-specs.js批量更新期望 HTML。运行前需先执行npm run build生成lib/下的 ESM 产物run-spec-tests.js从../lib/marked.esm.js导入。5.3 手写最小验证脚本也可绕过测试框架直接构造Marked实例复现import { Marked } from ./lib/marked.esm.js; const src * item1\n\n * item2\n\n text\n; // pedantic 模式与 list_item_text.html 期望一致 console.log(new Marked({ pedantic: true, gfm: false }).parse(src)); // 默认模式结构不同用于对照 console.log(new Marked({ gfm: false }).parse(src));注意 pedantic 模式下gfm会被rules.block block.pedantic分支覆盖见 Lexer.ts L48-L58因此显式设置gfm: false仅用于与original/组配置保持一致、避免歧义。六、扩展讨论pedantic 对行内语法的连带影响pedantic不只作用于块级列表还通过inline.pedantic影响行内解析Lexer.ts L49-L50。相关实现点包括行内链接解析Tokenizer中 L716-L731 使用pedanticHrefTitlerules.ts L79以宽松方式拆分 href 与 title并允许只有左尖括号、没有右尖括号的链接形式if (this.options.pedantic !endAngleBracket.test(trimmedUrl))强调定界符rules.ts L297 的注释指出pedantic 的 LDelim 在nextChar判断中排除开引号/闭引号使*text*更宽松地触发强调。这说明list_item_text所验证的固定indent 2与listReplaceNesting重对齐只是 pedantic 语义在块级列表上的一个切片同一选项还牵动链接、强调等行内规则使用时应整体把握详见 MarkedOptions.ts 选项说明 与 rules.ts 的 pedantic 分组。七、小结list_item_text是 marked 中一份小而关键的 pedantic 回归用例它验证了三件事固定缩进基准pedantic 模式下列表项缩进硬编码为 2Tokenizer.ts L301-L303使 2 空格缩进的text被判定为列表项延续内容嵌套重对齐listReplaceNesting正则把 4 空格缩进的嵌套列表重新对齐为 2 空格Tokenizer.ts L334-L336保证* item2被递归解析为子ul松散判定空行使list.loose trueTokenizer.ts L442-L448最终渲染出pitem1/p、ptext/p的段落包裹结构。理解这份用例既能帮助你在使用pedantic: true处理遗留 Markdown 文档时预判列表结构也能为阅读 marked 词法分析器Lexer.ts、Tokenizer.ts与规范测试体系test/run-spec-tests.js提供一条清晰的入口路径。【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考