fix(fe): 修弹层编译不过(链首被删),检查器补上 wx:if 链校验

## 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 里 <image></image> 是显式闭合的,
把它当 void 就变成「开标签不入栈、闭标签却弹栈」,一路把父级弹光,8 个误报。
只认 /> 就够。

**③ 自闭合不能用单独的捕获组。** 属性那段是贪婪的,会把 /> 里的 / 一起吃掉,
(\/?) 永远匹配空。多行写的 <input ... /> 因此被当成非自闭合入了栈,
把整份栈层级带偏,7 个误报。改成从 attrs 尾部 /\/\s*$/ 判断。

## 自测
把链首改回 wx:elif → 精确报第 96 行那一处,其余 24 个 wxml 零误报;
还原 → 恢复绿。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-30 10:49:23 +08:00
parent 15b52eebd0
commit 30b190363e
2 changed files with 44 additions and 2 deletions
@@ -96,8 +96,8 @@ module.exports.sel = function (map, key, index, def) {
搬到 pages/addrecord —— 字段由后端 record_types.fields 驱动,一个页面吃掉
全部 24 种,不再每种一个 wx:elif。弹层现在只管「不是记一笔」的那些事:
提醒、海报、档案、发帖、评论…… -->
<!-- 就医前摘要 -->
<block wx:elif="{{innerType === 'vetSummary'}}">
<!-- 就医前摘要。这是链上第一支,必须是 wx:if -->
<block wx:if="{{innerType === 'vetSummary'}}">
<view class="sheet-h3">就医前摘要</view>
<view class="sheet-p pre">宠物:{{pet.name}}
阶段:{{pet.stage}}
+42
View File
@@ -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(/<!--[\s\S]*?-->/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 尾部判断:属性那段是贪婪的,会把 /> 里的 / 一起吃掉,
// 单独用一个 (\/?) 捕获组永远是空的。多行写的 <input ... /> 就是这么
// 被当成非自闭合、入了栈,把整份栈的层级全带偏,满屏误报。
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 里 <image></image> 是显式闭合的,
// 把它当 void 就会「开标签不入栈、闭标签却弹栈」,一路把父级弹光,
// 于是满屏误报。只认 /> 自闭合,其余一律入栈。
if (!selfClose) stack.push({ last: null });
}
}
// 事件绑定的方法必须真的存在——绑了个不存在的方法,点了没反应且不报错
if (fs.existsSync(js)) {
const code = read(js);