@dongmingzhixiu/vuexir-compiler 0.3.18 → 0.3.20
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.
- package/dist/codegen/compose.js +34 -4
- package/dist/codegen/swiftui.js +15 -2
- package/package.json +1 -1
package/dist/codegen/compose.js
CHANGED
|
@@ -427,7 +427,37 @@ function kotlinStr(s) {
|
|
|
427
427
|
return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '${\'$\'}').replace(/\n/g, '\\n').replace(/\r/g, '\\r')}"`;
|
|
428
428
|
}
|
|
429
429
|
/** 条件表达式 → Kotlin(对位 iOS swiftCondExpr:=== / !== 归一 + 单引号字符串转双引号) */
|
|
430
|
-
|
|
430
|
+
/** R114:文本绑定里引用的标识符若既不是页面状态也不是循环变量 → 视为未支持,回退空串 */
|
|
431
|
+
function safeBind(name, ctx, loop) {
|
|
432
|
+
const base = name.split('.')[0];
|
|
433
|
+
const isState = (ctx.states ?? []).some((st) => st.name === base);
|
|
434
|
+
const isLoop = loop && (loop.itemKey === base || loop.indexKey === base);
|
|
435
|
+
const isGlobal = ['true', 'false', 'Math', 'String', 'Number'].includes(base);
|
|
436
|
+
if (isState || isLoop || isGlobal)
|
|
437
|
+
return loopField(name, loop);
|
|
438
|
+
ctx.warnings?.push?.(`模板引用「${name}」在原生端没有对应状态 → 已回退空串(R114)`);
|
|
439
|
+
return '""';
|
|
440
|
+
}
|
|
441
|
+
function kotlinCondExpr(expr, ctx, loop) {
|
|
442
|
+
// R114:条件里引用了原生端不存在的标识符 → 回退为 true(保留内容)并告警,避免整页编译失败
|
|
443
|
+
{
|
|
444
|
+
// 先剥掉字符串字面量,避免把 "success" 之类当成标识符
|
|
445
|
+
const stripped = expr.replace(/'[^']*'|"[^"]*"/g, '""');
|
|
446
|
+
const ids = [...stripped.matchAll(/(?<![.\w$])([A-Za-z_$][\w$]*)/g)].map((m) => m[1]);
|
|
447
|
+
const known = new Set((ctx?.states ?? []).map((st) => st.name));
|
|
448
|
+
if (loop) {
|
|
449
|
+
known.add(loop.itemKey);
|
|
450
|
+
if (loop.indexKey)
|
|
451
|
+
known.add(loop.indexKey);
|
|
452
|
+
}
|
|
453
|
+
for (const k of ['true', 'false', 'Math', 'String', 'Number', 'stateStore'])
|
|
454
|
+
known.add(k);
|
|
455
|
+
const unknown = ids.filter((id) => !known.has(id));
|
|
456
|
+
if (unknown.length > 0 && ctx && (ctx.states ?? []).length > 0) {
|
|
457
|
+
ctx.warnings?.push?.(`条件「${expr.slice(0, 40)}」引用了原生端不存在的标识符(${unknown.join(', ')})→ 按 true 处理(R114)`);
|
|
458
|
+
return 'true';
|
|
459
|
+
}
|
|
460
|
+
}
|
|
431
461
|
// R78 续(G12):对象字段条件 → 非空判断
|
|
432
462
|
const f = expr.match(/^__vrnField__:(\w+):(\w+)$/);
|
|
433
463
|
if (f)
|
|
@@ -987,7 +1017,7 @@ function emitNode(node, ctx, depth, loop, useParamModifier = false, parentStretc
|
|
|
987
1017
|
const pad = ' '.repeat(depth);
|
|
988
1018
|
// v-if → Kotlin if 包裹(表达式为双端兼容子集 ==/!=/&&/||/!,=== / !== 由 kotlinCondExpr 归一)
|
|
989
1019
|
if (node.condition && node.type !== 'root') {
|
|
990
|
-
return (pad + 'if (' + kotlinCondExpr(node.condition) + ') {\n' +
|
|
1020
|
+
return (pad + 'if (' + kotlinCondExpr(node.condition, ctx, loop) + ') {\n' +
|
|
991
1021
|
emitNodeInner(node, ctx, depth + 1, loop, useParamModifier, parentStretch) +
|
|
992
1022
|
'\n' + pad + '}\n');
|
|
993
1023
|
}
|
|
@@ -1138,7 +1168,7 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
|
|
|
1138
1168
|
if ('text' in seg)
|
|
1139
1169
|
return String(seg.text).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '${\'$\'}').replace(/\n/g, '\\n');
|
|
1140
1170
|
if ('bind' in seg)
|
|
1141
|
-
return '${' +
|
|
1171
|
+
return '${' + safeBind(seg.bind, ctx, loop) + '}';
|
|
1142
1172
|
if ('fieldOf' in seg)
|
|
1143
1173
|
return ('${vrnField(' +
|
|
1144
1174
|
seg.fieldOf.obj +
|
|
@@ -1169,7 +1199,7 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
|
|
|
1169
1199
|
textExpr = kotlinStringExpr(p.bindExpr);
|
|
1170
1200
|
}
|
|
1171
1201
|
else if (p.bind !== undefined) {
|
|
1172
|
-
textExpr = '"${' +
|
|
1202
|
+
textExpr = '"${' + safeBind(String(p.bind), ctx, loop) + '}"';
|
|
1173
1203
|
}
|
|
1174
1204
|
else if (p.ref !== undefined) {
|
|
1175
1205
|
textExpr = '"${' + loopField(p.ref, loop) + '}"';
|
package/dist/codegen/swiftui.js
CHANGED
|
@@ -502,13 +502,23 @@ function emitNode(node, ctx) {
|
|
|
502
502
|
if (node.condition) {
|
|
503
503
|
const L = ctx.level;
|
|
504
504
|
const inner = emitNodeInner(node, ctx);
|
|
505
|
-
const
|
|
505
|
+
const known = ctx.stateNames && ctx.stateNames.size > 0 ? ctx.stateNames : undefined;
|
|
506
|
+
const swiftCond = swiftCondExpr(node.condition, known);
|
|
506
507
|
return `${indent(L)}if ${swiftCond} {\n${inner}\n${indent(L)}}`;
|
|
507
508
|
}
|
|
508
509
|
return emitNodeInner(node, ctx);
|
|
509
510
|
}
|
|
510
511
|
/** JS 条件表达式 → Swift 兼容:===/!== 为 JS 严格比较,Swift 用 ==/!=;字符串字面量单引号 → 双引号 */
|
|
511
|
-
function swiftCondExpr(expr) {
|
|
512
|
+
function swiftCondExpr(expr, known) {
|
|
513
|
+
// R114:条件引用原生端不存在的标识符 → true(保留内容),避免整页编译失败
|
|
514
|
+
if (known && known.size > 0) {
|
|
515
|
+
// 先剥掉字符串字面量,避免把 "success" 之类当成标识符
|
|
516
|
+
const stripped = expr.replace(/'[^']*'|"[^"]*"/g, '""');
|
|
517
|
+
const ids = [...stripped.matchAll(/(?<![.\w$])([A-Za-z_$][\w$]*)/g)].map((m) => m[1]);
|
|
518
|
+
const unknown = ids.filter((id) => !known.has(id) && !['true', 'false', 'Math', 'String', 'Number'].includes(id));
|
|
519
|
+
if (unknown.length > 0)
|
|
520
|
+
return 'true';
|
|
521
|
+
}
|
|
512
522
|
// R78 续(G12):对象字段条件 → 非空判断
|
|
513
523
|
const f = expr.match(/^__vrnField__:(\w+):(\w+)$/);
|
|
514
524
|
if (f)
|
|
@@ -612,6 +622,7 @@ function emitNodeInner(node, ctx) {
|
|
|
612
622
|
parentAxis: 'column',
|
|
613
623
|
level: L + 1,
|
|
614
624
|
definiteHeight: true, // root 撑满全屏 → 子项建议高度确定(R-H3 可分配)
|
|
625
|
+
stateNames: ctx.stateNames,
|
|
615
626
|
warnings: ctx.warnings,
|
|
616
627
|
path: `${ctx.path}[${i}]`,
|
|
617
628
|
inheritGap: gapInheritFor(c, props.gap, node.children ?? []),
|
|
@@ -1115,6 +1126,7 @@ function navBarModifiers(screen, warnings) {
|
|
|
1115
1126
|
return mods.join('\n');
|
|
1116
1127
|
}
|
|
1117
1128
|
export function generateScreenSource(screen, opts = {}) {
|
|
1129
|
+
const __stateNames = new Set((screen.state ?? []).map((st) => st.name));
|
|
1118
1130
|
const rt = opts.runtime ?? 'Runtime.shared';
|
|
1119
1131
|
// R42:色板存在时颜色/字体走 Theme token;R43:frame 走 View 扩展短调用
|
|
1120
1132
|
currentColorNames = opts.theme?.colorNames;
|
|
@@ -1129,6 +1141,7 @@ export function generateScreenSource(screen, opts = {}) {
|
|
|
1129
1141
|
// R-PAGESCROLL:根容器始终为可滚动视口(见 emitNode root 分支),无需内容高度启发式
|
|
1130
1142
|
const dataProps = [];
|
|
1131
1143
|
const body = emitNode(screen.ui, {
|
|
1144
|
+
stateNames: __stateNames,
|
|
1132
1145
|
parentAxis: 'column',
|
|
1133
1146
|
level: 1,
|
|
1134
1147
|
definiteHeight: true, // root 撑满全屏(maxHeight:.infinity)→ 根容器高度确定
|