ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Wekan 中 Jade/Pug 模板的非法语法守卫:anti-cases 反例测试集解析

Wekan 中 Jade/Pug 模板的非法语法守卫:anti-cases 反例测试集解析 Wekan 中 Jade/Pug 模板的非法语法守卫anti-cases 反例测试集解析【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan在 Wekan 的客户端模板体系中Jade现称 Pug是视图层的主要书写语言——client/components目录下 131 个.jade文件支撑了看板、卡片、列表等全部界面。为了保证这些模板能被 Meteor 的 Blaze/Spacebars 编译器可靠转换Wekan 打包了一套源自 mquandalle:jade-compiler 的 meteor-jade-loader其中内嵌了一份 fork 版 Jade 1.3.0 及其完整测试套件。本文聚焦其中的anti-cases反例测试集这个目录收录了一批历史上曾被解析器静默接受、但按语法本应报错的非法 Jade 片段并配套回归测试确保它们全部抛出带文件名与行号的编译错误。读完本文你将掌握 Jade 语法边界判定的完整清单、对应的底层报错实现位置以及如何在 Wekan 项目里自查和复现这些错误。一、背景为什么需要一份反例测试集test/anti-cases/readme.md对该目录的定位描述得十分直白This folder collects examples of files that are not validjade, but were at some point accepted by the parser without throwing an error. The tests ensure that all these cases now throw some form of error message (hopefully a helpful one).也就是说anti-cases不是常规的正确用法示范那属于同级的test/cases正向用例集而是一份历史 bug 清单的固化凡是曾经漏报、误收的非法语法都被写成最小复现文件放进这里并用测试保证此后永远报错。这是典型的回归测试regression test思路——先让错误暴露再把错误固化为断言。从仓库结构看jade-compiler.js 把整条编译链串联起来自定义 Lexer处理component、if/unless/each/with等语法→ 自定义 Parser包装 jade 解析器并支持 markdown mixin→ Spacebars 编译器 → JadeCompiler 对外 API其中var jade require(./vendor/jade)见 jade-compiler.js。因此vendor/jade目录下的解析器与测试正是 Wekan 客户端模板编译所依赖的那份实现anti-cases的每个反例最终都会在meteor-jade-loader解析用户模板时同样生效。二、测试如何执行run.js 中的反例驱动逻辑反例测试的驱动代码位于 test/run.js核心逻辑非常简洁var anti fs.readdirSync(test/anti-cases).filter(function(file){ return ~file.indexOf(.jade); }).map(function(file){ return file.replace(.jade, ); }); describe(certain syntax is not allowed and will throw a compile time error, function () { anti.forEach(function(test){ var name test.replace(/[-.]/g, ); it(name, function(){ var path test/anti-cases/ test .jade; var str fs.readFileSync(path, utf8); try { var fn jade.compile(str, { filename: path, pretty: true, basedir: test/anti-cases }); } catch (ex) { ex.should.be.an.instanceof(Error); ex.message.replace(/\\/g, /).should.startWith(path); ex.message.replace(/\\/g, /).should.match(/:\d$/m); return; } throw new Error(test should have thrown an error); }) }); });这段代码揭示了反例测试的三个关键断言也是你排查模板报错时应该对齐的错误契约必须抛错对每个anti-cases/*.jade文件调用jade.compile(str, { filename, pretty: true, basedir: ... })如果编译成功没有进入 catch 分支测试直接以should have thrown an error失败错误必须是 Error 实例ex.should.be.an.instanceof(Error)错误信息必须携带文件路径与行号消息经路径反斜杠归一化后需以该反例文件名开头并匹配:\d$即形如test/anti-cases/xxx.jade:12的文件名:行号格式这正是jade.compile传入filename选项的价值所在——它让解析器在抛出Error时把出错位置拼进消息。三、18 个反例全解析非法语法清单与底层实现以下是当前anti-cases目录收录的全部 18 个反例文件及其对应的解析器报错实现报错逻辑位于 lexer.js 与 parser.js。按问题类别分组介绍3.1 属性语法错误3 例attrs.unescaped.jade——未转义属性错误用法script(typetext/x-template) #user(id!user-% user.id %) h1 % user.title %id!...这种非转义属性写法在此场景下属于非法组合与test/cases/attrs.unescaped.jade的正向用例形成对比。key-ending-badly.jade与key-char-ending-badly.jade——属性键值拼接残缺div(fooabc) // key-ending-badly.jade div(foo!~abc) // key-char-ending-badly.jade属性区(...)内出现无法被合法解析的字符序列。lexer 在扫描属性时遇到预期之外的字符会直接报错throw new Error(Unexpected character str[i 1] expected , \\n, ,, ! or ); // lexer.js:651 throw new Error(Unexpected character str[i] expected ); // lexer.js:663open-brace-in-attributes.jade——属性中未闭合的花括号div(title[)属性值使用了[开头却未闭合。lexer 的表达式扫描逻辑会核对括号配对if (start ! ( start ! { start ! [) throw new Error(unrecognized start character); // lexer.js:128 if (this.input[range.end] ! end) throw new Error(start character start does not match end character this.input[range.end]); // lexer.js:131同时Nesting must match on expressionlexer.js也负责这类嵌套配对校验。3.2 条件与分支结构错误3 例else-without-if.jade——孤儿 elseelse .fooparser 在遇到没有对应if的else时抛出throw new Error(Unexpected else without if); // parser.js:335else-condition.jade——else 携带条件if foo div else bar articleelse bar的写法不合法正确写法是else if bar。lexer 给出明确提示throw new Error(else cannot have a condition, perhaps you meant else if); // lexer.js:513case-without-with.jade——case 分支没有 whencase foo .divcase后直接跟普通块而非whenparser 的 token 校验拒绝这种结构throw new Error(unexpected token this.peek().type ); // parser.js:2333.3 doctype 相关1 例doctype-5.jade——旧式 HTML5 声明doctype 5 //not a doctypedoctype 5是旧版写法现在必须写作doctype html。lexer 在 doctype 词法阶段拦截throw new Error(doctype 5 is deprecated, you must now use doctype html); // lexer.js:253同文件的!!!旧语法也被弃用见 lexer.js。3.4 自闭合标签错误用法2 例self-closing.jade与self-closing-tag-with-body.jade——自闭合标签携带内容doctype html img Image with content? // self-closing.jade input Inputs cant have content // self-closing-tag-with-body.jadeimg、input等标签自带 self-closing 语义见 self-closing.js不允许再写正文。parser 明确拒绝throw new Error(tag.name is self closing and should not have content.); // parser.js:7753.5 Mixin 与 Block 误用4 例non-mixin-block.jade——block 脱离 mixindiv blockblock只能出现在继承布局layout与 mixin 场景中匿名 block 单独出现不合法throw new Error(Anonymous blocks are not allowed unless they are part of a mixin.); // parser.js:527mixins-blocks-with-bodies.jade——mixin block 带正文mixin foo block barmixin 内部的block不应自带 body。inlining-a-mixin-after-a-tag.jade——标签后直接内联 mixin 调用foo()bar()标签/表达式行内出现两个并排调用parser 在行尾 token 校验时报Unexpected token对应 parser.js 的Unexpected token ...逻辑。multiple-non-nested-tags-on-a-line.jade——一行多个并列标签foo()bar与上一例类似foo()bar试图在一行内拼两个独立标签parser 抛出Unexpected token。3.6 过滤器与缩进问题2 例non-existant-filter.jade——引用不存在的过滤器:not-a-valid-filter foo bar:xxx过滤器必须在已注册列表中。运行时过滤器注册表抛错throw new Error(unknown filter : name ); // filters.js:14 与 filters-client.js:8tabs-and-spaces.jade——制表符与空格混用缩进div div article第二行用两个空格、第三行用 Tab缩进风格混用直接判非法throw new Error(Invalid indentation, you can use tabs or spaces but not both); // lexer.js:7483.7 其他1 例case-when.jade——when 游离于 case 之外when 5 .foo when 6 .barwhen必须包裹在case语句内部顶层孤立出现即报错。四、错误信息契约与正向用例的对照反例的对立面是test/cases目录——那里存放约两百个正向.jade用例及其期望输出的.html文件如basic.jade、mixins.jade、inheritance.extend.jade、include-filter.jade等可对比 test/cases 目录。run.js同样遍历正向用例并断言JSON.stringify(actual.trim())与期望 HTML 完全一致test/run.js。两套用例共同构成 Jade 语法的红绿灯cases绿灯合法语法应产出确定 HTMLanti-cases红灯非法语法必须抛错、且错误信息满足以文件名开头 :行号结尾的契约。这种成对对照 回归断言的测试设计使得任何一次 lexer/parser 的改动只要让某个反例从报错退化为静默通过测试套件立刻失败从而防止历史 bug 复活。五、在 Wekan 中如何复现与自查由于meteor-jade-loader直接复用了这份 vendor 版 jade你在 Wekan 仓库内即可本地复现每个反例的报错行为进入反例目录查看全部 18 个最小复现文件test/anti-cases运行完整测试套件Mocha其中certain syntax is not allowed and will throw a compile time error用例组即反例回归测试配置见 test/mocha.opts手动构造等价输入调用jade.compile(str, { filename: demo.jade, pretty: true })观察是否抛出带demo.jade:行号的 Error对照 lexer.js 与 parser.js 中的throw new Error(...)定位具体报错分支。排查实战建议当 Wekan 客户端模板编译失败时优先确认错误信息是否满足文件名 行号格式——这是诊断的锚点随后对照上文 3.1–3.7 的类别清单快速判断属于属性语法、条件分支、自闭合标签、mixin/block、过滤器还是缩进问题。例如看到Invalid indentation, you can use tabs or spaces but not both直接去对应.jade文件检查是否混用了 Tab 与空格。六、小结anti-cases反例测试集的价值不在于展示怎么写对而在于强制约束什么必须报错。通过 readme.md 对目录定位的说明、run.js 的三重断言抛错、Error 实例、文件名:行号契约以及 lexer/parser 中散布的十余处throw new Error实现Wekan 得以保证任何历史上有过漏报的非法模板语法都会在编译期被清晰、可定位地拒绝——这正是 Meteor 生态下大规模模板工程的健壮性基石。理解这份测试集等于掌握了 Jade 语法边界判定的完整源码级证据链。【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表