Skip to content

Commit

Permalink
fix(compiler-cli): incorrectly type checking calls to implicit templa…
Browse files Browse the repository at this point in the history
…te 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
  • Loading branch information
crisbeto authored and atscott committed Nov 16, 2020
1 parent 7e724ad commit a61fe96
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 3 deletions.
Expand Up @@ -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);
Expand Down
Expand Up @@ -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', () => {
Expand Down
Expand Up @@ -106,14 +106,20 @@ 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', () => {
const TEMPLATE = `<div *ngFor="let user of users"></div>`;
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>`;
Expand Down
24 changes: 24 additions & 0 deletions packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
Expand Up @@ -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', `
Expand Down

0 comments on commit a61fe96

Please sign in to comment.