@dongmingzhixiu/vuexir-compiler 0.3.10 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1587,6 +1587,10 @@ function buildComponentTree(comp, warnings) {
1587
1587
  }
1588
1588
  function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable = [], propRefs, loopScope = [], ancestorClasses = []) {
1589
1589
  let elementClasses = []; // M5 后代选择器:本元素 class 清单(子节点递归传祖先链)
1590
+ // R107:SVG 图标(svg/path/circle/line/rect/polygon/g/defs/use)纯装饰 → 忽略,不阻断编译
1591
+ if (['svg', 'path', 'circle', 'line', 'rect', 'polygon', 'polyline', 'g', 'defs', 'use', 'text', 'ellipse'].includes(node.tag) && node.tag !== 'text') {
1592
+ return { type: 'spacer', props: { height: 0 } };
1593
+ }
1590
1594
  // R104:<br> 换行 → 占位间距(原生端没有行内换行语义)
1591
1595
  if (node.tag === 'br') {
1592
1596
  return { type: 'spacer', props: { height: 8 } };
@@ -1596,7 +1600,38 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
1596
1600
  if (forDir) {
1597
1601
  // AttributeNode 无 exp;DirectiveNode 才有(find 命中 DIRECTIVE 但 TS 不窄化 union)
1598
1602
  const exp = 'exp' in forDir ? forDir.exp : undefined;
1599
- const vf = parseVFor(exp && exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : '', warnings, vforConsts);
1603
+ const forExp = exp && exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : '';
1604
+ // R105:`v-for="x in stats"` 里的 stats 是 computed/普通变量(不是 ref 状态)
1605
+ // → 下沉成运行时求值的 JSON 数组状态,列表照常渲染
1606
+ let sunkList;
1607
+ {
1608
+ const m = forExp.trim().match(/^(?:\(([^)]+)\)|([A-Za-z_$][\w$]*))\s+in\s+([A-Za-z_$][\w$]*)$/);
1609
+ if (m) {
1610
+ const src = m[3];
1611
+ const isState = (state?.states ?? []).some((st) => st.name === src);
1612
+ const isConst = vforConsts && src in vforConsts;
1613
+ if (!isState && !isConst && scriptDeclaredNames.has(src)) {
1614
+ sunkList = addJsExprState(`JSON.stringify(${src})`);
1615
+ const vfSunk = {
1616
+ listState: sunkList,
1617
+ itemKey: (m[1] ? m[1].split(',')[0] : m[2]).trim(),
1618
+ indexKey: m[1] ? m[1].split(',')[1]?.trim() : undefined,
1619
+ sunk: true,
1620
+ };
1621
+ const innerNodeSunk = {
1622
+ ...node,
1623
+ props: node.props.filter((p) => !(p.type === NodeTypes.DIRECTIVE && p.name === 'for')),
1624
+ };
1625
+ const itemSunk = walkElement(innerNodeSunk, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable, propRefs, [
1626
+ ...loopScope,
1627
+ { itemKey: vfSunk.itemKey, indexKey: vfSunk.indexKey },
1628
+ ], ancestorClasses);
1629
+ return { type: 'for', vfor: vfSunk, children: [itemSunk] };
1630
+ }
1631
+ }
1632
+ }
1633
+ const vf = parseVFor(forExp, warnings, vforConsts);
1634
+ void sunkList;
1600
1635
  if (!vf) {
1601
1636
  throw new VueCompileError('v-for 仅支持静态数组字面量(M2,如 v-for="item in [1,2,3]" / v-for="(item, i) in [\'a\',\'b\']";动态列表随 M3/M4)');
1602
1637
  }
@@ -1874,6 +1909,10 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
1874
1909
  refOnly = name;
1875
1910
  parts.push({ bind: name });
1876
1911
  }
1912
+ else if (scriptDeclaredNames.has(name)) {
1913
+ // R106:computed / 普通 const / 解构值 → 运行时求值
1914
+ parts.push({ bind: addJsExprState(textOf(child.content).trim()) });
1915
+ }
1877
1916
  else {
1878
1917
  throw new VueCompileError(`插值 {{ ${name} }}:未在 <script setup> 中声明为 ref 状态(M1 白名单:仅支持已声明状态)`);
1879
1918
  }
@@ -1928,6 +1967,16 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
1928
1967
  else if (child.type === NodeTypes.COMMENT) {
1929
1968
  // 忽略注释
1930
1969
  }
1970
+ else if (child.type === NodeTypes.ELEMENT && mapping.type === 'text') {
1971
+ // R107:行内嵌套(<b>光<b/></b>、<span><svg/></span>)→ 有文字并入,纯图标忽略
1972
+ const innerText = elementInnerText(child).trim();
1973
+ if (innerText) {
1974
+ text += innerText;
1975
+ parts.push({ text: innerText }); // 同步进 parts,避免只走 bindParts 时丢失
1976
+ }
1977
+ else
1978
+ warnings.push(`<${node.tag}> 内嵌图标元素 <${child.tag}> 已忽略(R107)`);
1979
+ }
1931
1980
  else {
1932
1981
  throw new VueCompileError(`<${node.tag}> 内嵌子元素(行内混合)暂不支持(text 节点仅文本/插值)`);
1933
1982
  }
@@ -1954,14 +2003,20 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
1954
2003
  else {
1955
2004
  const t = text.trim();
1956
2005
  if (!t) {
1957
- throw new VueCompileError(`<${node.tag}> 无文本内容`);
2006
+ // R106:空的行内元素(图标占位 <i></i>)→ 空文本,内容不丢但也不阻断编译
2007
+ warnings.push(`<${node.tag}> 无文本内容 → 已降级为空占位(R106)`);
2008
+ props.text = ' ';
2009
+ }
2010
+ else {
2011
+ props.text = t;
1958
2012
  }
1959
- props.text = t;
1960
2013
  }
1961
2014
  }
1962
2015
  else {
1963
2016
  for (const child of node.children) {
1964
2017
  if (mapping.type === 'button') {
2018
+ if (child.type === NodeTypes.ELEMENT && (child.tag === 'br' || !elementInnerText(child).trim()))
2019
+ continue; // R106/R107:按钮内换行/纯图标忽略
1965
2020
  if (child.type === NodeTypes.TEXT && child.content.trim()) {
1966
2021
  props.label = ((props.label ?? '') + child.content.trim()).trim();
1967
2022
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongmingzhixiu/vuexir-compiler",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "Vue SFC → IR → 原生源码(SwiftUI / Compose)编译器核心:纯函数、零 IO",
5
5
  "type": "module",
6
6
  "license": "MIT",