diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts index d0c25504ba..73d589ecc7 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts @@ -41,9 +41,6 @@ export class Environment { private pipeInsts = new Map(); protected pipeInstStatements: ts.Statement[] = []; - private outputHelperIdent: ts.Identifier|null = null; - protected helperStatements: ts.Statement[] = []; - constructor( readonly config: TypeCheckingConfig, protected importManager: ImportManager, private refEmitter: ReferenceEmitter, private reflector: ReflectionHost, @@ -113,93 +110,6 @@ export class Environment { return pipeInstId; } - /** - * Declares a helper function to be able to cast directive outputs of type `EventEmitter` to - * have an accurate `subscribe()` method that properly carries over the generic type `T` into the - * listener function passed as argument to `subscribe`. This is done to work around a typing - * deficiency in `EventEmitter.subscribe`, where the listener function is typed as any. - */ - declareOutputHelper(): ts.Expression { - if (this.outputHelperIdent !== null) { - return this.outputHelperIdent; - } - - const outputHelperIdent = ts.createIdentifier('_outputHelper'); - const genericTypeDecl = ts.createTypeParameterDeclaration('T'); - const genericTypeRef = ts.createTypeReferenceNode('T', /* typeParameters */ undefined); - - const eventEmitter = this.referenceExternalType( - '@angular/core', 'EventEmitter', [new ExpressionType(new WrappedNodeExpr(genericTypeRef))]); - - // Declare a type that has a `subscribe` method that carries over type `T` as parameter - // into the callback. The below code generates the following type literal: - // `{subscribe(cb: (event: T) => any): void;}` - const observableLike = ts.createTypeLiteralNode([ts.createMethodSignature( - /* typeParameters */ undefined, - /* parameters */[ts.createParameter( - /* decorators */ undefined, - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - /* name */ 'cb', - /* questionToken */ undefined, - /* type */ - ts.createFunctionTypeNode( - /* typeParameters */ undefined, - /* parameters */[ts.createParameter( - /* decorators */ undefined, - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - /* name */ 'event', - /* questionToken */ undefined, - /* type */ genericTypeRef)], - /* type */ ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)))], - /* type */ ts.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword), - /* name */ 'subscribe', - /* questionToken */ undefined)]); - - // Declares the first signature of `_outputHelper` that matches arguments of type - // `EventEmitter`, to convert them into `observableLike` defined above. The following - // statement is generated: - // `declare function _outputHelper(output: EventEmitter): observableLike;` - this.helperStatements.push(ts.createFunctionDeclaration( - /* decorators */ undefined, - /* modifiers */[ts.createModifier(ts.SyntaxKind.DeclareKeyword)], - /* asteriskToken */ undefined, - /* name */ outputHelperIdent, - /* typeParameters */[genericTypeDecl], - /* parameters */[ts.createParameter( - /* decorators */ undefined, - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - /* name */ 'output', - /* questionToken */ undefined, - /* type */ eventEmitter)], - /* type */ observableLike, - /* body */ undefined)); - - // Declares the second signature of `_outputHelper` that matches all other argument types, - // i.e. ensures type identity for output types other than `EventEmitter`. This corresponds - // with the following statement: - // `declare function _outputHelper(output: T): T;` - this.helperStatements.push(ts.createFunctionDeclaration( - /* decorators */ undefined, - /* modifiers */[ts.createModifier(ts.SyntaxKind.DeclareKeyword)], - /* asteriskToken */ undefined, - /* name */ outputHelperIdent, - /* typeParameters */[genericTypeDecl], - /* parameters */[ts.createParameter( - /* decorators */ undefined, - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - /* name */ 'output', - /* questionToken */ undefined, - /* type */ genericTypeRef)], - /* type */ genericTypeRef, - /* body */ undefined)); - - return this.outputHelperIdent = outputHelperIdent; - } - /** * Generate a `ts.Expression` that references the given node. * @@ -250,7 +160,6 @@ export class Environment { getPreludeStatements(): ts.Statement[] { return [ - ...this.helperStatements, ...this.pipeInstStatements, ...this.typeCtorStatements, ]; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts index c509777c49..cd7bc32c27 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts @@ -174,7 +174,7 @@ export class SymbolBuilder { } // Outputs in the TCB look like one of the two: - // * _outputHelper(_t1["outputField"]).subscribe(handler); + // * _t1["outputField"].subscribe(handler); // * _t1.addEventListener(handler); // Even with strict null checks disabled, we still produce the access as a separate statement // so that it can be found here. 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 1940aa5504..4ee589a5f1 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 @@ -874,17 +874,8 @@ export class TcbDirectiveOutputsOp extends TcbOp { // For strict checking of directive events, generate a call to the `subscribe` method // on the directive's output field to let type information flow into the handler function's // `$event` parameter. - // - // Note that the `EventEmitter` type from '@angular/core' that is typically used for - // outputs has a typings deficiency in its `subscribe` method. The generic type `T` is not - // carried into the handler function, which is vital for inference of the type of `$event`. - // As a workaround, the directive's field is passed into a helper function that has a - // specially crafted set of signatures, to effectively cast `EventEmitter` 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); - const outputHelper = - ts.createCall(this.tcb.env.declareOutputHelper(), undefined, [outputField]); - const subscribeFn = ts.createPropertyAccess(outputHelper, 'subscribe'); + const subscribeFn = ts.createPropertyAccess(outputField, 'subscribe'); const call = ts.createCall(subscribeFn, /* typeArguments */ undefined, [handler]); addParseSpanInfo(call, output.sourceSpan); this.scope.addStatement(ts.createExpressionStatement(call)); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts index 767f78e861..5b2996b8c4 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts @@ -56,9 +56,6 @@ export class TypeCheckFile extends Environment { '\n\n'; const printer = ts.createPrinter(); source += '\n'; - for (const stmt of this.helperStatements) { - source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n'; - } for (const stmt of this.pipeInstStatements) { source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n'; } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts index 8b24dc4f38..23b82d9fcd 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts @@ -593,10 +593,6 @@ class FakeEnvironment /* implements Environment */ { return ts.createParen(ts.createAsExpression(ts.createNull(), this.referenceType(ref))); } - declareOutputHelper(): ts.Expression { - return ts.createIdentifier('_outputHelper'); - } - reference(ref: Reference>): ts.Expression { return ref.node.name; } 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 6de3126cb0..3c9e7ef0e9 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 @@ -666,7 +666,7 @@ describe('type check blocks', () => { const TEMPLATE = `
`; const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });'); }); it('should emit a listener function with AnimationEvent for animation events', () => { @@ -828,7 +828,7 @@ describe('type check blocks', () => { it('should check types of directive outputs when enabled', () => { const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });'); expect(block).toContain( '_t2.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); }); @@ -865,7 +865,7 @@ describe('type check blocks', () => { it('should check types of DOM events when enabled', () => { const block = tcb(TEMPLATE, DIRECTIVES); expect(block).toContain( - '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });'); expect(block).toContain( '_t2.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });'); }); @@ -875,7 +875,7 @@ describe('type check blocks', () => { // Note that directive outputs are still checked, that is controlled by // `checkTypeOfOutputEvents` expect(block).toContain( - '_outputHelper(_t1["outputField"]).subscribe(function ($event): any { (ctx).foo($event); });'); + '_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });'); expect(block).toContain('function ($event: any): any { (ctx).foo($event); }'); }); }); diff --git a/packages/language-service/ivy/definitions.ts b/packages/language-service/ivy/definitions.ts index e78068bcf7..e8cf459ed5 100644 --- a/packages/language-service/ivy/definitions.ts +++ b/packages/language-service/ivy/definitions.ts @@ -48,7 +48,7 @@ export class DefinitionBuilder { const definitions: ts.DefinitionInfo[] = []; for (const definitionMeta of definitionMetas) { // The `$event` of event handlers would point to the $event parameter in the shim file, as in - // `_outputHelper(_t3["x"]).subscribe(function ($event): any { $event }) ;` + // `_t3["x"].subscribe(function ($event): any { $event }) ;` // If we wanted to return something for this, it would be more appropriate for something like // `getTypeDefinition`. if (isDollarEvent(definitionMeta.node)) {