From a61fe96b7078edf31c2e90c01ee74c544242d2d4 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sun, 15 Nov 2020 10:53:51 +0100 Subject: [PATCH] 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. ```
{{ fn(1) }}
``` 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 --- .../ngtsc/typecheck/src/type_check_block.ts | 2 +- .../typecheck/test/span_comments_spec.ts | 2 +- .../typecheck/test/type_check_block_spec.ts | 8 ++++++- .../test/ngtsc/template_typecheck_spec.ts | 24 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index 0d417fae78..0410b72bc5 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -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); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts index caa312ce0a..6d8236e23e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/span_comments_spec.ts @@ -71,7 +71,7 @@ describe('type check blocks diagnostics', () => { const TEMPLATE = `{{ method(a, b) }}`; 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', () => { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts index f81ac38469..82fc1d0583 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts @@ -106,7 +106,7 @@ describe('type check blocks', () => { it('should handle method calls of template variables', () => { const TEMPLATE = `{{a(1)}}`; 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 = `
{{a(1)}}
`; + 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 = `
`; diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index 4e20f958ec..80d0f20199 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -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: '
{{fn()}}
', + }) + 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', `