diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 55f1d4871d99b..d7cd49ea0bc52 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -425,26 +425,29 @@ export class NgtscProgram implements api.Program { // requested. let typeCheckingConfig: TypeCheckingConfig; if (this.options.fullTemplateTypeCheck) { + const strictTemplates = !!this.options.strictTemplates; typeCheckingConfig = { applyTemplateContextGuards: true, checkQueries: false, checkTemplateBodies: true, - checkTypeOfInputBindings: true, - strictNullInputBindings: true, - checkTypeOfAttributes: true, + checkTypeOfInputBindings: strictTemplates, + strictNullInputBindings: strictTemplates, + checkTypeOfAttributes: strictTemplates, // Even in full template type-checking mode, DOM binding checks are not quite ready yet. checkTypeOfDomBindings: false, - checkTypeOfOutputEvents: true, - checkTypeOfAnimationEvents: true, + checkTypeOfOutputEvents: strictTemplates, + checkTypeOfAnimationEvents: strictTemplates, // Checking of DOM events currently has an adverse effect on developer experience, // e.g. for `` enabling this check results in: // - error TS2531: Object is possibly 'null'. // - error TS2339: Property 'value' does not exist on type 'EventTarget'. - checkTypeOfDomEvents: false, - checkTypeOfDomReferences: true, + checkTypeOfDomEvents: strictTemplates, + checkTypeOfDomReferences: strictTemplates, + // Non-DOM references have the correct type in View Engine so there is no strictness flag. checkTypeOfNonDomReferences: true, + // Pipes are checked in View Engine so there is no strictness flag. checkTypeOfPipes: true, - strictSafeNavigationTypes: true, + strictSafeNavigationTypes: strictTemplates, }; } else { typeCheckingConfig = { @@ -465,6 +468,31 @@ export class NgtscProgram implements api.Program { }; } + // Apply explicitly configured strictness flags on top of the default configuration + // based on "fullTemplateTypeCheck". + if (this.options.strictInputTypes !== undefined) { + typeCheckingConfig.checkTypeOfInputBindings = this.options.strictInputTypes; + } + if (this.options.strictNullInputTypes !== undefined) { + typeCheckingConfig.strictNullInputBindings = this.options.strictNullInputTypes; + } + if (this.options.strictOutputEventTypes !== undefined) { + typeCheckingConfig.checkTypeOfOutputEvents = this.options.strictOutputEventTypes; + typeCheckingConfig.checkTypeOfAnimationEvents = this.options.strictOutputEventTypes; + } + if (this.options.strictDomEventTypes !== undefined) { + typeCheckingConfig.checkTypeOfDomEvents = this.options.strictDomEventTypes; + } + if (this.options.strictSafeNavigationTypes !== undefined) { + typeCheckingConfig.strictSafeNavigationTypes = this.options.strictSafeNavigationTypes; + } + if (this.options.strictDomLocalRefTypes !== undefined) { + typeCheckingConfig.checkTypeOfDomReferences = this.options.strictDomLocalRefTypes; + } + if (this.options.strictAttributeTypes !== undefined) { + typeCheckingConfig.checkTypeOfAttributes = this.options.strictAttributeTypes; + } + // Execute the typeCheck phase of each decorator in the program. const prepSpan = this.perfRecorder.start('typeCheckPrep'); const ctx = new TypeCheckContext(typeCheckingConfig, this.refEmitter !, this.typeCheckFilePath); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts index 8ef1f71bc8c40..977943bf02da2 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts @@ -107,10 +107,10 @@ export interface TypeCheckingConfig { * Whether to check text attributes that happen to be consumed by a directive or component. * * For example, in a template containing `` the `disabled` attribute ends - * up being consumed as an input with type `boolean` by the `matInput` directive. At runtime the - * input will be set to the attribute's string value, which is the empty string for attributes - * without a value, so with this flag set to `true` an error would be reported. If set to `false`, - * text attributes will never report an error. + * up being consumed as an input with type `boolean` by the `matInput` directive. At runtime, the + * input will be set to the attribute's string value, which is an empty string for attributes + * without a value, so with this flag set to `true`, an error would be reported. If set to + * `false`, text attributes will never report an error. * * Note that if `checkTypeOfInputBindings` is set to `false`, this flag has no effect. */ @@ -129,7 +129,8 @@ export interface TypeCheckingConfig { checkTypeOfDomBindings: boolean; /** - * Whether to infer the type of the `$event` variable in event bindings for directive outputs. + * Whether to infer the type of the `$event` variable in event bindings for directive outputs or + * animation events. * * If this is `true`, the type of `$event` will be inferred based on the generic type of * `EventEmitter`/`Subject` of the output. If set to `false`, the `$event` variable will be of diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index 2fb4466ffeb26..86d88cadc4a30 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -108,10 +108,112 @@ export interface CompilerOptions extends ts.CompilerOptions { // Default is true. generateCodeForLibraries?: boolean; - // Whether to enable all type checks for templates. - // This will be true be default in Angular 6. + /** + * Whether to type check the entire template. + * + * This flag currently controls a couple aspects of template type-checking, including + * whether embedded views are checked. + * + * For maximum type-checking, set this to `true`, and set `strictTemplates` to `true`. + */ fullTemplateTypeCheck?: boolean; + /** + * If `true`, implies all template strictness flags below (unless individually disabled). + * + * Has no effect unless `fullTemplateTypeCheck` is also enabled. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. + */ + strictTemplates?: boolean; + + + /** + * Whether to check the type of a binding to a directive/component input against the type of the + * field on the directive/component. + * + * For example, if this is `false` then the expression `[input]="expr"` will have `expr` type- + * checked, but not the assignment of the resulting type to the `input` property of whichever + * directive or component is receiving the binding. If set to `true`, both sides of the assignment + * are checked. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. + */ + strictInputTypes?: boolean; + + /** + * Whether to use strict null types for input bindings for directives. + * + * If this is `true`, applications that are compiled with TypeScript's `strictNullChecks` enabled + * will produce type errors for bindings which can evaluate to `undefined` or `null` where the + * inputs's type does not include `undefined` or `null` in its type. If set to `false`, all + * binding expressions are wrapped in a non-null assertion operator to effectively disable strict + * null checks. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. Note that if `strictInputTypes` is + * not set, or set to `false`, this flag has no effect. + */ + strictNullInputTypes?: boolean; + + /** + * Whether to check text attributes that happen to be consumed by a directive or component. + * + * For example, in a template containing `` the `disabled` attribute ends + * up being consumed as an input with type `boolean` by the `matInput` directive. At runtime, the + * input will be set to the attribute's string value, which is an empty string for attributes + * without a value, so with this flag set to `true`, an error would be reported. If set to + * `false`, text attributes will never report an error. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. Note that if `strictInputTypes` is + * not set, or set to `false`, this flag has no effect. + */ + strictAttributeTypes?: boolean; + + /** + * Whether to use a strict type for null-safe navigation operations. + * + * If this is `false`, then the return type of `a?.b` or `a?()` will be `any`. If set to `true`, + * then the return type of `a?.b` for example will be the same as the type of the ternary + * expression `a != null ? a.b : a`. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. + */ + strictSafeNavigationTypes?: boolean; + + /** + * Whether to infer the type of local references. + * + * If this is `true`, the type of a `#ref` variable on a DOM node in the template will be + * determined by the type of `document.createElement` for the given DOM node. If set to `false`, + * the type of `ref` for DOM nodes will be `any`. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. + */ + strictDomLocalRefTypes?: boolean; + + /** + * Whether to infer the type of the `$event` variable in event bindings for directive outputs or + * animation events. + * + * If this is `true`, the type of `$event` will be inferred based on the generic type of + * `EventEmitter`/`Subject` of the output. If set to `false`, the `$event` variable will be of + * type `any`. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. + */ + strictOutputEventTypes?: boolean; + + /** + * Whether to infer the type of the `$event` variable in event bindings to DOM events. + * + * If this is `true`, the type of `$event` will be inferred based on TypeScript's + * `HTMLElementEventMap`, with a fallback to the native `Event` type. If set to `false`, the + * `$event` variable will be of type `any`. + * + * Defaults to `false`, even if "fullTemplateTypeCheck" is set. + */ + strictDomEventTypes?: boolean; + // Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate // import module specifiers. This is false by default, and exists to support running ngtsc // within Google. This option is internal and is used by the ng_module.bzl rule to switch diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index 6b0293592b3ab..9522cf8a82e2b 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -58,6 +58,11 @@ export declare class NgIf { export declare class CommonModule { static ɵmod: i0.ɵɵNgModuleDefWithMeta; } +`); + env.write('node_modules/@angular/animations/index.d.ts', ` +export declare class AnimationEvent { + element: any; +} `); }); @@ -81,6 +86,8 @@ export declare class CommonModule { }); it('should check regular attributes that are directive inputs', () => { + env.tsconfig( + {fullTemplateTypeCheck: true, strictInputTypes: true, strictAttributeTypes: true}); env.write('test.ts', ` import {Component, Directive, NgModule, Input} from '@angular/core'; @@ -107,6 +114,7 @@ export declare class CommonModule { }); it('should check event bindings', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictOutputEventTypes: true}); env.write('test.ts', ` import {Component, Directive, EventEmitter, NgModule, Output} from '@angular/core'; @@ -142,6 +150,449 @@ export declare class CommonModule { expect(diags[2].messageText).toEqual(`Property 'focused' does not exist on type 'TestCmp'.`); }); + describe('strictInputTypes', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, Directive, NgModule, Input} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
', + }) + class TestCmp {} + + @Directive({selector: '[dir]'}) + class TestDir { + @Input() foo: string; + } + + @NgModule({ + declarations: [TestCmp, TestDir], + }) + class Module {} + `); + }); + + it('should check expressions and their type when enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText).toEqual(`Type 'number' is not assignable to type 'string'.`); + expect(diags[1].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + + it('should check expressions and their type when overall strictness is enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText).toEqual(`Type 'number' is not assignable to type 'string'.`); + expect(diags[1].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + + it('should check expressions but not their type when not enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + }); + + describe('strictNullInputTypes', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, Directive, NgModule, Input} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
', + }) + class TestCmp { + nullable: string | null | undefined; + } + + @Directive({selector: '[dir]'}) + class TestDir { + @Input() foo: string; + } + + @NgModule({ + declarations: [TestCmp, TestDir], + }) + class Module {} + `); + }); + + it('should check expressions and their nullability when enabled', () => { + env.tsconfig( + {fullTemplateTypeCheck: true, strictInputTypes: true, strictNullInputTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect((diags[0].messageText as ts.DiagnosticMessageChain).messageText) + .toEqual(`Type 'string | null | undefined' is not assignable to type 'string'.`); + expect(diags[1].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + + it('should check expressions and their nullability when overall strictness is enabled', + () => { + env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect((diags[0].messageText as ts.DiagnosticMessageChain).messageText) + .toEqual(`Type 'string | null | undefined' is not assignable to type 'string'.`); + expect(diags[1].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + + it('should check expressions but not their nullability when not enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + }); + + describe('strictSafeNavigationTypes', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, Directive, NgModule, Input} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
', + }) + class TestCmp { + user?: {name: string}; + } + + @Directive({selector: '[dir]'}) + class TestDir { + @Input() foo: string; + } + + @NgModule({ + declarations: [TestCmp, TestDir], + }) + class Module {} + `); + }); + + it('should infer result type for safe navigation expressions when enabled', () => { + env.tsconfig({ + fullTemplateTypeCheck: true, + strictInputTypes: true, + strictNullInputTypes: true, + strictSafeNavigationTypes: true + }); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect((diags[0].messageText as ts.DiagnosticMessageChain).messageText) + .toEqual(`Type 'string | undefined' is not assignable to type 'string'.`); + expect(diags[1].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + + it('should infer result type for safe navigation expressions when overall strictness is enabled', + () => { + env.tsconfig({ + fullTemplateTypeCheck: true, + strictTemplates: true, + }); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect((diags[0].messageText as ts.DiagnosticMessageChain).messageText) + .toEqual(`Type 'string | undefined' is not assignable to type 'string'.`); + expect(diags[1].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + + it('should not infer result type for safe navigation expressions when not enabled', () => { + env.tsconfig({ + fullTemplateTypeCheck: true, + strictInputTypes: true, + }); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + }); + + describe('strictOutputEventTypes', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, Directive, EventEmitter, NgModule, Output} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
', + }) + class TestCmp { + update(data: string) {} + } + + @Directive({selector: '[dir]'}) + class TestDir { + @Output() update = new EventEmitter(); + } + + @NgModule({ + declarations: [TestCmp, TestDir], + }) + class Module {} + `); + }); + + it('should expressions and infer type of $event when enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictOutputEventTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + expect(diags[1].messageText) + .toEqual(`Argument of type 'number' is not assignable to parameter of type 'string'.`); + }); + + it('should expressions and infer type of $event when overall strictness is enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + expect(diags[1].messageText) + .toEqual(`Argument of type 'number' is not assignable to parameter of type 'string'.`); + }); + + it('should check expressions but not infer type of $event when not enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + }); + + describe('strictOutputEventTypes and animation event bindings', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
', + }) + class TestCmp { + update(data: string) {} + } + + @NgModule({ + declarations: [TestCmp], + }) + class Module {} + `); + }); + + it('should check expressions and let $event be of type AnimationEvent when enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictOutputEventTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + expect(diags[1].messageText) + .toEqual( + `Argument of type 'AnimationEvent' is not assignable to parameter of type 'string'.`); + }); + + it('should check expressions and let $event be of type AnimationEvent when overall strictness is enabled', + () => { + env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + expect(diags[1].messageText) + .toEqual( + `Argument of type 'AnimationEvent' is not assignable to parameter of type 'string'.`); + }); + + it('should check expressions and let $event be of type any when not enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + }); + + describe('strictDomLocalRefTypes', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '{{ref.does_not_exist}}', + }) + class TestCmp {} + + @NgModule({ + declarations: [TestCmp], + }) + class Module {} + `); + }); + + it('should infer the type of DOM references when enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictDomLocalRefTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'does_not_exist' does not exist on type 'HTMLInputElement'.`); + }); + + it('should infer the type of DOM references when overall strictness is enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'does_not_exist' does not exist on type 'HTMLInputElement'.`); + }); + + it('should let the type of DOM references be any when not enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + }); + + describe('strictAttributeTypes', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, Directive, NgModule, Input} from '@angular/core'; + + @Component({ + selector: 'test', + template: '', + }) + class TestCmp {} + + @Directive({selector: '[dir]'}) + class TestDir { + @Input() disabled: boolean; + @Input() cols: number; + } + + @NgModule({ + declarations: [TestCmp, TestDir], + }) + class Module {} + `); + }); + + it('should produce an error for text attributes when enabled', () => { + env.tsconfig( + {fullTemplateTypeCheck: true, strictInputTypes: true, strictAttributeTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText).toEqual(`Type 'string' is not assignable to type 'boolean'.`); + expect(diags[1].messageText).toEqual(`Type 'string' is not assignable to type 'number'.`); + }); + + it('should produce an error for text attributes when overall strictness is enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText).toEqual(`Type 'string' is not assignable to type 'boolean'.`); + expect(diags[1].messageText).toEqual(`Type 'string' is not assignable to type 'number'.`); + }); + + it('should not produce an error for text attributes when not enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + }); + + describe('strictDomEventTypes', () => { + beforeEach(() => { + env.write('test.ts', ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
', + }) + class TestCmp { + update(data: string) {} + } + + @NgModule({ + declarations: [TestCmp], + }) + class Module {} + `); + }); + + it('should check expressions and infer type of $event when enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictDomEventTypes: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + expect(diags[1].messageText) + .toEqual( + `Argument of type 'FocusEvent' is not assignable to parameter of type 'string'.`); + }); + + it('should check expressions and infer type of $event when overall strictness is enabled', + () => { + env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + expect(diags[1].messageText) + .toEqual( + `Argument of type 'FocusEvent' is not assignable to parameter of type 'string'.`); + }); + + it('should check expressions but not infer type of $event when not enabled', () => { + env.tsconfig({fullTemplateTypeCheck: true}); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); + }); + }); + it('should check basic usage of NgIf', () => { env.write('test.ts', ` import {CommonModule} from '@angular/common'; @@ -212,6 +663,7 @@ export declare class CommonModule { }); it('should report an error inside the NgFor template', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); env.write('test.ts', ` import {CommonModule} from '@angular/common'; import {Component, NgModule} from '@angular/core'; @@ -315,6 +767,7 @@ export declare class CommonModule { }); it('should constrain types using type parameter bounds', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); env.write('test.ts', ` import {CommonModule} from '@angular/common'; import {Component, Input, NgModule} from '@angular/core'; @@ -367,6 +820,7 @@ export declare class CommonModule { }); it('should properly type-check inherited directives', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); env.write('test.ts', ` import {Component, Directive, Input, NgModule} from '@angular/core'; @@ -412,6 +866,8 @@ export declare class CommonModule { }); it('should properly type-check inherited directives from external libraries', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.write('node_modules/external/index.d.ts', ` import * as i0 from '@angular/core'; @@ -511,6 +967,8 @@ export declare class CommonModule { it('should give an error if the binding expression type is not accepted by the coercion function', () => { + env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.write('test.ts', ` import {Component, NgModule} from '@angular/core'; import {MatInputModule} from '@angular/material';