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 11adcedde58..5c6c83be97d 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 @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AST, BindingPipe, BindingType, BoundTarget, Call, DYNAMIC_TYPE, ImplicitReceiver, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, SafeCall, SafePropertyRead, SchemaMetadata, ThisReceiver, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstDeferredBlock, TmplAstElement, TmplAstIcu, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable, TransplantedType} from '@angular/compiler'; +import {AST, BindingPipe, BindingType, BoundTarget, Call, DYNAMIC_TYPE, ImplicitReceiver, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, SafeCall, SafePropertyRead, SchemaMetadata, ThisReceiver, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstDeferredBlock, TmplAstElement, TmplAstForLoopBlock, TmplAstIcu, TmplAstIfBlock, TmplAstNode, TmplAstReference, TmplAstSwitchBlock, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable, TransplantedType} from '@angular/compiler'; import ts from 'typescript'; import {Reference} from '../../imports'; @@ -1510,6 +1510,20 @@ class Scope { node.placeholder !== null && this.appendChildren(node.placeholder); node.loading !== null && this.appendChildren(node.loading); node.error !== null && this.appendChildren(node.error); + } else if (node instanceof TmplAstIfBlock) { + // TODO(crisbeto): type check the branch expression. + for (const branch of node.branches) { + this.appendChildren(branch); + } + } else if (node instanceof TmplAstSwitchBlock) { + // TODO(crisbeto): type check switch condition + for (const currentCase of node.cases) { + this.appendChildren(currentCase); + } + } else if (node instanceof TmplAstForLoopBlock) { + // TODO(crisbeto): type check loop expression, context variables and trackBy + this.appendChildren(node); + node.empty && this.appendChildren(node.empty); } else if (node instanceof TmplAstBoundText) { this.opQueue.push(new TcbTextInterpolationOp(this.tcb, this, node)); } else if (node instanceof TmplAstIcu) { 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 1ec36f21cd9..6df995b21dc 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 @@ -1379,4 +1379,61 @@ describe('type check blocks', () => { '"" + ((this).main()); "" + ((this).placeholder()); "" + ((this).loading()); "" + ((this).error());'); }); }); + + // TODO(crisbeto): tests for the bindings of conditionals and context variables. + describe('conditional blocks', () => { + // TODO(crisbeto): temporary utility while conditional blocks are disabled by default + function conditionalTcb(template: string): string { + return tcb( + template, undefined, undefined, undefined, + {enabledBlockTypes: new Set(['if', 'switch'])}); + } + + it('should generate bindings inside if block', () => { + const TEMPLATE = ` + {#if expr} + {{main()}} + {:else if expr1}{{one()}} + {:else if expr2}{{two()}} + {:else}{{other()}} + {/if} + `; + + expect(conditionalTcb(TEMPLATE)) + .toContain( + '"" + ((this).main()); "" + ((this).one()); "" + ((this).two()); "" + ((this).other());'); + }); + + it('should generate bindings inside switch block', () => { + const TEMPLATE = ` + {#switch expr} + {:case 1}{{one()}} + {:case 2}{{two()}} + {:default}{{default()}} + {/switch} + `; + + expect(conditionalTcb(TEMPLATE)) + .toContain('"" + ((this).one()); "" + ((this).two()); "" + ((this).default());'); + }); + }); + + // TODO(crisbeto): tests for the for loop expression and context variables + describe('for loop blocks', () => { + // TODO(crisbeto): temporary utility while for loop blocks are disabled by default + function loopTcb(template: string): string { + return tcb(template, undefined, undefined, undefined, {enabledBlockTypes: new Set(['for'])}); + } + + it('should generate bindings inside for loop blocks', () => { + const TEMPLATE = ` + {#for item of items; track item} + {{main()}} + {:empty}{{empty()}} + {/for} + `; + + expect(loopTcb(TEMPLATE)).toContain('"" + ((this).main()); "" + ((this).empty());'); + }); + }); }); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js index b8522b17a82..5c11dff9797 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js @@ -530,6 +530,7 @@ import * as i0 from "@angular/core"; export declare class MyApp { message: string; value: () => number; + alias: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -579,6 +580,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE import * as i0 from "@angular/core"; export declare class MyApp { value: () => number; + root: any; + inner: any; + innermost: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -592,7 +596,7 @@ export class MyApp { constructor() { this.value = () => 1; } - log(_) { } + log(..._) { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @@ -633,7 +637,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE import * as i0 from "@angular/core"; export declare class MyApp { value: () => number; - log(_: any): void; + log(..._: any[]): void; + root: any; + inner: any; + innermost: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -677,6 +684,7 @@ export declare class MyApp { items: { name: string; }[]; + item: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -726,6 +734,7 @@ export declare class MyApp { items: { name: string; }[]; + item: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -769,6 +778,7 @@ export declare class MyApp { items: { name: string; }[]; + item: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -812,6 +822,7 @@ export declare class MyApp { items: { name: string; }[]; + item: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -866,6 +877,8 @@ export declare class MyApp { name: string; subItems: string[]; }[]; + item: any; + subitem: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -921,6 +934,13 @@ import * as i0 from "@angular/core"; export declare class MyApp { message: string; items: never[]; + item: any; + $index: any; + $first: any; + $last: any; + $even: any; + $odd: any; + $count: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -976,6 +996,12 @@ import * as i0 from "@angular/core"; export declare class MyApp { message: string; items: never[]; + idx: any; + f: any; + l: any; + ev: any; + o: any; + co: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -1036,6 +1062,9 @@ export declare class MyApp { name: string; subItems: string[]; }[]; + item: any; + outerCount: any; + $count: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -1083,6 +1112,11 @@ export declare class MyApp { message: string; items: never[]; log(..._: any[]): void; + item: any; + ev: any; + $index: any; + $first: any; + $count: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -1112,6 +1146,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE import * as i0 from "@angular/core"; export declare class MyApp { items: never[]; + item: any; + $odd: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } @@ -1151,6 +1187,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE import * as i0 from "@angular/core"; export declare class MyApp { items: string[]; + item: any; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for.ts index 3fc1b7fe644..2da0a4c6ccb 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for.ts @@ -11,4 +11,5 @@ import {Component} from '@angular/core'; export class MyApp { message = 'hello'; items = [{name: 'one'}, {name: 'two'}, {name: 'three'}]; + item: any; // TODO(crisbeto): remove this once template type checking is full implemented. } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables.ts index 883ed33025a..02e472ddc74 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables.ts @@ -18,4 +18,12 @@ import {Component} from '@angular/core'; export class MyApp { message = 'hello'; items = []; + + // TODO(crisbeto): remove this once template type checking is full implemented. + idx: any; + f: any; + l: any; + ev: any; + o: any; + co: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots.ts index 9c74bfa9493..2fba79a5291 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots.ts @@ -11,4 +11,5 @@ import {Component} from '@angular/core'; }) export class MyApp { items = ['one', 'two', 'three']; + item: any; // TODO(crisbeto): remove this once template type checking is full implemented. } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables.ts index d37012244e0..dfdd321a6dc 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables.ts @@ -18,4 +18,13 @@ import {Component} from '@angular/core'; export class MyApp { message = 'hello'; items = []; + + // TODO(crisbeto): remove this once template type checking is full implemented. + item: any; + $index: any; + $first: any; + $last: any; + $even: any; + $odd: any; + $count: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener.ts index 40cfafe48ca..2cb348c30be 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener.ts @@ -14,4 +14,11 @@ export class MyApp { message = 'hello'; items = []; log(..._: any[]) {} + + // TODO(crisbeto): remove this once template type checking is full implemented. + item: any; + ev: any; + $index: any; + $first: any; + $count: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field.ts index 4035eee0fa7..de495f43d20 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field.ts @@ -11,4 +11,5 @@ import {Component} from '@angular/core'; export class MyApp { message = 'hello'; items = [{name: 'one'}, {name: 'two'}, {name: 'three'}]; + item: any; // TODO(crisbeto): remove this once template type checking is full implemented. } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index.ts index d0aec2de26d..c3ee7be427e 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index.ts @@ -11,4 +11,5 @@ import {Component} from '@angular/core'; export class MyApp { message = 'hello'; items = [{name: 'one'}, {name: 'two'}, {name: 'three'}]; + item: any; // TODO(crisbeto): remove this once template type checking is full implemented. } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_variables_expression.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_variables_expression.ts index 92ad8ce2e44..d7889837bf4 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_variables_expression.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_variables_expression.ts @@ -5,4 +5,7 @@ import {Component} from '@angular/core'; }) export class MyApp { items = []; + // TODO(crisbeto): remove this once template type checking is full implemented. + item: any; + $odd: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty.ts index 2f72f30252a..39e8afb45a9 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty.ts @@ -14,4 +14,5 @@ import {Component} from '@angular/core'; export class MyApp { message = 'hello'; items = [{name: 'one'}, {name: 'two'}, {name: 'three'}]; + item: any; // TODO(crisbeto): remove this once template type checking is full implemented. } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias.ts index 9b2ebdfc617..49b98e127cd 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias.ts @@ -15,4 +15,8 @@ import {Component} from '@angular/core'; }) export class MyApp { value = () => 1; + // TODO(crisbeto): remove this once template type checking is full implemented. + root: any; + inner: any; + innermost: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias_listeners.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias_listeners.ts index c6f0805753d..db86f9bc616 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias_listeners.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_nested_alias_listeners.ts @@ -17,5 +17,9 @@ import {Component} from '@angular/core'; }) export class MyApp { value = () => 1; - log(_: any) {} + log(..._: any[]) {} + // TODO(crisbeto): remove this once template type checking is full implemented. + root: any; + inner: any; + innermost: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_with_alias.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_with_alias.ts index efba07e4d0a..ef1f6947a8a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_with_alias.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/if_with_alias.ts @@ -11,4 +11,6 @@ import {Component} from '@angular/core'; export class MyApp { message = 'hello'; value = () => 1; + // TODO(crisbeto): remove this once template type checking is full implemented. + alias: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for.ts index af65fe384fa..19ba9b5408a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for.ts @@ -18,4 +18,8 @@ export class MyApp { {name: 'two', subItems: ['sub one', 'sub two', 'sub three']}, {name: 'three', subItems: ['sub one', 'sub two', 'sub three']}, ]; + + // TODO(crisbeto): remove this once template type checking is full implemented. + item: any; + subitem: any; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables.ts index 3cef397ad90..ae74cdc0ff8 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables.ts @@ -21,4 +21,9 @@ export class MyApp { {name: 'two', subItems: ['sub one', 'sub two', 'sub three']}, {name: 'three', subItems: ['sub one', 'sub two', 'sub three']}, ]; + + // TODO(crisbeto): remove this once template type checking is full implemented. + item: any; + outerCount: any; + $count: any; } diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index 4a909bfdd44..77e7f81ad1c 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -3656,5 +3656,106 @@ suppress ]); }); }); + + describe('conditional blocks', () => { + beforeEach(() => { + env.tsconfig({_enabledBlockTypes: ['if', 'switch']}); + }); + + // TODO(crisbeto): test to check the bindings of the branches. + // TODO(crisbeto): test for an `if` block with an `as` assignment. + // TODO(crisbeto): test for type narrowing. + it('should check bindings inside if blocks', () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + template: \` + {#if expr} + {{does_not_exist_main}} + {:else if expr1}{{does_not_exist_one}} + {:else if expr2}{{does_not_exist_two}} + {:else}{{does_not_exist_else}} + {/if} + \`, + standalone: true, + }) + export class Main { + expr = false; + expr1 = false; + expr2 = false; + } + `); + + const diags = env.driveDiagnostics(); + expect(diags.map(d => ts.flattenDiagnosticMessageText(d.messageText, ''))).toEqual([ + `Property 'does_not_exist_main' does not exist on type 'Main'.`, + `Property 'does_not_exist_one' does not exist on type 'Main'.`, + `Property 'does_not_exist_two' does not exist on type 'Main'.`, + `Property 'does_not_exist_else' does not exist on type 'Main'.`, + ]); + }); + + // TODO(crisbeto): test to check the bindings of the cases. + // TODO(crisbeto): test for type narrowing. + it('should check bindings inside switch blocks', () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + template: \` + {#switch expr} + {:case 1}{{does_not_exist_one}} + {:case 2}{{does_not_exist_two}} + {:default}{{does_not_exist_default}} + {/switch} + \`, + standalone: true, + }) + export class Main { + expr: any; + } + `); + + const diags = env.driveDiagnostics(); + expect(diags.map(d => ts.flattenDiagnosticMessageText(d.messageText, ''))).toEqual([ + `Property 'does_not_exist_one' does not exist on type 'Main'.`, + `Property 'does_not_exist_two' does not exist on type 'Main'.`, + `Property 'does_not_exist_default' does not exist on type 'Main'.`, + ]); + }); + }); + + // TODO(crisbeto): test for the loop expression binding + // TODO(crisbeto): test for the track expression. + // TODO(crisbeto): test for the context variables ($index, $odd etc). + describe('for loop blocks', () => { + beforeEach(() => { + env.tsconfig({_enabledBlockTypes: ['for']}); + }); + + it('should check bindings inside of for loop blocks', () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + template: \` + {#for item of items; track item} + {{does_not_exist_main}} + {:empty}{{does_not_exist_empty}} + {/for} + \`, + standalone: true, + }) + export class Main {} + `); + + const diags = env.driveDiagnostics(); + expect(diags.map(d => ts.flattenDiagnosticMessageText(d.messageText, ''))).toEqual([ + `Property 'does_not_exist_main' does not exist on type 'Main'.`, + `Property 'does_not_exist_empty' does not exist on type 'Main'.`, + ]); + }); + }); }); });