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 fa46e7f1db..ab21246052 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 @@ -99,6 +99,13 @@ export function generateTypeCheckBlock( * `ts.Expression` which can be used to reference the operation's result. */ abstract class TcbOp { + /** + * Set to true if this operation can be considered optional. Optional operations are only executed + * when depended upon by other operations, otherwise they are disregarded. This allows for less + * code to generate, parse and type-check, overall positively contributing to performance. + */ + abstract readonly optional: boolean; + abstract execute(): ts.Expression|null; /** @@ -125,6 +132,13 @@ class TcbElementOp extends TcbOp { super(); } + get optional() { + // The statement generated by this operation is only used for type-inference of the DOM + // element's type and won't report diagnostics by itself, so the operation is marked as optional + // to avoid generating statements for DOM elements that are never referenced. + return true; + } + execute(): ts.Identifier { const id = this.tcb.allocateId(); // Add the declaration of the element using document.createElement. @@ -148,6 +162,10 @@ class TcbVariableOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): ts.Identifier { // Look for a context variable for the template. const ctx = this.scope.resolve(this.template); @@ -176,6 +194,10 @@ class TcbTemplateContextOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): ts.Identifier { // Allocate a template ctx variable and declare it with an 'any' type. The type of this variable // may be narrowed as a result of template guard conditions. @@ -198,6 +220,10 @@ class TcbTemplateBodyOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): null { // An `if` will be constructed, within which the template's children will be type checked. The // `if` is used for two reasons: it creates a new syntactic scope, isolating variables declared @@ -301,6 +327,10 @@ class TcbTextInterpolationOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): null { const expr = tcbExpression(this.binding.value, this.tcb, this.scope); this.scope.addStatement(ts.createExpressionStatement(expr)); @@ -324,6 +354,10 @@ class TcbDirectiveTypeOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): ts.Identifier { const id = this.tcb.allocateId(); @@ -352,6 +386,10 @@ class TcbDirectiveCtorOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): ts.Identifier { const id = this.tcb.allocateId(); @@ -409,6 +447,10 @@ class TcbDirectiveInputsOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): null { const dirId = this.scope.resolve(this.node, this.dir); @@ -514,6 +556,10 @@ class TcbDirectiveCtorCircularFallbackOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): ts.Identifier { const id = this.tcb.allocateId(); const typeCtor = this.tcb.env.typeCtorFor(this.dir); @@ -541,6 +587,10 @@ class TcbDomSchemaCheckerOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): ts.Expression|null { if (this.checkElement) { this.tcb.domSchemaChecker.checkElement(this.tcb.id, this.element, this.tcb.schemas); @@ -597,10 +647,14 @@ class TcbUnclaimedInputsOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): null { // `this.inputs` contains only those bindings not matched by any directive. These bindings go to // the element itself. - const elId = this.scope.resolve(this.element); + let elId: ts.Expression|null = null; // TODO(alxhub): this could be more efficient. for (const binding of this.element.inputs) { @@ -622,6 +676,9 @@ class TcbUnclaimedInputsOp extends TcbOp { if (this.tcb.env.config.checkTypeOfDomBindings && binding.type === BindingType.Property) { if (binding.name !== 'style' && binding.name !== 'class') { + if (elId === null) { + elId = this.scope.resolve(this.element); + } // A direct binding to a property. const propertyName = ATTR_TO_PROP[binding.name] || binding.name; const prop = ts.createElementAccess(elId, ts.createStringLiteral(propertyName)); @@ -656,6 +713,10 @@ class TcbDirectiveOutputsOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): null { const dirId = this.scope.resolve(this.node, this.dir); @@ -723,8 +784,12 @@ class TcbUnclaimedOutputsOp extends TcbOp { super(); } + get optional() { + return false; + } + execute(): null { - const elId = this.scope.resolve(this.element); + let elId: ts.Expression|null = null; // TODO(alxhub): this could be more efficient. for (const output of this.element.outputs) { @@ -749,6 +814,9 @@ class TcbUnclaimedOutputsOp extends TcbOp { // base `Event` type. const handler = tcbCreateEventHandler(output, this.tcb, this.scope, EventParamType.Infer); + if (elId === null) { + elId = this.scope.resolve(this.element); + } const call = ts.createCall( /* expression */ ts.createPropertyAccess(elId, 'addEventListener'), /* typeArguments */ undefined, @@ -965,7 +1033,7 @@ class Scope { */ render(): ts.Statement[] { for (let i = 0; i < this.opQueue.length; i++) { - this.executeOp(i); + this.executeOp(i, /* skipOptional */ true); } return this.statements; } @@ -1031,7 +1099,7 @@ class Scope { * Like `executeOp`, but assert that the operation actually returned `ts.Expression`. */ private resolveOp(opIndex: number): ts.Expression { - const res = this.executeOp(opIndex); + const res = this.executeOp(opIndex, /* skipOptional */ false); if (res === null) { throw new Error(`Error resolving operation, got null`); } @@ -1045,12 +1113,16 @@ class Scope { * and also protects against a circular dependency from the operation to itself by temporarily * setting the operation's result to a special expression. */ - private executeOp(opIndex: number): ts.Expression|null { + private executeOp(opIndex: number, skipOptional: boolean): ts.Expression|null { const op = this.opQueue[opIndex]; if (!(op instanceof TcbOp)) { return op; } + if (skipOptional && op.optional) { + return null; + } + // Set the result of the operation in the queue to its circular fallback. If executing this // operation results in a circular dependency, this will prevent an infinite loop and allow for // the resolution of such cycles. 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 b6c5322fbf..c15b1bc608 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 @@ -158,7 +158,7 @@ describe('type check blocks diagnostics', () => { }]; const TEMPLATE = `{{ a || a }}`; expect(tcbWithSpans(TEMPLATE, DIRECTIVES)) - .toContain('((_t2 /*23,24*/) || (_t2 /*28,29*/) /*23,29*/);'); + .toContain('((_t1 /*23,24*/) || (_t1 /*28,29*/) /*23,29*/);'); }); }); }); 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 8a11d6d87c..27cb1ac6e3 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 @@ -55,7 +55,7 @@ describe('type check blocks', () => { selector: '[dir]', inputs: {inputA: 'inputA'}, }]; - expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t2: DirA = (null!); _t2.inputA = ("value");'); + expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t1: DirA = (null!); _t1.inputA = ("value");'); }); it('should handle multiple bindings to the same property', () => { @@ -67,8 +67,8 @@ describe('type check blocks', () => { inputs: {inputA: 'inputA'}, }]; const block = tcb(TEMPLATE, DIRECTIVES); - expect(block).toContain('_t2.inputA = (1);'); - expect(block).toContain('_t2.inputA = (2);'); + expect(block).toContain('_t1.inputA = (1);'); + expect(block).toContain('_t1.inputA = (2);'); }); it('should handle empty bindings', () => { @@ -79,7 +79,7 @@ describe('type check blocks', () => { selector: '[dir-a]', inputs: {inputA: 'inputA'}, }]; - expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t2.inputA = (undefined);'); + expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t1.inputA = (undefined);'); }); it('should handle bindings without value', () => { @@ -90,7 +90,7 @@ describe('type check blocks', () => { selector: '[dir-a]', inputs: {inputA: 'inputA'}, }]; - expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t2.inputA = (undefined);'); + expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t1.inputA = (undefined);'); }); it('should handle implicit vars on ng-template', () => { @@ -124,7 +124,7 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2 = Dir.ngTypeCtor({ "fieldA": (((ctx).foo)), "fieldB": (null as any) });'); + 'var _t1 = Dir.ngTypeCtor({ "fieldA": (((ctx).foo)), "fieldB": (null as any) });'); }); it('should handle multiple bindings to the same property', () => { @@ -157,7 +157,7 @@ describe('type check blocks', () => { }]; const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - 'var _t2 = Dir.ngTypeCtor({ "color": (null as any), "strong": (null as any), "enabled": (null as any) });'); + 'var _t1 = Dir.ngTypeCtor({ "color": (null as any), "strong": (null as any), "enabled": (null as any) });'); expect(block).toContain('"blue"; false; true;'); }); @@ -175,8 +175,8 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t3 = Dir.ngTypeCtor((null!)); ' + - 'var _t2 = Dir.ngTypeCtor({ "input": (_t3) });'); + 'var _t2 = Dir.ngTypeCtor((null!)); ' + + 'var _t1 = Dir.ngTypeCtor({ "input": (_t2) });'); }); it('should generate circular references between two directives correctly', () => { @@ -204,9 +204,9 @@ describe('type check blocks', () => { ]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t4 = DirA.ngTypeCtor((null!)); ' + - 'var _t3 = DirB.ngTypeCtor({ "inputB": (_t4) }); ' + - 'var _t2 = DirA.ngTypeCtor({ "inputA": (_t3) });'); + 'var _t3 = DirA.ngTypeCtor((null!)); ' + + 'var _t2 = DirB.ngTypeCtor({ "inputB": (_t3) }); ' + + 'var _t1 = DirA.ngTypeCtor({ "inputA": (_t2) });'); }); it('should handle empty bindings', () => { @@ -247,12 +247,23 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2 = Dir.ngTypeCtor({ "fieldA": (((ctx).foo)) }); ' + - 'var _t3: typeof Dir.ngAcceptInputType_fieldA = (null!); ' + - '_t3 = (((ctx).foo));'); + 'var _t1 = Dir.ngTypeCtor({ "fieldA": (((ctx).foo)) }); ' + + 'var _t2: typeof Dir.ngAcceptInputType_fieldA = (null!); ' + + '_t2 = (((ctx).foo));'); }); }); + it('should only generate code for DOM elements that are actually referenced', () => { + const TEMPLATE = ` +
+ + `; + const block = tcb(TEMPLATE); + expect(block).not.toContain('"div"'); + expect(block).toContain('var _t1 = document.createElement("button");'); + expect(block).toContain('(ctx).handle(_t1);'); + }); + it('should generate a forward element reference correctly', () => { const TEMPLATE = ` {{ i.value }} @@ -273,9 +284,7 @@ describe('type check blocks', () => { selector: '[dir]', exportAs: ['dir'], }]; - expect(tcb(TEMPLATE, DIRECTIVES)) - .toContain( - 'var _t1: Dir = (null!); "" + ((_t1).value); var _t2 = document.createElement("div");'); + expect(tcb(TEMPLATE, DIRECTIVES)).toContain('var _t1: Dir = (null!); "" + ((_t1).value);'); }); it('should handle style and class bindings specially', () => { @@ -301,7 +310,7 @@ describe('type check blocks', () => { inputs: {'color': 'color', 'strong': 'strong', 'enabled': 'enabled'}, }]; const block = tcb(TEMPLATE, DIRECTIVES); - expect(block).toContain('var _t2: Dir = (null!);'); + expect(block).toContain('var _t1: Dir = (null!);'); expect(block).not.toContain('"color"'); expect(block).not.toContain('"strong"'); expect(block).not.toContain('"enabled"'); @@ -321,8 +330,8 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + - '_t2.input = (_t2);'); + 'var _t1: Dir = (null!); ' + + '_t1.input = (_t1);'); }); it('should generate circular references between two directives correctly', () => { @@ -348,11 +357,10 @@ describe('type check blocks', () => { ]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: DirA = (null!); ' + - 'var _t3: DirB = (null!); ' + - '_t2.inputA = (_t3); ' + - 'var _t4 = document.createElement("div"); ' + - '_t3.inputA = (_t2);'); + 'var _t1: DirA = (null!); ' + + 'var _t2: DirB = (null!); ' + + '_t1.inputA = (_t2); ' + + '_t2.inputA = (_t1);'); }); it('should handle undeclared properties', () => { @@ -368,7 +376,7 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + + 'var _t1: Dir = (null!); ' + '(((ctx).foo)); '); }); @@ -385,9 +393,9 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + - 'var _t3: typeof _t2["fieldA"] = (null!); ' + - '_t3 = (((ctx).foo)); '); + 'var _t1: Dir = (null!); ' + + 'var _t2: typeof _t1["fieldA"] = (null!); ' + + '_t2 = (((ctx).foo)); '); }); it('should assign properties via element access for field names that are not JS identifiers', @@ -404,8 +412,8 @@ describe('type check blocks', () => { }]; const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - 'var _t2: Dir = (null!); ' + - '_t2["some-input.xs"] = (((ctx).foo)); '); + 'var _t1: Dir = (null!); ' + + '_t1["some-input.xs"] = (((ctx).foo)); '); }); it('should handle a single property bound to multiple fields', () => { @@ -421,8 +429,8 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + - '_t2.field2 = _t2.field1 = (((ctx).foo));'); + 'var _t1: Dir = (null!); ' + + '_t1.field2 = _t1.field1 = (((ctx).foo));'); }); it('should handle a single property bound to multiple fields, where one of them is coerced', @@ -440,9 +448,9 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + - 'var _t3: typeof Dir.ngAcceptInputType_field1 = (null!); ' + - '_t2.field2 = _t3 = (((ctx).foo));'); + 'var _t1: Dir = (null!); ' + + 'var _t2: typeof Dir.ngAcceptInputType_field1 = (null!); ' + + '_t1.field2 = _t2 = (((ctx).foo));'); }); it('should handle a single property bound to multiple fields, where one of them is undeclared', @@ -460,8 +468,8 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + - '_t2.field2 = (((ctx).foo));'); + 'var _t1: Dir = (null!); ' + + '_t1.field2 = (((ctx).foo));'); }); it('should use coercion types if declared', () => { @@ -477,9 +485,9 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + - 'var _t3: typeof Dir.ngAcceptInputType_fieldA = (null!); ' + - '_t3 = (((ctx).foo));'); + 'var _t1: Dir = (null!); ' + + 'var _t2: typeof Dir.ngAcceptInputType_fieldA = (null!); ' + + '_t2 = (((ctx).foo));'); }); it('should use coercion types if declared, even when backing field is not declared', () => { @@ -496,9 +504,9 @@ describe('type check blocks', () => { }]; expect(tcb(TEMPLATE, DIRECTIVES)) .toContain( - 'var _t2: Dir = (null!); ' + - 'var _t3: typeof Dir.ngAcceptInputType_fieldA = (null!); ' + - '_t3 = (((ctx).foo));'); + 'var _t1: Dir = (null!); ' + + 'var _t2: typeof Dir.ngAcceptInputType_fieldA = (null!); ' + + '_t2 = (((ctx).foo));'); }); it('should handle $any casts', () => { @@ -561,7 +569,7 @@ describe('type check blocks', () => { const TEMPLATE = `
`; const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - '_outputHelper(_t2["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); }); it('should emit a listener function with AnimationEvent for animation events', () => { @@ -658,14 +666,14 @@ describe('type check blocks', () => { it('should include null and undefined when enabled', () => { const block = tcb(TEMPLATE, DIRECTIVES); - expect(block).toContain('_t2.dirInput = (((ctx).a));'); + expect(block).toContain('_t1.dirInput = (((ctx).a));'); expect(block).toContain('((ctx).b);'); }); it('should use the non-null assertion operator when disabled', () => { const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, strictNullInputBindings: false}; const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG); - expect(block).toContain('_t2.dirInput = (((ctx).a)!);'); + expect(block).toContain('_t1.dirInput = (((ctx).a)!);'); expect(block).toContain('((ctx).b)!;'); }); }); @@ -674,7 +682,7 @@ describe('type check blocks', () => { it('should check types of bindings when enabled', () => { const TEMPLATE = `
`; const block = tcb(TEMPLATE, DIRECTIVES); - expect(block).toContain('_t2.dirInput = (((ctx).a));'); + expect(block).toContain('_t1.dirInput = (((ctx).a));'); expect(block).toContain('((ctx).b);'); }); @@ -683,7 +691,7 @@ describe('type check blocks', () => { const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfInputBindings: false}; const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG); - expect(block).toContain('_t2.dirInput = ((((ctx).a) as any));'); + expect(block).toContain('_t1.dirInput = ((((ctx).a) as any));'); expect(block).toContain('(((ctx).b) as any);'); }); @@ -692,7 +700,7 @@ describe('type check blocks', () => { const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfInputBindings: false}; const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG); - expect(block).toContain('_t2.dirInput = ((((((ctx).a)) === (((ctx).b))) as any));'); + expect(block).toContain('_t1.dirInput = ((((((ctx).a)) === (((ctx).b))) as any));'); }); }); @@ -702,9 +710,9 @@ describe('type check blocks', () => { it('should check types of directive outputs when enabled', () => { const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - '_outputHelper(_t2["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); expect(block).toContain( - '_t1.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); + '_t2.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); }); it('should not check types of directive outputs when disabled', () => { const DISABLED_CONFIG: @@ -713,7 +721,7 @@ describe('type check blocks', () => { expect(block).toContain('function ($event: any): any { (ctx).foo($event); }'); // Note that DOM events are still checked, that is controlled by `checkTypeOfDomEvents` expect(block).toContain( - '_t1.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); + '_t2.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); }); }); @@ -739,9 +747,9 @@ describe('type check blocks', () => { it('should check types of DOM events when enabled', () => { const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - '_outputHelper(_t2["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); expect(block).toContain( - '_t1.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); + '_t2.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); }); it('should not check types of DOM events when disabled', () => { const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfDomEvents: false}; @@ -749,7 +757,7 @@ describe('type check blocks', () => { // Note that directive outputs are still checked, that is controlled by // `checkTypeOfOutputEvents` expect(block).toContain( - '_outputHelper(_t2["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); expect(block).toContain('function ($event: any): any { (ctx).foo($event); }'); }); }); @@ -785,7 +793,7 @@ describe('type check blocks', () => { it('should trace references to a directive when enabled', () => { const block = tcb(TEMPLATE, DIRECTIVES); - expect(block).toContain('(_t2).value'); + expect(block).toContain('(_t1).value'); }); it('should trace references to an when enabled', () => { @@ -812,9 +820,9 @@ describe('type check blocks', () => { it('should assign string value to the input when enabled', () => { const block = tcb(TEMPLATE, DIRECTIVES); - expect(block).toContain('_t2.disabled = ("");'); - expect(block).toContain('_t2.cols = ("3");'); - expect(block).toContain('_t2.rows = (2);'); + expect(block).toContain('_t1.disabled = ("");'); + expect(block).toContain('_t1.cols = ("3");'); + expect(block).toContain('_t1.rows = (2);'); }); it('should use any for attributes but still check bound attributes when disabled', () => { @@ -822,7 +830,7 @@ describe('type check blocks', () => { const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG); expect(block).not.toContain('"disabled"'); expect(block).not.toContain('"cols"'); - expect(block).toContain('_t2.rows = (2);'); + expect(block).toContain('_t1.rows = (2);'); }); }); @@ -912,8 +920,8 @@ describe('type check blocks', () => { TypeCheckingConfig = {...BASE_CONFIG, honorAccessModifiersForInputBindings: true}; const block = tcb(TEMPLATE, DIRECTIVES, enableChecks); expect(block).toContain( - 'var _t2: Dir = (null!); ' + - '_t2["some-input.xs"] = (((ctx).foo)); '); + 'var _t1: Dir = (null!); ' + + '_t1["some-input.xs"] = (((ctx).foo)); '); }); it('should assign restricted properties via property access', () => { @@ -930,8 +938,8 @@ describe('type check blocks', () => { TypeCheckingConfig = {...BASE_CONFIG, honorAccessModifiersForInputBindings: true}; const block = tcb(TEMPLATE, DIRECTIVES, enableChecks); expect(block).toContain( - 'var _t2: Dir = (null!); ' + - '_t2.fieldA = (((ctx).foo)); '); + 'var _t1: Dir = (null!); ' + + '_t1.fieldA = (((ctx).foo)); '); }); }); }); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts index 0a075efbf3..e2a14bce68 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker_spec.ts @@ -66,7 +66,7 @@ runInEachFileSystem(os => { const file1 = absoluteFrom('/file1.ts'); const file2 = absoluteFrom('/file2.ts'); const {program, templateTypeChecker, programStrategy} = setup([ - {fileName: file1, templates: {'Cmp1': '
'}}, + {fileName: file1, templates: {'Cmp1': '
{{value}}
'}}, {fileName: file2, templates: {'Cmp2': ''}} ]); @@ -74,7 +74,7 @@ runInEachFileSystem(os => { const block = templateTypeChecker.getTypeCheckBlock(cmp1); expect(block).not.toBeNull(); expect(block!.getText()).toMatch(/: i[0-9]\.Cmp1/); - expect(block!.getText()).toContain(`document.createElement("div")`); + expect(block!.getText()).toContain(`value`); }); it('should clear old inlines when necessary', () => { @@ -223,43 +223,43 @@ runInEachFileSystem(os => { const fileName = absoluteFrom('/main.ts'); const {program, templateTypeChecker} = setup([{ fileName, - templates: {'Cmp': '
'}, + templates: {'Cmp': '
{{original}}
'}, }]); const sf = getSourceFileOrError(program, fileName); const cmp = getClass(sf, 'Cmp'); const tcbReal = templateTypeChecker.getTypeCheckBlock(cmp)!; - expect(tcbReal.getText()).toContain('div'); + expect(tcbReal.getText()).toContain('original'); - templateTypeChecker.overrideComponentTemplate(cmp, ''); + templateTypeChecker.overrideComponentTemplate(cmp, '
{{override}}
'); const tcbOverridden = templateTypeChecker.getTypeCheckBlock(cmp); expect(tcbOverridden).not.toBeNull(); - expect(tcbOverridden!.getText()).not.toContain('div'); - expect(tcbOverridden!.getText()).toContain('span'); + expect(tcbOverridden!.getText()).not.toContain('original'); + expect(tcbOverridden!.getText()).toContain('override'); }); it('should clear overrides on request', () => { const fileName = absoluteFrom('/main.ts'); const {program, templateTypeChecker} = setup([{ fileName, - templates: {'Cmp': '
'}, + templates: {'Cmp': '
{{original}}
'}, }]); const sf = getSourceFileOrError(program, fileName); const cmp = getClass(sf, 'Cmp'); - templateTypeChecker.overrideComponentTemplate(cmp, ''); + templateTypeChecker.overrideComponentTemplate(cmp, '
{{override}}
'); const tcbOverridden = templateTypeChecker.getTypeCheckBlock(cmp)!; - expect(tcbOverridden.getText()).not.toContain('div'); - expect(tcbOverridden.getText()).toContain('span'); + expect(tcbOverridden.getText()).not.toContain('original'); + expect(tcbOverridden.getText()).toContain('override'); templateTypeChecker.resetOverrides(); - // The template should be back to the original, which has
and not . + // The template should be back to the original. const tcbReal = templateTypeChecker.getTypeCheckBlock(cmp)!; - expect(tcbReal.getText()).toContain('div'); - expect(tcbReal.getText()).not.toContain('span'); + expect(tcbReal.getText()).toContain('original'); + expect(tcbReal.getText()).not.toContain('override'); }); it('should override a template and make use of previously unused directives', () => {