Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(ngcc): override getInternalNameOfClass() and `getAdjacentNameOf…
…Class()` for ES5 (#33533)

In ES5 the class consists of an outer variable declaration that is
initialised by an IIFE. Inside the IIFE the class is implemented by
an inner function declaration that is returned from the IIFE.
This inner declaration may have a different name to the outer
declaration.

This commit overrides `getInternalNameOfClass()` and
`getAdjacentNameOfClass()` in `Esm5ReflectionHost` with methods that
can find the correct inner declaration name identifier.

PR Close #33533
  • Loading branch information
petebacondarwin authored and atscott committed Nov 5, 2019
1 parent 90f33dd commit 93a23b9
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
17 changes: 17 additions & 0 deletions packages/compiler-cli/ngcc/src/host/esm5_host.ts
Expand Up @@ -86,6 +86,23 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
return iife.parent.arguments[0];
}

getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier {
const innerClass = this.getInnerFunctionDeclarationFromClassDeclaration(clazz);
if (innerClass === undefined) {
throw new Error(
`getInternalNameOfClass() called on a non-ES5 class: expected ${clazz.name.text} to have an inner class declaration`);
}
if (innerClass.name === undefined) {
throw new Error(
`getInternalNameOfClass() called on a class with an anonymous inner declaration: expected a name on:\n${innerClass.getText()}`);
}
return innerClass.name;
}

getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier {
return this.getInternalNameOfClass(clazz);
}

/**
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE,
* whose value is assigned to a variable (which represents the class to the rest of the program).
Expand Down
66 changes: 66 additions & 0 deletions packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts
Expand Up @@ -147,6 +147,24 @@ var NoDecoratorConstructorClass = (function() {
}
return NoDecoratorConstructorClass;
}());
var OuterClass1 = (function() {
function InnerClass1() {
}
return InnerClass1;
}());
var OuterClass2 = (function() {
function InnerClass2() {
}
InnerClass2_1 = InnerClass12
var InnerClass2_1;
return InnerClass2;
}());
var SuperClass = (function() { function SuperClass() {} return SuperClass; }());
var ChildClass = /** @class */ (function (_super) {
__extends(ChildClass, _super);
function InnerChildClass() {}
return InnerChildClass;
}(SuperClass);
exports.EmptyClass = EmptyClass;
exports.NoDecoratorConstructorClass = NoDecoratorConstructorClass;
`,
Expand Down Expand Up @@ -2212,6 +2230,54 @@ exports.ExternalModule = ExternalModule;
});
});

describe('getInternalNameOfClass()', () => {
it('should return the name of the inner class declaration', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);

const emptyClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass');

const class1 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1');

const class2 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2');

const childClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});

describe('getAdjacentNameOfClass()', () => {
it('should return the name of the inner class declaration', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);

const emptyClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass');

const class1 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1');

const class2 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2');

const childClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});

describe('getModuleWithProvidersFunctions', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
Expand Down
22 changes: 22 additions & 0 deletions packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts
Expand Up @@ -2088,6 +2088,28 @@ runInEachFileSystem(() => {
});
});

describe('getInternalNameOfClass()', () => {
it('should return the name of the class (there is no separate inner class in ES2015)', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const node =
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
expect(host.getInternalNameOfClass(node).text).toEqual('EmptyClass');
});
});

describe('getAdjacentNameOfClass()', () => {
it('should return the name of the class (there is no separate inner class in ES2015)', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const node =
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
expect(host.getAdjacentNameOfClass(node).text).toEqual('EmptyClass');
});
});

describe('getModuleWithProvidersFunctions()', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
Expand Down
66 changes: 66 additions & 0 deletions packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts
Expand Up @@ -192,6 +192,24 @@ runInEachFileSystem(() => {
}
return NoDecoratorConstructorClass;
}());
var OuterClass1 = (function() {
function InnerClass1() {
}
return InnerClass1;
}());
var OuterClass2 = (function() {
function InnerClass2() {
}
InnerClass2_1 = InnerClass12
var InnerClass2_1;
return InnerClass2;
}());
var SuperClass = (function() { function SuperClass() {} return SuperClass; }());
var ChildClass = /** @class */ (function (_super) {
__extends(ChildClass, _super);
function InnerChildClass() {}
return InnerChildClass;
}(SuperClass);
`,
};

Expand Down Expand Up @@ -2331,6 +2349,54 @@ runInEachFileSystem(() => {
});
});

describe('getInternalNameOfClass()', () => {
it('should return the name of the inner class declaration', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());

const emptyClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass');

const class1 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1');

const class2 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2');

const childClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});

describe('getAdjacentNameOfClass()', () => {
it('should return the name of the inner class declaration', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());

const emptyClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass');

const class1 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1');

const class2 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2');

const childClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});

describe('getModuleWithProvidersFunctions', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
Expand Down
66 changes: 66 additions & 0 deletions packages/compiler-cli/ngcc/test/host/umd_host_spec.ts
Expand Up @@ -163,6 +163,24 @@ runInEachFileSystem(() => {
}
return NoDecoratorConstructorClass;
}());
var OuterClass1 = (function() {
function InnerClass1() {
}
return InnerClass1;
}());
var OuterClass2 = (function() {
function InnerClass2() {
}
InnerClass2_1 = InnerClass12
var InnerClass2_1;
return InnerClass2;
}());
var SuperClass = (function() { function SuperClass() {} return SuperClass; }());
var ChildClass = /** @class */ (function (_super) {
__extends(ChildClass, _super);
function InnerChildClass() {}
return InnerChildClass;
}(SuperClass);
exports.EmptyClass = EmptyClass;
exports.NoDecoratorConstructorClass = NoDecoratorConstructorClass;
})));`,
Expand Down Expand Up @@ -2221,6 +2239,54 @@ runInEachFileSystem(() => {
});
});

describe('getInternalNameOfClass()', () => {
it('should return the name of the inner class declaration', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);

const emptyClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass');

const class1 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1');

const class2 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2');

const childClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});

describe('getAdjacentNameOfClass()', () => {
it('should return the name of the inner class declaration', () => {
loadTestFiles([SIMPLE_CLASS_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);

const emptyClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass');

const class1 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1');

const class2 = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2');

const childClass = getDeclaration(
program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass');
});
});

describe('getModuleWithProvidersFunctions', () => {
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
() => {
Expand Down

0 comments on commit 93a23b9

Please sign in to comment.