fix(ngcc): correctly detect outer aliased class identifiers in ES5 (#35527)
In ES5 and ES2015, class identifiers may have aliases. Previously, the `NgccReflectionHost`s recognized the following formats: - ES5: ```js var MyClass = (function () { function InnerClass() {} InnerClass_1 = InnerClass; ... }()); ``` - ES2015: ```js let MyClass = MyClass_1 = class MyClass { ... }; ``` In addition to the above, this commit adds support for recognizing an alias outside the IIFE in ES5 classes (which was previously not supported): ```js var MyClass = MyClass_1 = (function () { ... }()); ``` Jira issue: [FW-1869](https://angular-team.atlassian.net/browse/FW-1869) Partially addresses #35399. PR Close #35527
This commit is contained in:
parent
2baf90209b
commit
fde89156fa
|
@ -634,7 +634,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
if (!this.preprocessedSourceFiles.has(sourceFile)) {
|
||||
this.preprocessedSourceFiles.add(sourceFile);
|
||||
|
||||
for (const statement of sourceFile.statements) {
|
||||
for (const statement of this.getModuleStatements(sourceFile)) {
|
||||
this.preprocessStatement(statement);
|
||||
}
|
||||
}
|
||||
|
@ -660,7 +660,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
const declaration = declarations[0];
|
||||
const initializer = declaration.initializer;
|
||||
if (!ts.isIdentifier(declaration.name) || !initializer || !isAssignment(initializer) ||
|
||||
!ts.isIdentifier(initializer.left) || !ts.isClassExpression(initializer.right)) {
|
||||
!ts.isIdentifier(initializer.left) || !this.isClass(declaration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -295,8 +295,8 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
* @param checker the TS program TypeChecker
|
||||
* @returns the inner function declaration or `undefined` if it is not a "class".
|
||||
*/
|
||||
protected getInnerFunctionDeclarationFromClassDeclaration(decl: ts.Declaration): ts.FunctionDeclaration
|
||||
|undefined {
|
||||
protected getInnerFunctionDeclarationFromClassDeclaration(decl: ts.Declaration):
|
||||
ts.FunctionDeclaration|undefined {
|
||||
// Extract the IIFE body (if any).
|
||||
const iifeBody = getIifeBody(decl);
|
||||
if (!iifeBody) return undefined;
|
||||
|
@ -605,7 +605,15 @@ export function getIifeBody(declaration: ts.Declaration): ts.Block|undefined {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const call = stripParentheses(declaration.initializer);
|
||||
// Recognize a variable declaration of one of the forms:
|
||||
// - `var MyClass = (function () { ... }());`
|
||||
// - `var MyClass = MyClass_1 = (function () { ... }());`
|
||||
let parenthesizedCall = declaration.initializer;
|
||||
while (isAssignment(parenthesizedCall)) {
|
||||
parenthesizedCall = parenthesizedCall.right;
|
||||
}
|
||||
|
||||
const call = stripParentheses(parenthesizedCall);
|
||||
if (!ts.isCallExpression(call)) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -795,7 +795,8 @@ exports.MissingClass2 = MissingClass2;
|
|||
contents: `
|
||||
var functions = require('./functions');
|
||||
var methods = require('./methods');
|
||||
var aliased_class = require('./aliased_class');
|
||||
var outer_aliased_class = require('./outer_aliased_class');
|
||||
var inner_aliased_class = require('./inner_aliased_class');
|
||||
`
|
||||
},
|
||||
{
|
||||
|
@ -877,7 +878,19 @@ exports.InternalModule = InternalModule;
|
|||
`
|
||||
},
|
||||
{
|
||||
name: _('/src/aliased_class.js'),
|
||||
name: _('/src/outer_aliased_class.js'),
|
||||
contents: `
|
||||
var AliasedModule = AliasedModule_1 = (function() {
|
||||
function AliasedModule() {}
|
||||
return AliasedModule;
|
||||
}());
|
||||
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
|
||||
exports.AliasedModule = AliasedModule;
|
||||
var AliasedModule_1;
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/src/inner_aliased_class.js'),
|
||||
contents: `
|
||||
var AliasedModule = (function() {
|
||||
function AliasedModule() {}
|
||||
|
@ -1670,6 +1683,35 @@ exports.ExternalModule = ExternalModule;
|
|||
expect(actualDeclaration !.viaModule).toBe(null);
|
||||
});
|
||||
|
||||
it('should return the correct declaration for an outer alias identifier', () => {
|
||||
const PROGRAM_FILE: TestFile = {
|
||||
name: _('/test.js'),
|
||||
contents: `
|
||||
var AliasedClass = AliasedClass_1 = (function () {
|
||||
function InnerClass() {
|
||||
}
|
||||
return InnerClass;
|
||||
}());
|
||||
var AliasedClass_1;
|
||||
`,
|
||||
};
|
||||
|
||||
loadTestFiles([PROGRAM_FILE]);
|
||||
const bundle = makeTestBundleProgram(PROGRAM_FILE.name);
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
|
||||
|
||||
const expectedDeclaration = getDeclaration(
|
||||
bundle.program, PROGRAM_FILE.name, 'AliasedClass', isNamedVariableDeclaration);
|
||||
// Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`).
|
||||
const aliasIdentifier =
|
||||
(expectedDeclaration.initializer as ts.BinaryExpression).left as ts.Identifier;
|
||||
const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier);
|
||||
|
||||
expect(aliasIdentifier.getText()).toBe('AliasedClass_1');
|
||||
expect(actualDeclaration).not.toBe(null);
|
||||
expect(actualDeclaration !.node).toBe(expectedDeclaration);
|
||||
});
|
||||
|
||||
it('should return the source-file of an import namespace', () => {
|
||||
loadFakeCore(getFileSystem());
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
|
@ -2403,17 +2445,30 @@ exports.ExternalModule = ExternalModule;
|
|||
]);
|
||||
});
|
||||
|
||||
it('should resolve aliased module references to their original declaration (outer alias)',
|
||||
() => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(_('/src/index.js'));
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/29078
|
||||
it('should resolve aliased module references to their original declaration', () => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(_('/src/index.js'));
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
it('should resolve aliased module references to their original declaration (inner alias)',
|
||||
() => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(_('/src/index.js'));
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -772,7 +772,8 @@ runInEachFileSystem(() => {
|
|||
contents: `
|
||||
import * as functions from './functions';
|
||||
import * as methods from './methods';
|
||||
import * as aliased_class from './aliased_class';
|
||||
import * as outer_aliased_class from './outer_aliased_class';
|
||||
import * as inner_aliased_class from './inner_aliased_class';
|
||||
`
|
||||
},
|
||||
{
|
||||
|
@ -842,7 +843,19 @@ runInEachFileSystem(() => {
|
|||
`
|
||||
},
|
||||
{
|
||||
name: _('/src/aliased_class.js'),
|
||||
name: _('/src/outer_aliased_class.js'),
|
||||
contents: `
|
||||
var AliasedModule = AliasedModule_1 = (function() {
|
||||
function AliasedModule() {}
|
||||
return AliasedModule;
|
||||
}());
|
||||
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
|
||||
export { AliasedModule };
|
||||
var AliasedModule_1;
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/src/inner_aliased_class.js'),
|
||||
contents: `
|
||||
var AliasedModule = (function() {
|
||||
function AliasedModule() {}
|
||||
|
@ -2028,6 +2041,34 @@ runInEachFileSystem(() => {
|
|||
expect(superGetDeclarationOfIdentifierSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should return the correct declaration for an outer alias identifier', () => {
|
||||
const PROGRAM_FILE: TestFile = {
|
||||
name: _('/test.js'),
|
||||
contents: `
|
||||
var AliasedClass = AliasedClass_1 = (function () {
|
||||
function InnerClass() {
|
||||
}
|
||||
return InnerClass;
|
||||
}());
|
||||
var AliasedClass_1;
|
||||
`,
|
||||
};
|
||||
|
||||
loadTestFiles([PROGRAM_FILE]);
|
||||
const bundle = makeTestBundleProgram(PROGRAM_FILE.name);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
|
||||
|
||||
const expectedDeclaration = getDeclaration(
|
||||
bundle.program, PROGRAM_FILE.name, 'AliasedClass', isNamedVariableDeclaration);
|
||||
// Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`).
|
||||
const aliasIdentifier =
|
||||
(expectedDeclaration.initializer as ts.BinaryExpression).left as ts.Identifier;
|
||||
const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier) !;
|
||||
|
||||
expect(aliasIdentifier.getText()).toBe('AliasedClass_1');
|
||||
expect(actualDeclaration.node !.getText()).toBe(expectedDeclaration.getText());
|
||||
});
|
||||
|
||||
it('should return the correct outer declaration for an aliased inner class declaration inside an ES5 IIFE',
|
||||
() => {
|
||||
// Note that the inner class declaration `function FroalaEditorModule() {}` is aliased
|
||||
|
@ -2672,17 +2713,30 @@ runInEachFileSystem(() => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should resolve aliased module references to their original declaration (outer alias)',
|
||||
() => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(_('/src/index.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/29078
|
||||
it('should resolve aliased module references to their original declaration', () => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(_('/src/index.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
it('should resolve aliased module references to their original declaration (inner alias)',
|
||||
() => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(_('/src/index.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEndOfClass()', () => {
|
||||
|
|
|
@ -929,10 +929,10 @@ runInEachFileSystem(() => {
|
|||
name: _('/src/index.js'),
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./functions'), require('./methods'), require('./aliased_class')) :
|
||||
typeof define === 'function' && define.amd ? define('index', ['exports', './functions', './methods', './aliased_class'], factory) :
|
||||
(factory(global.index,global.functions,global.methods,global.aliased_class));
|
||||
}(this, (function (exports,functions,methods,aliased_class) { 'use strict';
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./functions'), require('./methods'), require('./outer_aliased_class'), require('./inner_aliased_class')) :
|
||||
typeof define === 'function' && define.amd ? define('index', ['exports', './functions', './methods', './outer_aliased_class', './inner_aliased_class'], factory) :
|
||||
(factory(global.index,global.functions,global.methods,global.outer_aliased_class,global.inner_aliased_class));
|
||||
}(this, (function (exports,functions,methods,outer_aliased_class,inner_aliased_class) { 'use strict';
|
||||
}))));
|
||||
`,
|
||||
},
|
||||
|
@ -1025,12 +1025,30 @@ runInEachFileSystem(() => {
|
|||
`
|
||||
},
|
||||
{
|
||||
name: _('/src/aliased_class.js'),
|
||||
name: _('/src/outer_aliased_class.js'),
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define('aliased_class', ['exports'], factory) :
|
||||
(factory(global.aliased_class));
|
||||
typeof define === 'function' && define.amd ? define('outer_aliased_class', ['exports'], factory) :
|
||||
(factory(global.outer_aliased_class));
|
||||
}(this, (function (exports,module) { 'use strict';
|
||||
var AliasedModule = AliasedModule_1 = (function() {
|
||||
function AliasedModule() {}
|
||||
return AliasedModule;
|
||||
}());
|
||||
AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; };
|
||||
exports.AliasedModule = AliasedModule;
|
||||
var AliasedModule_1;
|
||||
})));
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/src/inner_aliased_class.js'),
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define('inner_aliased_class', ['exports'], factory) :
|
||||
(factory(global.inner_aliased_class));
|
||||
}(this, (function (exports,module) { 'use strict';
|
||||
var AliasedModule = (function() {
|
||||
function AliasedModule() {}
|
||||
|
@ -1831,6 +1849,41 @@ runInEachFileSystem(() => {
|
|||
expect(actualDeclaration !.viaModule).toBe(null);
|
||||
});
|
||||
|
||||
it('should return the correct declaration for an outer alias identifier', () => {
|
||||
const PROGRAM_FILE: TestFile = {
|
||||
name: _('/test.js'),
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define('test', ['exports'], factory) :
|
||||
(factory(global.test));
|
||||
}(this, (function (exports,module) { 'use strict';
|
||||
var AliasedClass = AliasedClass_1 = (function () {
|
||||
function InnerClass() {
|
||||
}
|
||||
return InnerClass;
|
||||
}());
|
||||
var AliasedClass_1;
|
||||
})));
|
||||
`,
|
||||
};
|
||||
|
||||
loadTestFiles([PROGRAM_FILE]);
|
||||
const bundle = makeTestBundleProgram(PROGRAM_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
|
||||
|
||||
const expectedDeclaration = getDeclaration(
|
||||
bundle.program, PROGRAM_FILE.name, 'AliasedClass', isNamedVariableDeclaration);
|
||||
// Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`).
|
||||
const aliasIdentifier =
|
||||
(expectedDeclaration.initializer as ts.BinaryExpression).left as ts.Identifier;
|
||||
const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier);
|
||||
|
||||
expect(aliasIdentifier.getText()).toBe('AliasedClass_1');
|
||||
expect(actualDeclaration).not.toBe(null);
|
||||
expect(actualDeclaration !.node).toBe(expectedDeclaration);
|
||||
});
|
||||
|
||||
it('should return the source-file of an import namespace', () => {
|
||||
loadFakeCore(getFileSystem());
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
|
@ -2642,17 +2695,30 @@ runInEachFileSystem(() => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should resolve aliased module references to their original declaration (outer alias)',
|
||||
() => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/29078
|
||||
it('should resolve aliased module references to their original declaration', () => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
it('should resolve aliased module references to their original declaration (inner alias)',
|
||||
() => {
|
||||
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
||||
const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
|
||||
const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js'));
|
||||
const fn = host.getModuleWithProvidersFunctions(file);
|
||||
expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([
|
||||
['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue