fix(ngcc): ensure reflection hosts can handle TS 3.9 IIFE wrapped classes (#36989)

In TS 3.9, ES2015 output can contain ES classes that are wrapped in an
IIFE. So now ES2015 class declarations can look like one of:

```
class OuterClass1 {}
```

```
let OuterClass = class InnerClass {};
```

```
var AliasClass;
let OuterClass = AliasClass = class InnerClass {};
```

```
let OuterClass = (() => class InnerClass {}};
```

```
var AliasClass;
let OuterClass = AliasClass = (() => class InnerClass {})();
```

```
let OuterClass = (() => {
  let AdjacentClass = class InnerClass {};
  // ... static properties or decorators attached to `AdjacentClass`
  return AdjacentClass;
})();
```

```
var AliasClass;
let OuterClass = AliasClass = (() => {
  let AdjacentClass = class InnerClass {};
  // ... static properties or decorators attached to `AdjacentClass`
  return AdjacentClass;
})();
```

The `Esm5ReflectionHost` already handles slightly different IIFE wrappers
around function-based classes. This can be substantially reused when
fixing `Esm2015ReflectionHost`, since there is a lot of commonality
between the two.

This commit moves code from the `Esm5ReflectionHost` into the `Esm2015ReflectionHost`
and looks to share as much as possible between the two hosts.

PR Close #36989
This commit is contained in:
Pete Bacon Darwin
2020-05-12 08:20:00 +01:00
committed by Kara Erickson
parent a2b8dc1cfb
commit d7440c452a
8 changed files with 792 additions and 467 deletions

View File

@ -14,8 +14,8 @@ import {ClassMemberKind, ConcreteDeclaration, CtorParameter, Decorator, Downleve
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Esm5ReflectionHost, getIifeBody} from '../../src/host/esm5_host';
import {Esm2015ReflectionHost, getIifeBody} from '../../src/host/esm2015_host';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {NgccReflectionHost} from '../../src/host/ngcc_host';
import {BundleProgram} from '../../src/packages/bundle_program';
import {MockLogger} from '../helpers/mock_logger';
@ -2283,7 +2283,8 @@ runInEachFileSystem(() => {
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const outerNode = getDeclaration(
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
.statements.find(isNamedFunctionDeclaration)!;
const classSymbol = host.getClassSymbol(outerNode);
expect(classSymbol).toBeDefined();
@ -2297,7 +2298,8 @@ runInEachFileSystem(() => {
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const outerNode = getDeclaration(
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
.statements.find(isNamedFunctionDeclaration)!;
const classSymbol = host.getClassSymbol(innerNode);
expect(classSymbol).toBeDefined();
@ -2312,7 +2314,8 @@ runInEachFileSystem(() => {
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const outerNode = getDeclaration(
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
.statements.find(isNamedFunctionDeclaration)!;
const innerSymbol = host.getClassSymbol(innerNode)!;
const outerSymbol = host.getClassSymbol(outerNode)!;
@ -2327,7 +2330,8 @@ runInEachFileSystem(() => {
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const outerNode = getDeclaration(
bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration);
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
.statements.find(isNamedFunctionDeclaration)!;
const classSymbol = host.getClassSymbol(outerNode);
expect(classSymbol).toBeDefined();
@ -2343,7 +2347,8 @@ runInEachFileSystem(() => {
const outerNode = getDeclaration(
bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass',
isNamedVariableDeclaration);
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
.statements.find(isNamedFunctionDeclaration)!;
const classSymbol = host.getClassSymbol(outerNode);
expect(classSymbol).toBeDefined();
@ -2403,7 +2408,8 @@ runInEachFileSystem(() => {
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const outerNode = getDeclaration(
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration);
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
.statements.find(isNamedFunctionDeclaration)!;
expect(host.isClass(innerNode)).toBe(true);
});