From 142ac41cacb018ea5fd7cd1a6727f8789c76b6b2 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 7 Mar 2019 11:44:12 +0000 Subject: [PATCH] fix(ivy): ngcc - handle prototype pseudo-member from typings file in ESM5 host (#29158) When processing a JavaScript program, TS may come across a symbol that has been imported from a TypeScript typings file. In this case the compiler may pass the ReflectionHost a `prototype` symbol as an export of the class. This pseudo-member symbol has no declarations, which previously caused the code in `Esm5ReflectionHost.reflectMembers()` to crash. Now we just quietly ignore such a symbol and leave `Esm2015ReflectionHost` to deal with it. (As it happens `Esm2015ReflectionHost` also quietly ignores this symbol). PR Close #29158 --- .../src/ngcc/src/host/esm2015_host.ts | 3 +++ .../src/ngcc/src/host/esm5_host.ts | 2 +- .../src/ngcc/test/host/esm5_host_spec.ts | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts index b99eff1bb8..9b387129b0 100644 --- a/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts @@ -775,6 +775,9 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; if (!node) { + // If the symbol has been imported from a TypeScript typings file then the compiler + // may pass the `prototype` symbol as an export of the class. + // But this has no declaration. In this case we just quietly ignore it. return null; } diff --git a/packages/compiler-cli/src/ngcc/src/host/esm5_host.ts b/packages/compiler-cli/src/ngcc/src/host/esm5_host.ts index 77490430aa..c6ab1d85be 100644 --- a/packages/compiler-cli/src/ngcc/src/host/esm5_host.ts +++ b/packages/compiler-cli/src/ngcc/src/host/esm5_host.ts @@ -228,7 +228,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost { protected reflectMembers(symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean): ClassMember[]|null { const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; - const propertyDefinition = getPropertyDefinition(node); + const propertyDefinition = node && getPropertyDefinition(node); if (propertyDefinition) { const members: ClassMember[] = []; if (propertyDefinition.setter) { diff --git a/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts b/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts index b83110b36a..0f93aa57e8 100644 --- a/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts @@ -520,6 +520,15 @@ const DECORATED_FILES = [ } ]; +const UNWANTED_PROTOTYPE_EXPORT_FILE = { + name: '/library.d.ts', + contents: ` + export declare class SomeParam { + someInstanceMethod(): void; + static someStaticProp: any; + }` +}; + describe('Esm5ReflectionHost', () => { describe('getDecoratorsOfDeclaration()', () => { @@ -906,6 +915,16 @@ describe('Esm5ReflectionHost', () => { expect(decorators[0].args).toEqual([]); }); }); + + it('should ignore the prototype pseudo-static property on class imported from typings files', + () => { + const program = makeTestProgram(UNWANTED_PROTOTYPE_EXPORT_FILE); + const host = new Esm5ReflectionHost(false, program.getTypeChecker()); + const classNode = getDeclaration( + program, UNWANTED_PROTOTYPE_EXPORT_FILE.name, 'SomeParam', ts.isClassDeclaration); + const members = host.getMembersOfClass(classNode); + expect(members.find(m => m.name === 'prototype')).toBeUndefined(); + }); }); describe('getConstructorParameters', () => {