@odoo/owl 3.0.0-alpha.19 → 3.0.0-alpha.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.
@@ -328,6 +328,7 @@ function interpolate(s) {
328
328
  return replaceDynamicParts(s, compileExpr);
329
329
  }
330
330
 
331
+ const zero = Symbol("zero");
331
332
  const whitespaceRE = /\s+/g;
332
333
  // using a non-html document so that <inner/outer>HTML serializes as XML instead
333
334
  // of HTML (as we will parse it as xml later)
@@ -446,10 +447,11 @@ class CodeTarget {
446
447
  name;
447
448
  indentLevel = 0;
448
449
  loopLevel = 0;
450
+ loopCtxVars = [];
451
+ tSetVars = new Map();
449
452
  code = [];
450
453
  hasRoot = false;
451
- hasCache = false;
452
- shouldProtectScope = false;
454
+ needsScopeProtection = false;
453
455
  on;
454
456
  constructor(name, on) {
455
457
  this.name = name;
@@ -467,13 +469,8 @@ class CodeTarget {
467
469
  generateCode() {
468
470
  let result = [];
469
471
  result.push(`function ${this.name}(ctx, node, key = "") {`);
470
- if (this.shouldProtectScope) {
472
+ if (this.needsScopeProtection) {
471
473
  result.push(` ctx = Object.create(ctx);`);
472
- result.push(` ctx[isBoundary] = 1`);
473
- }
474
- if (this.hasCache) {
475
- result.push(` let cache = ctx.cache || {};`);
476
- result.push(` let nextCache = ctx.cache = {};`);
477
474
  }
478
475
  for (let line of this.code) {
479
476
  result.push(line);
@@ -506,7 +503,6 @@ const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
506
503
  class CodeGenerator {
507
504
  blocks = [];
508
505
  nextBlockId = 1;
509
- hasSafeContext;
510
506
  isDebug = false;
511
507
  targets = [];
512
508
  target = new CodeTarget("template");
@@ -532,7 +528,6 @@ class CodeGenerator {
532
528
  }
533
529
  this.translatableAttributes = [...attrs];
534
530
  }
535
- this.hasSafeContext = options.hasSafeContext || false;
536
531
  this.dev = options.dev || false;
537
532
  this.ast = ast;
538
533
  this.templateName = options.name;
@@ -549,7 +544,6 @@ class CodeGenerator {
549
544
  block: null,
550
545
  index: 0,
551
546
  forceNewBlock: false,
552
- isLast: true,
553
547
  translate: true,
554
548
  translationCtx: "",
555
549
  tKeyExpr: null,
@@ -656,39 +650,6 @@ class CodeGenerator {
656
650
  this.define(block.varName, blockExpr);
657
651
  }
658
652
  }
659
- /**
660
- * Captures variables that are used inside of an expression. This is useful
661
- * because in compiled code, almost all variables are accessed through the ctx
662
- * object. In the case of functions, that lookup in the context can be delayed
663
- * which can cause issues if the value has changed since the function was
664
- * defined.
665
- *
666
- * @param expr the expression to capture
667
- * @param forceCapture whether the expression should capture its scope even if
668
- * it doesn't contain a function. Useful when the expression will be used as
669
- * a function body.
670
- * @returns a new expression that uses the captured values
671
- */
672
- captureExpression(expr, forceCapture = false) {
673
- if (!forceCapture && !expr.includes("=>")) {
674
- return compileExpr(expr);
675
- }
676
- const tokens = compileExprToArray(expr);
677
- const mapping = new Map();
678
- return tokens
679
- .map((tok) => {
680
- if (tok.varName && !tok.isLocal) {
681
- if (!mapping.has(tok.varName)) {
682
- const varId = generateId("v");
683
- mapping.set(tok.varName, varId);
684
- this.define(varId, tok.value);
685
- }
686
- tok.value = mapping.get(tok.varName);
687
- }
688
- return tok.value;
689
- })
690
- .join("");
691
- }
692
653
  translate(str, translationCtx) {
693
654
  const match = translationRE.exec(str);
694
655
  return match[1] + this.translateFn(match[2], translationCtx) + match[3];
@@ -802,7 +763,28 @@ class CodeGenerator {
802
763
  if (modifiers.length) {
803
764
  modifiersCode = `${modifiers.join(",")}, `;
804
765
  }
805
- return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
766
+ const compiled = compileExpr(handler);
767
+ if (!compiled.trim()) {
768
+ return `[${modifiersCode}, ctx]`;
769
+ }
770
+ let hoistedExpr;
771
+ const arrowMatch = compiled.match(/^(\([^)]*\))\s*=>/);
772
+ const bareArrowMatch = !arrowMatch && compiled.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
773
+ if (arrowMatch) {
774
+ const inner = arrowMatch[1].slice(1, -1).trim();
775
+ const rest = compiled.slice(arrowMatch[0].length);
776
+ hoistedExpr = inner ? `(ctx,${inner})=>${rest}` : `(ctx)=>${rest}`;
777
+ }
778
+ else if (bareArrowMatch) {
779
+ const rest = compiled.slice(bareArrowMatch[0].length);
780
+ hoistedExpr = `(ctx,${bareArrowMatch[1]})=>${rest}`;
781
+ }
782
+ else {
783
+ hoistedExpr = `(ctx, ev) => (${compiled}).call(ctx['this'], ev)`;
784
+ }
785
+ const id = generateId("hdlr_fn");
786
+ this.staticDefs.push({ id, expr: hoistedExpr });
787
+ return `[${modifiersCode}${id}, ctx]`;
806
788
  }
807
789
  compileTDomNode(ast, ctx) {
808
790
  let { block, forceNewBlock } = ctx;
@@ -907,7 +889,7 @@ class CodeGenerator {
907
889
  let valueCode = `ev.target.${targetAttr}`;
908
890
  valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
909
891
  valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
910
- const handler = `[(ev) => { ${exprId}.set(${valueCode}); }]`;
892
+ const handler = `[(ctx, ev) => { ${exprId}.set(${valueCode}); }, ctx]`;
911
893
  idx = block.insertData(handler, "hdlr");
912
894
  attrs[`block-handler-${idx}`] = eventType;
913
895
  }
@@ -945,7 +927,6 @@ class CodeGenerator {
945
927
  block,
946
928
  index: block.childNumber,
947
929
  forceNewBlock: false,
948
- isLast: ctx.isLast && i === children.length - 1,
949
930
  tKeyExpr: ctx.tKeyExpr,
950
931
  nameSpace,
951
932
  tModelSelectedExpr,
@@ -975,6 +956,16 @@ class CodeGenerator {
975
956
  }
976
957
  return block.varName;
977
958
  }
959
+ compileZero() {
960
+ this.helpers.add("zero");
961
+ const isMultiple = this.slotNames.has(zero);
962
+ this.slotNames.add(zero);
963
+ let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
964
+ if (isMultiple) {
965
+ key = this.generateComponentKey(key);
966
+ }
967
+ return `ctx[zero]?.(node, ${key}) || text("")`;
968
+ }
978
969
  compileTOut(ast, ctx) {
979
970
  let { block } = ctx;
980
971
  if (block) {
@@ -983,8 +974,7 @@ class CodeGenerator {
983
974
  block = this.createBlock(block, "html", ctx);
984
975
  let blockStr;
985
976
  if (ast.expr === "0") {
986
- this.helpers.add("zero");
987
- blockStr = `ctx[zero]`;
977
+ blockStr = this.compileZero();
988
978
  }
989
979
  else if (ast.body) {
990
980
  let bodyValue = null;
@@ -1064,7 +1054,9 @@ class CodeGenerator {
1064
1054
  block = this.createBlock(block, "list", ctx);
1065
1055
  this.target.loopLevel++;
1066
1056
  const loopVar = `i${this.target.loopLevel}`;
1067
- this.addLine(`ctx = Object.create(ctx);`);
1057
+ const ctxVar = generateId("ctx");
1058
+ this.addLine(`const ${ctxVar} = ctx;`);
1059
+ this.target.loopCtxVars.push(ctxVar);
1068
1060
  const vals = `v_block${block.id}`;
1069
1061
  const keys = `k_block${block.id}`;
1070
1062
  const l = `l_block${block.id}`;
@@ -1077,17 +1069,18 @@ class CodeGenerator {
1077
1069
  }
1078
1070
  this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
1079
1071
  this.target.indentLevel++;
1072
+ this.addLine(`let ctx = Object.create(${ctxVar});`);
1080
1073
  this.addLine(`ctx[\`${ast.elem}\`] = ${keys}[${loopVar}];`);
1081
- if (!ast.hasNoFirst) {
1074
+ if (!(ast.noFlags & 1 /* ForEachNoFlag.First */)) {
1082
1075
  this.addLine(`ctx[\`${ast.elem}_first\`] = ${loopVar} === 0;`);
1083
1076
  }
1084
- if (!ast.hasNoLast) {
1077
+ if (!(ast.noFlags & 2 /* ForEachNoFlag.Last */)) {
1085
1078
  this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${keys}.length - 1;`);
1086
1079
  }
1087
- if (!ast.hasNoIndex) {
1080
+ if (!(ast.noFlags & 4 /* ForEachNoFlag.Index */)) {
1088
1081
  this.addLine(`ctx[\`${ast.elem}_index\`] = ${loopVar};`);
1089
1082
  }
1090
- if (!ast.hasNoValue) {
1083
+ if (!(ast.noFlags & 8 /* ForEachNoFlag.Value */)) {
1091
1084
  this.addLine(`ctx[\`${ast.elem}_value\`] = ${vals}[${loopVar}];`);
1092
1085
  }
1093
1086
  this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
@@ -1097,35 +1090,12 @@ class CodeGenerator {
1097
1090
  this.addLine(`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
1098
1091
  this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
1099
1092
  }
1100
- let id;
1101
- if (ast.memo) {
1102
- this.target.hasCache = true;
1103
- id = generateId();
1104
- this.define(`memo${id}`, compileExpr(ast.memo));
1105
- this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
1106
- this.addLine(`if (vnode${id}) {`);
1107
- this.target.indentLevel++;
1108
- this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
1109
- this.target.indentLevel++;
1110
- this.addLine(`${c}[${loopVar}] = vnode${id};`);
1111
- this.addLine(`nextCache[key${this.target.loopLevel}] = vnode${id};`);
1112
- this.addLine(`continue;`);
1113
- this.target.indentLevel--;
1114
- this.addLine("}");
1115
- this.target.indentLevel--;
1116
- this.addLine("}");
1117
- }
1118
1093
  const subCtx = createContext(ctx, { block, index: loopVar });
1119
1094
  this.compileAST(ast.body, subCtx);
1120
- if (ast.memo) {
1121
- this.addLine(`nextCache[key${this.target.loopLevel}] = Object.assign(${c}[${loopVar}], {memo: memo${id}});`);
1122
- }
1123
1095
  this.target.indentLevel--;
1124
1096
  this.target.loopLevel--;
1097
+ this.target.loopCtxVars.pop();
1125
1098
  this.addLine(`}`);
1126
- if (!ctx.isLast) {
1127
- this.addLine(`ctx = ctx.__proto__;`);
1128
- }
1129
1099
  this.insertBlock("l", block, ctx);
1130
1100
  return block.varName;
1131
1101
  }
@@ -1163,7 +1133,6 @@ class CodeGenerator {
1163
1133
  block,
1164
1134
  index,
1165
1135
  forceNewBlock,
1166
- isLast: ctx.isLast && i === l - 1,
1167
1136
  });
1168
1137
  this.compileAST(child, subCtx);
1169
1138
  if (forceNewBlock) {
@@ -1192,11 +1161,9 @@ class CodeGenerator {
1192
1161
  }
1193
1162
  compileTCall(ast, ctx) {
1194
1163
  let { block, forceNewBlock } = ctx;
1195
- let ctxVar = ctx.ctxVar || "ctx";
1196
- if (ast.context) {
1197
- ctxVar = generateId("ctx");
1198
- this.addLine(`let ${ctxVar} = {this: ${compileExpr(ast.context)}, __owl__: this.__owl__};`);
1199
- }
1164
+ const attrs = ast.attrs
1165
+ ? this.formatPropObject(ast.attrs, ast.attrsTranslationCtx, ctx.translationCtx)
1166
+ : [];
1200
1167
  const isDynamic = INTERP_REGEXP.test(ast.name);
1201
1168
  const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
1202
1169
  if (block && !forceNewBlock) {
@@ -1204,39 +1171,39 @@ class CodeGenerator {
1204
1171
  }
1205
1172
  block = this.createBlock(block, "multi", ctx);
1206
1173
  if (ast.body) {
1207
- this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
1208
- this.addLine(`${ctxVar}[isBoundary] = 1;`);
1209
- this.helpers.add("isBoundary");
1210
- const subCtx = createContext(ctx, { ctxVar });
1211
- const bl = this.compileMulti({ type: 3 /* ASTType.Multi */, content: ast.body }, subCtx);
1212
- if (bl) {
1213
- this.helpers.add("zero");
1214
- this.addLine(`${ctxVar}[zero] = ${bl};`);
1215
- }
1174
+ const name = this.compileInNewTarget("callBody", ast.body, ctx);
1175
+ const zeroStr = generateId("lazyBlock");
1176
+ this.define(zeroStr, `${name}.bind(this, ctx)`);
1177
+ this.helpers.add("zero");
1178
+ attrs.push(`[zero]: ${zeroStr}`);
1216
1179
  }
1217
- const key = this.generateComponentKey();
1218
- if (isDynamic) {
1219
- const templateVar = generateId("template");
1220
- if (!this.staticDefs.find((d) => d.id === "call")) {
1221
- this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
1180
+ let ctxExpr;
1181
+ const ctxString = `{${attrs.join(", ")}}`;
1182
+ if (ast.context) {
1183
+ const dynCtxVar = generateId("ctx");
1184
+ this.addLine(`const ${dynCtxVar} = ${compileExpr(ast.context)};`);
1185
+ if (ast.attrs) {
1186
+ ctxExpr = `Object.assign({}, ${dynCtxVar}${attrs.length ? ", " + ctxString : ""})`;
1187
+ }
1188
+ else {
1189
+ const thisCtx = `{this: ${dynCtxVar}, __owl__: this.__owl__}`;
1190
+ ctxExpr = `Object.assign({}, ${dynCtxVar}, ${thisCtx}${attrs.length ? ", " + ctxString : ""})`;
1222
1191
  }
1223
- this.define(templateVar, subTemplate);
1224
- this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
1225
- ...ctx,
1226
- forceNewBlock: !block,
1227
- });
1228
1192
  }
1229
1193
  else {
1230
- const id = generateId(`callTemplate_`);
1231
- this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
1232
- this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
1233
- ...ctx,
1234
- forceNewBlock: !block,
1235
- });
1236
- }
1237
- if (ast.body && !ctx.isLast) {
1238
- this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
1194
+ if (attrs.length === 0) {
1195
+ ctxExpr = "ctx";
1196
+ }
1197
+ else {
1198
+ ctxExpr = `Object.assign(Object.create(ctx), ${ctxString})`;
1199
+ }
1239
1200
  }
1201
+ const key = this.generateComponentKey();
1202
+ this.helpers.add("callTemplate");
1203
+ this.insertBlock(`callTemplate(${subTemplate}, this, app, ${ctxExpr}, node, ${key})`, block, {
1204
+ ...ctx,
1205
+ forceNewBlock: !block,
1206
+ });
1240
1207
  return block.varName;
1241
1208
  }
1242
1209
  compileTCallBlock(ast, ctx) {
@@ -1251,9 +1218,10 @@ class CodeGenerator {
1251
1218
  return block.varName;
1252
1219
  }
1253
1220
  compileTSet(ast, ctx) {
1254
- this.target.shouldProtectScope = true;
1255
- this.helpers.add("isBoundary").add("withDefault");
1256
1221
  const expr = ast.value ? compileExpr(ast.value || "") : "null";
1222
+ const isOuterScope = this.target.loopLevel === 0;
1223
+ const defLevel = this.target.tSetVars.get(ast.name);
1224
+ const isReassignment = defLevel !== undefined && this.target.loopLevel > defLevel;
1257
1225
  if (ast.body) {
1258
1226
  this.helpers.add("LazyValue");
1259
1227
  const bodyAst = { type: 3 /* ASTType.Multi */, content: ast.body };
@@ -1261,13 +1229,27 @@ class CodeGenerator {
1261
1229
  let key = this.target.currentKey(ctx);
1262
1230
  let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
1263
1231
  value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
1264
- this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
1232
+ this.helpers.add("withDefault");
1233
+ if (isReassignment) {
1234
+ const ctxVar = this.target.loopCtxVars[defLevel];
1235
+ this.addLine(`${ctxVar}[\`${ast.name}\`] = ${value};`);
1236
+ }
1237
+ else if (isOuterScope) {
1238
+ this.target.needsScopeProtection = true;
1239
+ this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
1240
+ this.target.tSetVars.set(ast.name, 0);
1241
+ }
1242
+ else {
1243
+ this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
1244
+ this.target.tSetVars.set(ast.name, this.target.loopLevel);
1245
+ }
1265
1246
  }
1266
1247
  else {
1267
1248
  let value;
1268
1249
  if (ast.defaultValue) {
1269
1250
  const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue, ctx.translationCtx) : ast.defaultValue);
1270
1251
  if (ast.value) {
1252
+ this.helpers.add("withDefault");
1271
1253
  value = `withDefault(${expr}, ${defaultValue})`;
1272
1254
  }
1273
1255
  else {
@@ -1277,8 +1259,19 @@ class CodeGenerator {
1277
1259
  else {
1278
1260
  value = expr;
1279
1261
  }
1280
- this.helpers.add("setContextValue");
1281
- this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
1262
+ if (isReassignment) {
1263
+ const ctxVar = this.target.loopCtxVars[defLevel];
1264
+ this.addLine(`${ctxVar}["${ast.name}"] = ${value};`);
1265
+ }
1266
+ else if (isOuterScope) {
1267
+ this.target.needsScopeProtection = true;
1268
+ this.addLine(`ctx["${ast.name}"] = ${value};`);
1269
+ this.target.tSetVars.set(ast.name, 0);
1270
+ }
1271
+ else {
1272
+ this.addLine(`ctx["${ast.name}"] = ${value};`);
1273
+ this.target.tSetVars.set(ast.name, this.target.loopLevel);
1274
+ }
1282
1275
  }
1283
1276
  return null;
1284
1277
  }
@@ -1306,7 +1299,7 @@ class CodeGenerator {
1306
1299
  value = toStringExpression(this.translateFn(value, attrTranslationCtx));
1307
1300
  }
1308
1301
  else {
1309
- value = this.captureExpression(value);
1302
+ value = compileExpr(value);
1310
1303
  }
1311
1304
  if (name.includes(".")) {
1312
1305
  let [_name, suffix] = name.split(".");
@@ -1345,19 +1338,13 @@ class CodeGenerator {
1345
1338
  // slots
1346
1339
  let slotDef = "";
1347
1340
  if (ast.slots) {
1348
- let ctxStr = "ctx";
1349
- if (this.target.loopLevel || !this.hasSafeContext) {
1350
- ctxStr = generateId("ctx");
1351
- this.helpers.add("capture");
1352
- this.define(ctxStr, `capture(ctx)`);
1353
- }
1354
1341
  let slotStr = [];
1355
1342
  for (let slotName in ast.slots) {
1356
1343
  const slotAst = ast.slots[slotName];
1357
1344
  const params = [];
1358
1345
  if (slotAst.content) {
1359
1346
  const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
1360
- params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);
1347
+ params.push(`__render: ${name}.bind(this), __ctx: ctx`);
1361
1348
  }
1362
1349
  const scope = ast.slots[slotName].scope;
1363
1350
  if (scope) {
@@ -1411,9 +1398,10 @@ class CodeGenerator {
1411
1398
  propList.push(`"${name}"`);
1412
1399
  }
1413
1400
  }
1401
+ this.helpers.add("createComponent");
1414
1402
  this.staticDefs.push({
1415
1403
  id,
1416
- expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, [${propList}])`,
1404
+ expr: `createComponent(app, ${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, [${propList}])`,
1417
1405
  });
1418
1406
  if (ast.isDynamic) {
1419
1407
  // If the component class changes, this can cause delayed renders to go
@@ -1515,25 +1503,18 @@ class CodeGenerator {
1515
1503
  return null;
1516
1504
  }
1517
1505
  compileTPortal(ast, ctx) {
1518
- if (!this.staticDefs.find((d) => d.id === "Portal")) {
1519
- this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
1520
- }
1506
+ this.helpers.add("Portal");
1521
1507
  let { block } = ctx;
1522
1508
  const name = this.compileInNewTarget("slot", ast.content, ctx);
1523
- let ctxStr = "ctx";
1524
- if (this.target.loopLevel || !this.hasSafeContext) {
1525
- ctxStr = generateId("ctx");
1526
- this.helpers.add("capture");
1527
- this.define(ctxStr, `capture(ctx)`);
1528
- }
1529
1509
  let id = generateId("comp");
1510
+ this.helpers.add("createComponent");
1530
1511
  this.staticDefs.push({
1531
1512
  id,
1532
- expr: `app.createComponent(null, false, true, false, false)`,
1513
+ expr: `createComponent(app, null, false, true, false, false)`,
1533
1514
  });
1534
1515
  const target = compileExpr(ast.target);
1535
1516
  const key = this.generateComponentKey();
1536
- const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, ${key}, node, ctx, Portal)`;
1517
+ const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ctx}}}, ${key}, node, ctx, Portal)`;
1537
1518
  if (block) {
1538
1519
  this.insertAnchor(block);
1539
1520
  }
@@ -1613,10 +1594,10 @@ function parseNode(node, ctx) {
1613
1594
  parseTForEach(node, ctx) ||
1614
1595
  parseTIf(node, ctx) ||
1615
1596
  parseTPortal(node, ctx) ||
1616
- parseTCall(node, ctx) ||
1617
- parseTCallBlock(node) ||
1618
1597
  parseTTranslation(node, ctx) ||
1619
1598
  parseTTranslationContext(node, ctx) ||
1599
+ parseTCall(node, ctx) ||
1600
+ parseTCallBlock(node) ||
1620
1601
  parseTKey(node, ctx) ||
1621
1602
  parseTOutNode(node, ctx) ||
1622
1603
  parseTCallSlot(node, ctx) ||
@@ -1862,28 +1843,27 @@ function parseTForEach(node, ctx) {
1862
1843
  throw new OwlError(`"Directive t-foreach should always be used with a t-key!" (expression: t-foreach="${collection}" t-as="${elem}")`);
1863
1844
  }
1864
1845
  node.removeAttribute("t-key");
1865
- const memo = node.getAttribute("t-memo") || "";
1866
- node.removeAttribute("t-memo");
1867
1846
  const body = parseNode(node, ctx);
1868
1847
  if (!body) {
1869
1848
  return null;
1870
1849
  }
1871
1850
  const hasNoTCall = !html.includes("t-call");
1872
- const hasNoFirst = hasNoTCall && !html.includes(`${elem}_first`);
1873
- const hasNoLast = hasNoTCall && !html.includes(`${elem}_last`);
1874
- const hasNoIndex = hasNoTCall && !html.includes(`${elem}_index`);
1875
- const hasNoValue = hasNoTCall && !html.includes(`${elem}_value`);
1851
+ let noFlags = 0;
1852
+ if (hasNoTCall && !html.includes(`${elem}_first`))
1853
+ noFlags |= 1 /* ForEachNoFlag.First */;
1854
+ if (hasNoTCall && !html.includes(`${elem}_last`))
1855
+ noFlags |= 2 /* ForEachNoFlag.Last */;
1856
+ if (hasNoTCall && !html.includes(`${elem}_index`))
1857
+ noFlags |= 4 /* ForEachNoFlag.Index */;
1858
+ if (hasNoTCall && !html.includes(`${elem}_value`))
1859
+ noFlags |= 8 /* ForEachNoFlag.Value */;
1876
1860
  return {
1877
1861
  type: 8 /* ASTType.TForEach */,
1878
1862
  collection,
1879
1863
  elem,
1880
1864
  body,
1881
- memo,
1882
1865
  key,
1883
- hasNoFirst,
1884
- hasNoLast,
1885
- hasNoIndex,
1886
- hasNoValue,
1866
+ noFlags,
1887
1867
  };
1888
1868
  }
1889
1869
  function parseTKey(node, ctx) {
@@ -1913,37 +1893,34 @@ function parseTCall(node, ctx) {
1913
1893
  if (!node.hasAttribute("t-call")) {
1914
1894
  return null;
1915
1895
  }
1896
+ if (node.tagName !== "t") {
1897
+ throw new OwlError(`Directive 't-call' can only be used on <t> nodes (used on a <${node.tagName}>)`);
1898
+ }
1916
1899
  const subTemplate = node.getAttribute("t-call");
1917
1900
  const context = node.getAttribute("t-call-context");
1918
1901
  node.removeAttribute("t-call");
1919
1902
  node.removeAttribute("t-call-context");
1920
- if (node.tagName !== "t") {
1921
- const ast = parseNode(node, ctx);
1922
- const tcall = { type: 6 /* ASTType.TCall */, name: subTemplate, body: null, context };
1923
- if (ast && ast.type === 2 /* ASTType.DomNode */) {
1924
- ast.content = [tcall];
1925
- return ast;
1926
- }
1927
- if (ast && ast.type === 10 /* ASTType.TComponent */) {
1928
- return {
1929
- ...ast,
1930
- slots: {
1931
- default: {
1932
- content: tcall,
1933
- scope: null,
1934
- on: null,
1935
- attrs: null,
1936
- attrsTranslationCtx: null,
1937
- },
1938
- },
1939
- };
1903
+ let attrs = null;
1904
+ let attrsTranslationCtx = null;
1905
+ for (let attributeName of node.getAttributeNames()) {
1906
+ const value = node.getAttribute(attributeName);
1907
+ if (attributeName.startsWith("t-translation-context-")) {
1908
+ const attrName = attributeName.slice(22);
1909
+ attrsTranslationCtx = attrsTranslationCtx || {};
1910
+ attrsTranslationCtx[attrName] = value;
1911
+ }
1912
+ else {
1913
+ attrs = attrs || {};
1914
+ attrs[attributeName] = value;
1940
1915
  }
1941
1916
  }
1942
- const body = parseChildren(node, ctx);
1917
+ const body = parseChildNodes(node, ctx);
1943
1918
  return {
1944
1919
  type: 6 /* ASTType.TCall */,
1945
1920
  name: subTemplate,
1946
- body: body.length ? body : null,
1921
+ attrs,
1922
+ attrsTranslationCtx,
1923
+ body,
1947
1924
  context,
1948
1925
  };
1949
1926
  }
@@ -2385,12 +2362,8 @@ function compile(template, options = {
2385
2362
  }) {
2386
2363
  // parsing
2387
2364
  const ast = parse(template, options.customDirectives);
2388
- // some work
2389
- const hasSafeContext = template instanceof Node
2390
- ? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
2391
- : !template.includes("t-set=") && !template.includes("t-call=");
2392
2365
  // code generation
2393
- const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
2366
+ const codeGenerator = new CodeGenerator(ast, options);
2394
2367
  const code = codeGenerator.generateCode();
2395
2368
  // template function
2396
2369
  try {