fix(ngcc): recognize enum declarations emitted in JavaScript (#36550)

An enum declaration in TypeScript code will be emitted into JavaScript
as a regular variable declaration, with the enum members being declared
inside an IIFE. For ngcc to support interpreting such variable
declarations as enum declarations with its members, ngcc needs to
recognize the enum declaration emit structure and extract all member
from the statements in the IIFE.

This commit extends the `ConcreteDeclaration` structure in the
`ReflectionHost` abstraction to be able to capture the enum members
on a variable declaration, as a substitute for the original
`ts.EnumDeclaration` as it existed in TypeScript code. The static
interpreter has been extended to handle the extracted enum members
as it would have done for `ts.EnumDeclaration`.

Fixes #35584
Resolves FW-2069

PR Close #36550
This commit is contained in:
JoostK
2020-04-10 00:35:40 +02:00
committed by Andrew Kushnir
parent 0ce96f1d78
commit c440165384
17 changed files with 865 additions and 26 deletions

View File

@ -9,7 +9,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, CtorParameter, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DownleveledEnum, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {CommonJsReflectionHost} from '../../src/host/commonjs_host';
@ -1706,6 +1706,7 @@ exports.ExternalModule = ExternalModule;
known: knownAs,
node: getHelperDeclaration(helperName),
viaModule,
identity: null,
});
};
@ -1740,6 +1741,7 @@ exports.ExternalModule = ExternalModule;
expect(actualDeclaration).not.toBe(null);
expect(actualDeclaration!.node).toBe(expectedDeclarationNode);
expect(actualDeclaration!.viaModule).toBe(null);
expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null);
});
it('should return the correct declaration for an outer alias identifier', () => {
@ -2197,6 +2199,121 @@ exports.ExternalModule = ExternalModule;
['__unknownHelper', null],
]);
});
it('should recognize enum declarations with string values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
var Enum;
(function (Enum) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})(exports.Enum || (exports.Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should recognize enum declarations with numeric values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
var Enum;
(function (Enum) {
Enum[Enum["ValueA"] = "1"] = "ValueA";
Enum[Enum["ValueB"] = "2"] = "ValueB";
})(exports.Enum || (exports.Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should not consider IIFEs that do no assign members to the parameter as an enum declaration',
() => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
var Enum;
(function (E) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})(exports.Enum || (exports.Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(declaration.identity).toBe(null);
});
it('should not consider IIFEs without call argument as an enum declaration', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
var Enum;
(function (Enum) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})();
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host =
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(declaration.identity).toBe(null);
});
});
describe('getClassSymbol()', () => {

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, CtorParameter, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DownleveledEnum, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
@ -1558,6 +1558,7 @@ runInEachFileSystem(() => {
expect(actualDeclaration).not.toBe(null);
expect(actualDeclaration!.node).toBe(classNode);
expect(actualDeclaration!.viaModule).toBe(null);
expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null);
});
it('should return the declaration of an externally defined identifier', () => {
@ -1618,6 +1619,119 @@ runInEachFileSystem(() => {
expect(host.getDeclarationOfIdentifier(aliasedClassIdentifier)!.node)
.toBe(classDeclaration);
});
it('should recognize enum declarations with string values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (Enum) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})(Enum || (Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should recognize enum declarations with numeric values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (Enum) {
Enum[Enum["ValueA"] = "1"] = "ValueA";
Enum[Enum["ValueB"] = "2"] = "ValueB";
})(Enum || (Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should not consider IIFEs that do no assign members to the parameter as an enum declaration',
() => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (E) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})(Enum || (Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host =
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
expect(declaration.identity).toBe(null);
});
it('should not consider IIFEs without call argument as an enum declaration', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (Enum) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})();
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
expect(declaration.identity).toBe(null);
});
});
describe('getExportsOfModule()', () => {

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, CtorParameter, Decorator, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, ConcreteDeclaration, CtorParameter, Decorator, DownleveledEnum, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
@ -1754,6 +1754,7 @@ runInEachFileSystem(() => {
known: knownAs,
node: getHelperDeclaration(helperName),
viaModule,
identity: null,
});
};
@ -1786,6 +1787,7 @@ runInEachFileSystem(() => {
expect(actualDeclaration).not.toBe(null);
expect(actualDeclaration!.node).toBe(expectedDeclarationNode);
expect(actualDeclaration!.viaModule).toBe(null);
expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null);
});
it('should return the declaration of an externally defined identifier', () => {
@ -2186,6 +2188,117 @@ runInEachFileSystem(() => {
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize enum declarations with string values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (Enum) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})(Enum || (Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should recognize enum declarations with numeric values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (Enum) {
Enum[Enum["ValueA"] = "1"] = "ValueA";
Enum[Enum["ValueB"] = "2"] = "ValueB";
})(Enum || (Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should not consider IIFEs that do no assign members to the parameter as an enum declaration',
() => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (E) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})(Enum || (Enum = {}));
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
expect(declaration.identity).toBe(null);
});
it('should not consider IIFEs without call argument as an enum declaration', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
export var Enum;
(function (Enum) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})();
var value = Enum;`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('export var Enum;');
expect(declaration.identity).toBe(null);
});
});
describe('getExportsOfModule()', () => {

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, CtorParameter, Import, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DownleveledEnum, Import, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
@ -1833,6 +1833,7 @@ runInEachFileSystem(() => {
known: knownAs,
node: getHelperDeclaration(factoryFn, helperName),
viaModule,
identity: null,
});
};
@ -1874,6 +1875,7 @@ runInEachFileSystem(() => {
expect(actualDeclaration).not.toBe(null);
expect(actualDeclaration!.node).toBe(expectedDeclarationNode);
expect(actualDeclaration!.viaModule).toBe(null);
expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null);
});
it('should return the correct declaration for an outer alias identifier', () => {
@ -2179,6 +2181,147 @@ runInEachFileSystem(() => {
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize enum declarations with string values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) :
(factory(global.some_directive,global.ng.core));
}(this, (function (exports,core) { 'use strict';
var Enum;
(function (Enum) {
Enum["ValueA"] = "1";
Enum["ValueB"] = "2";
})(exports.Enum || (exports.Enum = {}));
var value = Enum;
})));
`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const {factoryFn} = parseStatementForUmdModule(
getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js'))
.statements[0])!;
const valueDecl = getVariableDeclaration(factoryFn, 'value');
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should recognize enum declarations with numeric values', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) :
(factory(global.some_directive,global.ng.core));
}(this, (function (exports,core) { 'use strict';
var Enum;
(function (Enum) {
Enum[Enum["ValueA"] = "1"] = "ValueA";
Enum[Enum["ValueB"] = "2"] = "ValueB";
})(exports.Enum || (exports.Enum = {}));
var value = Enum;
})));
`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const {factoryFn} = parseStatementForUmdModule(
getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js'))
.statements[0])!;
const valueDecl = getVariableDeclaration(factoryFn, 'value');
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(enumMembers!.length).toBe(2);
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
});
it('should not consider IIFEs that do no assign members to the parameter as an enum declaration',
() => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) :
(factory(global.some_directive,global.ng.core));
}(this, (function (exports,core) { 'use strict';
var Enum;
(function (E) {
Enum["ValueA"] = "1"];
Enum["ValueB"] = "2"];
})(exports.Enum || (exports.Enum = {}));
var value = Enum;
})));
`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(declaration.identity).toBe(null);
});
it('should not consider IIFEs without call argument as an enum declaration', () => {
const testFile: TestFile = {
name: _('/node_modules/test-package/some/file.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) :
(factory(global.some_directive,global.ng.core));
}(this, (function (exports,core) { 'use strict';
var Enum;
(function (Enum) {
Enum["ValueA"] = "1"];
Enum["ValueB"] = "2"];
})();
var value = Enum;
})));
`
};
loadTestFiles([testFile]);
const bundle = makeTestBundleProgram(testFile.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const valueDecl = getDeclaration(
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
ts.isVariableDeclaration);
const declaration = host.getDeclarationOfIdentifier(
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
expect(declaration.identity).toBe(null);
});
});
describe('getExportsOfModule()', () => {

View File

@ -317,6 +317,46 @@ runInEachFileSystem(() => {
expect(jsContents).toContain('imports: [ɵngcc1.MyOtherModule]');
});
it('should be able to resolve enum values', () => {
compileIntoApf('test-package', {
'/index.ts': `
import {Component, NgModule} from '@angular/core';
export enum StringEnum {
ValueA = "a",
ValueB = "b",
}
export enum NumericEnum {
Value3 = 3,
Value4,
}
@Component({
template: \`\${StringEnum.ValueA} - \${StringEnum.ValueB} - \${NumericEnum.Value3} - \${NumericEnum.Value4}\`,
})
export class FooCmp {}
@NgModule({
declarations: [FooCmp],
})
export class FooModule {}
`,
});
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['esm2015', 'esm5'],
});
const es2015Contents = fs.readFile(_(`/node_modules/test-package/esm2015/src/index.js`));
expect(es2015Contents).toContain('ɵngcc0.ɵɵtext(0, "a - b - 3 - 4")');
const es5Contents = fs.readFile(_(`/node_modules/test-package/esm5/src/index.js`));
expect(es5Contents).toContain('ɵngcc0.ɵɵtext(0, "a - b - 3 - 4")');
});
it('should add ɵfac but not duplicate ɵprov properties on injectables', () => {
compileIntoFlatEs5Package('test-package', {
'/index.ts': `