refactor(compiler): computed for loop variables exposed on the wrong scope (#51618)

Fixes that the computed for loop variables (e.g. $first and $last) were exposed on the parent scope instead of the for loop scope.

PR Close #51618
This commit is contained in:
Kristiyan Kostadinov
2023-09-01 12:54:01 +02:00
committed by Jessica Janiuk
parent 75ab0bdf45
commit 6ecafa3305
5 changed files with 141 additions and 19 deletions
+22 -19
View File
@@ -158,7 +158,8 @@ function createComponentDefConsts(): ComponentDefConsts {
class TemplateData {
constructor(
readonly name: string, readonly index: number, private visitor: TemplateDefinitionBuilder) {}
readonly name: string, readonly index: number, readonly scope: BindingScope,
private visitor: TemplateDefinitionBuilder) {}
getConstCount() {
return this.visitor.getConstCount();
@@ -958,7 +959,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
});
return new TemplateData(name, index, visitor);
return new TemplateData(name, index, visitor._bindingScope, visitor);
}
private createEmbeddedTemplateFn(
@@ -1428,8 +1429,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Allocate one slot for the repeater metadata. The slots for the primary and empty block
// are implicitly inferred by the runtime to index + 1 and index + 2.
const blockIndex = this.allocateDataSlot();
const templateVariables = this.createForLoopVariables(block);
const primaryData = this.prepareEmbeddedTemplateFn(block.children, '_For', templateVariables);
const primaryData = this.prepareEmbeddedTemplateFn(block.children, '_For', [
new t.Variable(block.itemName, '$implicit', block.sourceSpan, block.sourceSpan),
new t.Variable(
getLoopLocalName(block, '$index'), '$index', block.sourceSpan, block.sourceSpan),
new t.Variable(
getLoopLocalName(block, '$count'), '$count', block.sourceSpan, block.sourceSpan),
]);
const emptyData = block.empty === null ?
null :
this.prepareEmbeddedTemplateFn(block.empty.children, '_ForEmpty');
@@ -1437,6 +1443,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const value = block.expression.visit(this._valueConverter);
this.allocateBindingSlots(value);
this.registerComputedLoopVariables(block, primaryData.scope);
// `repeaterCreate(0, ...)`
this.creationInstruction(block.sourceSpan, R3.repeaterCreate, () => {
const params = [
@@ -1462,32 +1470,27 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
() => [o.literal(blockIndex), this.convertPropertyBinding(value)]);
}
private createForLoopVariables(block: t.ForLoopBlock): t.Variable[] {
private registerComputedLoopVariables(block: t.ForLoopBlock, bindingScope: BindingScope): void {
const indexLocalName = getLoopLocalName(block, '$index');
const countLocalName = getLoopLocalName(block, '$count');
const level = bindingScope.bindingLevel;
this._bindingScope.set(
this.level, getLoopLocalName(block, '$odd'),
bindingScope.set(
level, getLoopLocalName(block, '$odd'),
scope => scope.get(indexLocalName)!.modulo(o.literal(2)).notIdentical(o.literal(0)));
this._bindingScope.set(
this.level, getLoopLocalName(block, '$even'),
bindingScope.set(
level, getLoopLocalName(block, '$even'),
scope => scope.get(indexLocalName)!.modulo(o.literal(2)).identical(o.literal(0)));
this._bindingScope.set(
this.level, getLoopLocalName(block, '$first'),
bindingScope.set(
level, getLoopLocalName(block, '$first'),
scope => scope.get(indexLocalName)!.identical(o.literal(0)));
this._bindingScope.set(
this.level, getLoopLocalName(block, '$last'),
bindingScope.set(
level, getLoopLocalName(block, '$last'),
scope =>
scope.get(indexLocalName)!.identical(scope.get(countLocalName)!.minus(o.literal(1))));
return [
new t.Variable(block.itemName, '$implicit', block.sourceSpan, block.sourceSpan),
new t.Variable(indexLocalName, '$index', block.sourceSpan, block.sourceSpan),
new t.Variable(countLocalName, '$count', block.sourceSpan, block.sourceSpan),
];
}
private createTrackByFunction(block: t.ForLoopBlock): o.Expression {