diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts
index 55f1d4871d..d7cd49ea0b 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 8ef1f71bc8..977943bf02 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 2fb4466ffe..86d88cadc4 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 6b0293592b..9522cf8a82 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';