diff --git a/packages/compiler-cli/test/diagnostics/check_types_spec.ts b/packages/compiler-cli/test/diagnostics/check_types_spec.ts index 18401a18a7..a8a3244bae 100644 --- a/packages/compiler-cli/test/diagnostics/check_types_spec.ts +++ b/packages/compiler-cli/test/diagnostics/check_types_spec.ts @@ -14,13 +14,16 @@ import * as ts from 'typescript'; import {TestSupport, expectNoDiagnostics, setup} from '../test_support'; +type MockFiles = { + [fileName: string]: string +}; + describe('ng type checker', () => { let errorSpy: jasmine.Spy&((s: string) => void); let testSupport: TestSupport; function compileAndCheck( - mockDirs: {[fileName: string]: string}[], - overrideOptions: ng.CompilerOptions = {}): ng.Diagnostics { + mockDirs: MockFiles[], overrideOptions: ng.CompilerOptions = {}): ng.Diagnostics { testSupport.writeFiles(...mockDirs); const fileNames: string[] = []; mockDirs.forEach((dir) => { @@ -40,13 +43,12 @@ describe('ng type checker', () => { testSupport = setup(); }); - function accept( - files: {[fileName: string]: string} = {}, overrideOptions: ng.CompilerOptions = {}) { + function accept(files: MockFiles = {}, overrideOptions: ng.CompilerOptions = {}) { expectNoDiagnostics({}, compileAndCheck([QUICKSTART, files], overrideOptions)); } function reject( - message: string | RegExp, location: RegExp, files: {[fileName: string]: string}, + message: string | RegExp, location: RegExp, files: MockFiles, overrideOptions: ng.CompilerOptions = {}) { const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions); if (!diagnostics || !diagnostics.length) { @@ -79,6 +81,41 @@ describe('ng type checker', () => { }); }); + describe('regressions ', () => { + const a = (files: MockFiles, options: object = {}) => { + accept(files, {fullTemplateTypeCheck: true, ...options}); + }; + + // #19905 + it('should accept an event binding', () => { + a({ + 'src/app.component.ts': '', + 'src/lib.ts': '', + 'src/app.module.ts': ` + import {NgModule, Component, Directive, HostListener} from '@angular/core'; + + @Component({ + selector: 'comp', + template: '
' + }) + export class MainComp {} + + @Directive({ + selector: '[someDir]' + }) + export class SomeDirective { + @HostListener('click', ['$event']) + onClick(event: any) {} + } + + @NgModule({ + declarations: [MainComp, SomeDirective], + }) + export class MainModule {}` + }); + }); + }); + describe('with modified quickstart (fullTemplateTypeCheck: false)', () => { addTests({fullTemplateTypeCheck: false}); }); diff --git a/packages/compiler/src/view_compiler/type_check_compiler.ts b/packages/compiler/src/view_compiler/type_check_compiler.ts index 3b4597979e..3e1677f280 100644 --- a/packages/compiler/src/view_compiler/type_check_compiler.ts +++ b/packages/compiler/src/view_compiler/type_check_compiler.ts @@ -69,6 +69,19 @@ interface Expression { const DYNAMIC_VAR_NAME = '_any'; +class TypeCheckLocalResolver implements LocalResolver { + getLocal(name: string): o.Expression|null { + if (name === EventHandlerVars.event.name) { + // References to the event should not be type-checked. + // TODO(chuckj): determine a better type for the event. + return o.variable(DYNAMIC_VAR_NAME); + } + return null; + } +} + +const defaultResolver = new TypeCheckLocalResolver(); + class ViewBuilder implements TemplateAstVisitor, LocalResolver { private refOutputVars = new Map(); private variables: VariableAst[] = []; @@ -112,7 +125,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver { this.updates.forEach((expression) => { const {sourceSpan, context, value} = this.preprocessUpdateExpression(expression); const bindingId = `${bindingCount++}`; - const nameResolver = context === this.component ? this : null; + const nameResolver = context === this.component ? this : defaultResolver; const {stmts, currValExpr} = convertPropertyBinding( nameResolver, o.variable(this.getOutputVar(context)), value, bindingId); stmts.push(new o.ExpressionStatement(currValExpr)); @@ -122,7 +135,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver { this.actions.forEach(({sourceSpan, context, value}) => { const bindingId = `${bindingCount++}`; - const nameResolver = context === this.component ? this : null; + const nameResolver = context === this.component ? this : defaultResolver; const {stmts} = convertActionBinding( nameResolver, o.variable(this.getOutputVar(context)), value, bindingId); viewStmts.push(...stmts.map(