fix(ivy): template inputs/outputs should not be bound in template scope (#30669)

The R3TargetBinder "binds" an Angular template AST, computing semantic
information regarding the template and making it accessible.

One of the binding passes previously had a bug, where for the following
template:

<div *ngIf="foo as foo"></div>

which desugars to:

<ng-template ngIf [ngIf]="foo" let-foo="ngIf">
  <div></div>
</ng-template>

would have the `[ngIf]` binding processed twice - in both the scope which
contains the `<ng-template>` and the scope inside the template. The bug
arises because during the latter, `foo` is a variable defined by `let-foo`,
and so the R3TargetBinder would incorrectly learn that `foo` inside `[ngIf]`
maps to that variable.

This commit fixes the bug by only processing inputs, outputs, and
templateAttrs from `Template`s in the outer scope.

PR Close #30669
This commit is contained in:
Alex Rickabaugh 2019-05-24 14:37:52 -07:00 committed by Matias Niemelä
parent b4644d7bb0
commit b61784948a
2 changed files with 27 additions and 6 deletions

View File

@ -270,6 +270,31 @@ describe('ngtsc type checking', () => {
expect(diags[0].messageText).toContain('does_not_exist'); expect(diags[0].messageText).toContain('does_not_exist');
}); });
it('should property type-check a microsyntax variable with the same name as the expression',
() => {
env.write('test.ts', `
import {CommonModule} from '@angular/common';
import {Component, Input, NgModule} from '@angular/core';
@Component({
selector: 'test',
template: '<div *ngIf="foo as foo">{{foo}}</div>',
})
export class TestCmp<T extends {name: string}> {
foo: any;
}
@NgModule({
declarations: [TestCmp],
imports: [CommonModule],
})
export class Module {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(0);
});
it('should properly type-check inherited directives', () => { it('should properly type-check inherited directives', () => {
env.write('test.ts', ` env.write('test.ts', `
import {Component, Directive, Input, NgModule} from '@angular/core'; import {Component, Directive, Input, NgModule} from '@angular/core';

View File

@ -372,12 +372,8 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor {
private ingest(template: Template|Node[]): void { private ingest(template: Template|Node[]): void {
if (template instanceof Template) { if (template instanceof Template) {
// For <ng-template>s, process inputs, outputs, template attributes, // For <ng-template>s, process only variables and child nodes. Inputs, outputs, templateAttrs,
// variables, and child nodes. // and references were all processed in the scope of the containing template.
// References were processed in the scope of the containing template.
template.inputs.forEach(this.visitNode);
template.outputs.forEach(this.visitNode);
template.templateAttrs.forEach(this.visitNode);
template.variables.forEach(this.visitNode); template.variables.forEach(this.visitNode);
template.children.forEach(this.visitNode); template.children.forEach(this.visitNode);