fix(ngcc): support defineProperty() re-exports in CommonJS and UMD (#36989)

In TypeScript 3.9 some re-export syntaxes have changed to be getter
functions (created by calls to `Object.defineProperty()`) rather than
simple property accessors.

This commit adds support into the CommonJS and UMD reflection hosts
for this style of re-export syntax.

PR Close #36989
This commit is contained in:
Pete Bacon Darwin
2020-05-12 08:20:01 +01:00
committed by Kara Erickson
parent d268d2ad85
commit 91092f668e
5 changed files with 241 additions and 41 deletions

View File

@ -45,6 +45,20 @@ export interface WildcardReexportStatement extends ts.ExpressionStatement {
expression: ts.CallExpression;
}
/**
* A CommonJS or UMD re-export statement using an `Object.defineProperty()` call.
* For example:
*
* ```
* Object.defineProperty(exports, "<exported-id>",
* { enumerable: true, get: function () { return <imported-id>; } });
* ```
*/
export interface DefinePropertyReexportStatement extends ts.ExpressionStatement {
expression: ts.CallExpression&
{arguments: [ts.Identifier, ts.StringLiteral, ts.ObjectLiteralExpression]};
}
export interface RequireCall extends ts.CallExpression {
arguments: ts.CallExpression['arguments']&[ts.StringLiteral];
}
@ -133,6 +147,69 @@ export function isWildcardReexportStatement(stmt: ts.Statement): stmt is Wildcar
return stmt.expression.arguments.length > 0;
}
/**
* Check whether the statement is a re-export of the form:
*
* ```
* Object.defineProperty(exports, "<export-name>",
* { enumerable: true, get: function () { return <import-name>; } });
* ```
*/
export function isDefinePropertyReexportStatement(stmt: ts.Statement):
stmt is DefinePropertyReexportStatement {
if (!ts.isExpressionStatement(stmt) || !ts.isCallExpression(stmt.expression)) {
return false;
}
// Check for Object.defineProperty
if (!ts.isPropertyAccessExpression(stmt.expression.expression) ||
!ts.isIdentifier(stmt.expression.expression.expression) ||
stmt.expression.expression.expression.text !== 'Object' ||
!ts.isIdentifier(stmt.expression.expression.name) ||
stmt.expression.expression.name.text !== 'defineProperty') {
return false;
}
const args = stmt.expression.arguments;
if (args.length !== 3) {
return false;
}
const exportsObject = args[0];
if (!ts.isIdentifier(exportsObject) || exportsObject.text !== 'exports') {
return false;
}
const propertyKey = args[1];
if (!ts.isStringLiteral(propertyKey)) {
return false;
}
const propertyDescriptor = args[2];
if (!ts.isObjectLiteralExpression(propertyDescriptor)) {
return false;
}
return (propertyDescriptor.properties.some(
prop => prop.name !== undefined && ts.isIdentifier(prop.name) && prop.name.text === 'get'));
}
export function extractGetterFnExpression(statement: DefinePropertyReexportStatement):
ts.Expression|null {
const args = statement.expression.arguments;
const getterFn = args[2].properties.find(
prop => prop.name !== undefined && ts.isIdentifier(prop.name) && prop.name.text === 'get');
if (getterFn === undefined || !ts.isPropertyAssignment(getterFn) ||
!ts.isFunctionExpression(getterFn.initializer)) {
return null;
}
const returnStatement = getterFn.initializer.body.statements[0];
if (!ts.isReturnStatement(returnStatement) || returnStatement.expression === undefined) {
return null;
}
return returnStatement.expression;
}
/**
* Check whether the specified `ts.Node` represents a `require()` call, i.e. an call expression of
* the form: `require('<foo>')`
@ -142,3 +219,7 @@ export function isRequireCall(node: ts.Node): node is RequireCall {
node.expression.text === 'require' && node.arguments.length === 1 &&
ts.isStringLiteral(node.arguments[0]);
}
export function isExternalImport(path: string): boolean {
return !/^\.\.?(\/|$)/.test(path);
}