From 269a775287069eaa30d8c99812631672ae4aef61 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 17 Nov 2020 13:00:22 -0800 Subject: [PATCH] refactor(compiler-cli): produce binding access when checkTypeOfOutputEvents is false (#39515) When `checkTypeOfOutputEvents` is `false`, we still need to produce the access to the `EventEmitter` so the Language Service can still get the type information about the field. That is, in a template `
` to something // that has a `subscribe` method that properly carries the `T` into the handler function. const handler = tcbCreateEventHandler(output, this.tcb, this.scope, EventParamType.Infer); - - if (dirId === null) { - dirId = this.scope.resolve(this.node, this.dir); - } - const outputField = ts.createElementAccess(dirId, ts.createStringLiteral(field)); - addParseSpanInfo(outputField, output.keySpan); const outputHelper = ts.createCall(this.tcb.env.declareOutputHelper(), undefined, [outputField]); const subscribeFn = ts.createPropertyAccess(outputHelper, 'subscribe'); @@ -890,8 +889,13 @@ export class TcbDirectiveOutputsOp extends TcbOp { addParseSpanInfo(call, output.sourceSpan); this.scope.addStatement(ts.createExpressionStatement(call)); } else { - // If strict checking of directive events is disabled, emit a handler function where the - // `$event` parameter has an explicit `any` type. + // If strict checking of directive events is disabled: + // + // * We still generate the access to the output field as a statement in the TCB so consumers + // of the `TemplateTypeChecker` can still find the node for the class member for the + // output. + // * Emit a handler function where the `$event` parameter has an explicit `any` type. + this.scope.addStatement(ts.createExpressionStatement(outputField)); const handler = tcbCreateEventHandler(output, this.tcb, this.scope, EventParamType.Any); this.scope.addStatement(ts.createExpressionStatement(handler)); } 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 82fc1d0583..8e2b580d7a 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 @@ -839,7 +839,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); });'); + 'addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); }); }); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts index d39489714a..23bb5912e7 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts @@ -1271,7 +1271,7 @@ runInEachFileSystem(() => { assertExpressionSymbol(eventSymbol); }); - it('returns empty list when checkTypeOfOutputEvents is false', () => { + it('still returns binding when checkTypeOfOutputEvents is false', () => { const fileName = absoluteFrom('/main.ts'); const dirFile = absoluteFrom('/dir.ts'); const {program, templateTypeChecker} = setup( @@ -1302,9 +1302,14 @@ runInEachFileSystem(() => { const nodes = templateTypeChecker.getTemplate(cmp)!; const outputABinding = (nodes[0] as TmplAstElement).outputs[0]; - const symbol = templateTypeChecker.getSymbolOfNode(outputABinding, cmp); - // TODO(atscott): should type checker still generate the subscription in this case? - expect(symbol).toBeNull(); + const symbol = templateTypeChecker.getSymbolOfNode(outputABinding, cmp)!; + assertOutputBindingSymbol(symbol); + expect( + (symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText()) + .toEqual('outputA'); + expect((symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration) + .parent.name?.text) + .toEqual('TestDir'); }); }); diff --git a/packages/language-service/ivy/test/quick_info_spec.ts b/packages/language-service/ivy/test/quick_info_spec.ts index 0a863ecd09..b6c91786b1 100644 --- a/packages/language-service/ivy/test/quick_info_spec.ts +++ b/packages/language-service/ivy/test/quick_info_spec.ts @@ -496,6 +496,17 @@ describe('quick info', () => { expectedDisplayString: '(property) TestComponent.name: string' }); }); + + it('can still get quick info when strictOutputEventTypes is false', () => { + initMockFileSystem('Native'); + env = LanguageServiceTestEnvironment.setup( + quickInfoSkeleton(), {strictOutputEventTypes: false}); + expectQuickInfo({ + templateOverride: ``, + expectedSpanText: 'test', + expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter' + }); + }); }); function expectQuickInfo(