fix(compiler-cli): incorrectly type checking calls to implicit template variables (#39686)

Currently when we encounter an implicit method call (e.g. `{{ foo(1) }}`) and we manage to resolve
its receiver to something within the template, we assume that the method is on the receiver itself
so we generate a type checking code to reflect it. This assumption is true in most cases, but it
breaks down if the call is on an implicit receiver and the receiver itself is being invoked. E.g.

```
<div *ngFor="let fn of functions">{{ fn(1) }}</div>
```

These changes resolve the issue by generating a regular function call if the method call's receiver
is pointing to `$implicit`.

Fixes #39634.

PR Close #39686
This commit is contained in:
Kristiyan Kostadinov 2020-11-15 10:53:51 +01:00 committed by atscott
parent 7e724add7e
commit a61fe96b70
4 changed files with 33 additions and 3 deletions

View File

@ -1622,7 +1622,7 @@ class TcbExpressionTranslator {
return null;
}
const method = ts.createPropertyAccess(wrapForDiagnostics(receiver), ast.name);
const method = wrapForDiagnostics(receiver);
addParseSpanInfo(method, ast.nameSpan);
const args = ast.args.map(arg => this.translate(arg));
const node = ts.createCall(method, undefined, args);

View File

@ -71,7 +71,7 @@ describe('type check blocks diagnostics', () => {
const TEMPLATE = `<ng-template let-method>{{ method(a, b) }}</ng-template>`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'(_t2 /*27,39*/).method /*27,33*/(((ctx).a /*34,35*/) /*34,35*/, ((ctx).b /*37,38*/) /*37,38*/) /*27,39*/');
'(_t2 /*27,39*/) /*27,33*/(((ctx).a /*34,35*/) /*34,35*/, ((ctx).b /*37,38*/) /*37,38*/) /*27,39*/');
});
it('should annotate function calls', () => {

View File

@ -106,7 +106,7 @@ describe('type check blocks', () => {
it('should handle method calls of template variables', () => {
const TEMPLATE = `<ng-template let-a>{{a(1)}}</ng-template>`;
expect(tcb(TEMPLATE)).toContain('var _t2 = _t1.$implicit;');
expect(tcb(TEMPLATE)).toContain('(_t2).a(1)');
expect(tcb(TEMPLATE)).toContain('(_t2)(1)');
});
it('should handle implicit vars when using microsyntax', () => {
@ -114,6 +114,12 @@ describe('type check blocks', () => {
expect(tcb(TEMPLATE)).toContain('var _t2 = _t1.$implicit;');
});
it('should handle direct calls of an implicit template variable', () => {
const TEMPLATE = `<div *ngFor="let a of letters">{{a(1)}}</div>`;
expect(tcb(TEMPLATE)).toContain('var _t2 = _t1.$implicit;');
expect(tcb(TEMPLATE)).toContain('(_t2)(1)');
});
describe('type constructors', () => {
it('should handle missing property bindings', () => {
const TEMPLATE = `<div dir [inputA]="foo"></div>`;

View File

@ -1009,6 +1009,30 @@ export declare class AnimationEvent {
expect(diags.length).toBe(0);
});
it('should allow the implicit value of an NgFor to be invoked', () => {
env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true});
env.write('test.ts', `
import {CommonModule} from '@angular/common';
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'test',
template: '<div *ngFor="let fn of functions">{{fn()}}</div>',
})
class TestCmp {
functions = [() => 1, () => 2];
}
@NgModule({
declarations: [TestCmp],
imports: [CommonModule],
})
class Module {}
`);
env.driveMain();
});
it('should infer the context of NgIf', () => {
env.tsconfig({strictTemplates: true});
env.write('test.ts', `