From 30b190363e81f2e768009e7f6e039252dc9427f6 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Thu, 30 Jul 2026 10:49:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(fe):=20=E4=BF=AE=E5=BC=B9=E5=B1=82=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E4=B8=8D=E8=BF=87=EF=BC=88=E9=93=BE=E9=A6=96=E8=A2=AB?= =?UTF-8?q?=E5=88=A0=EF=BC=89=EF=BC=8C=E6=A3=80=E6=9F=A5=E5=99=A8=E8=A1=A5?= =?UTF-8?q?=E4=B8=8A=20wx:if=20=E9=93=BE=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Bug 上一批把 9 个记录分支从弹层整体搬到 addrecord 页时,连带删掉了带 wx:if 的 第一支(weight),整条链从 wx:elif 开头。小程序直接报 Bad attr `wx:elif` with message: `wx:if not found, then something must be wrong` 整个组件编译不过 —— 不是少个样式,是弹层完全打不开。把现在的链首 (就医前摘要)改成 wx:if。 ## 检查器补这一条 这种错「删对了 9 支、漏改 1 个属性」就会发生,而且是编译期爆炸, 检查器原来完全看不见。 判断必须靠真正的标签栈。中间走错两次,都值得写下来: **① 按缩进深度分链 —— 不行。** 这个项目里 scroll-view 和它的子节点缩进 是一样深的,「同层」判断会把父节点当兄弟节点。而且更糟的是它不报错: 前一条链的 wx:if 被当成这一条的链首,注入错误也查不出来,是个假绿。 **② 给 void 标签列白名单 —— 不行。** wxml 里 是显式闭合的, 把它当 void 就变成「开标签不入栈、闭标签却弹栈」,一路把父级弹光,8 个误报。 只认 /> 就够。 **③ 自闭合不能用单独的捕获组。** 属性那段是贪婪的,会把 /> 里的 / 一起吃掉, (\/?) 永远匹配空。多行写的 因此被当成非自闭合入了栈, 把整份栈层级带偏,7 个误报。改成从 attrs 尾部 /\/\s*$/ 判断。 ## 自测 把链首改回 wx:elif → 精确报第 96 行那一处,其余 24 个 wxml 零误报; 还原 → 恢复绿。 Co-Authored-By: Claude Opus 5 --- .../components/bottom-sheet/bottom-sheet.wxml | 4 +- pets-fe/scripts/check.js | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/pets-fe/components/bottom-sheet/bottom-sheet.wxml b/pets-fe/components/bottom-sheet/bottom-sheet.wxml index d8bfcb8..c56bae2 100644 --- a/pets-fe/components/bottom-sheet/bottom-sheet.wxml +++ b/pets-fe/components/bottom-sheet/bottom-sheet.wxml @@ -96,8 +96,8 @@ module.exports.sel = function (map, key, index, def) { 搬到 pages/addrecord —— 字段由后端 record_types.fields 驱动,一个页面吃掉 全部 24 种,不再每种一个 wx:elif。弹层现在只管「不是记一笔」的那些事: 提醒、海报、档案、发帖、评论…… --> - - + + 就医前摘要 宠物:{{pet.name}} 阶段:{{pet.stage}} diff --git a/pets-fe/scripts/check.js b/pets-fe/scripts/check.js index 654120d..fc76aed 100644 --- a/pets-fe/scripts/check.js +++ b/pets-fe/scripts/check.js @@ -91,6 +91,48 @@ for (const f of files.filter((f) => f.endsWith('.wxml'))) { } } + // wx:if / wx:elif / wx:else 链的第一支必须是 wx:if。 + // 删掉链首那一支(比如把 9 个记录分支整体搬走)会让整条链从 wx:elif 开头, + // 小程序报「wx:if not found」直接编译不过——整个组件一起挂,不是少个样式。 + // + // 判断必须靠真正的标签栈,不能靠缩进:这个项目里 scroll-view 和它的子节点 + // 缩进是一样深的,按缩进判「同层」会把父节点当成兄弟节点,什么都查不出来。 + { + const noComment = src.replace(//g, ''); + const stack = [{ last: null }]; // 每层记「上一个兄弟节点带的条件指令」 + const re = /<(\/?)([a-zA-Z][\w-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g; + let m; + while ((m = re.exec(noComment))) { + const [, closing, tag, rawAttrs] = m; + // 自闭合要从 attrs 尾部判断:属性那段是贪婪的,会把 /> 里的 / 一起吃掉, + // 单独用一个 (\/?) 捕获组永远是空的。多行写的 就是这么 + // 被当成非自闭合、入了栈,把整份栈的层级全带偏,满屏误报。 + const selfClose = /\/\s*$/.test(rawAttrs); + const attrs = rawAttrs.replace(/\/\s*$/, ''); + const top = stack[stack.length - 1]; + if (closing) { + if (stack.length > 1) stack.pop(); + continue; + } + const cond = /\bwx:if\b/.test(attrs) ? 'if' + : /\bwx:elif\b/.test(attrs) ? 'elif' + : /\bwx:else\b/.test(attrs) ? 'else' : ''; + if (cond === 'elif' || cond === 'else') { + if (top.last !== 'if' && top.last !== 'elif') { + const line = noComment.slice(0, m.index).split('\n').length; + problems.push(`${r}: 第 ${line} 行附近 <${tag} wx:${cond}> 前面没有 wx:if` + + ` —— 链首被删了,这个文件编译不过`); + } + } + // 没带条件的兄弟节点会把链断开,所以无条件覆盖 + top.last = cond || null; + // 不搞「void 标签」白名单:wxml 里 是显式闭合的, + // 把它当 void 就会「开标签不入栈、闭标签却弹栈」,一路把父级弹光, + // 于是满屏误报。只认 /> 自闭合,其余一律入栈。 + if (!selfClose) stack.push({ last: null }); + } + } + // 事件绑定的方法必须真的存在——绑了个不存在的方法,点了没反应且不报错 if (fs.existsSync(js)) { const code = read(js);