babel-plugin-transform-modules-ui5
Version:
An unofficial babel plugin for SAP UI5.
222 lines (214 loc) • 8.97 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.convertDeclarationToExpression = convertDeclarationToExpression;
exports.convertFunctionDeclarationToExpression = convertFunctionDeclarationToExpression;
exports.findPropertiesOfNode = findPropertiesOfNode;
exports.getIdName = getIdName;
exports.getInternalStaticThingsOfClass = getInternalStaticThingsOfClass;
exports.getOtherPropertiesOfIdentifier = getOtherPropertiesOfIdentifier;
exports.getPropNames = getPropNames;
exports.getPropertiesOfObjectAssignOrExtendHelper = getPropertiesOfObjectAssignOrExtendHelper;
exports.groupPropertiesByName = groupPropertiesByName;
exports.isCallExpressionCalling = isCallExpressionCalling;
exports.isCommentBlock = isCommentBlock;
exports.isExtendsHelperExpression = isExtendsHelperExpression;
exports.isIdentifierNamed = isIdentifierNamed;
exports.isImport = isImport;
exports.isObjectAssignExpression = isObjectAssignExpression;
exports.isObjectAssignOrExtendsExpression = isObjectAssignOrExtendsExpression;
exports.isSuperCallExpression = isSuperCallExpression;
exports.isSuperPrototypeCallOf = isSuperPrototypeCallOf;
exports.isThisExpressionUsed = isThisExpressionUsed;
var _core = require("@babel/core");
var _arrayFlatten = require("array-flatten");
function isObjectAssignOrExtendsExpression(node) {
return isObjectAssignExpression(node) || isExtendsHelperExpression(node);
}
function isObjectAssignExpression(node) {
if (!_core.types.isCallExpression(node)) return false;
const callee = node && node.callee;
return !!(callee.object && callee.property && callee.object.name === "Object" && callee.property.name === "assign");
}
function isExtendsHelperExpression(node) {
if (!_core.types.isCallExpression(node)) return false;
const callee = node && node.callee;
return isIdentifierNamed(callee, "_extends");
}
// t.isImport only exists if the dynamic import syntax plugin is used, so avoid using that to make it optional.
function isImport(node) {
return node && node.type === "Import";
}
function isIdentifierNamed(node, name) {
return _core.types.isIdentifier(node, {
name: name
});
}
// This doesn't exist on babel-types
function isCommentBlock(node) {
return node && node.type === "CommentBlock";
}
function getIdName(node) {
return node.id && node.id.name;
}
function isSuperCallExpression(expression) {
return _core.types.isCallExpression(expression) && expression.callee.type === "Super";
}
// export function isSuperExpressionStatement(node) {
// return isSuperCallExpression(node.expression)
// }
function findPropertiesOfNode(blockScopeNode, declaration) {
if (_core.types.isFunctionDeclaration(declaration) || _core.types.isArrowFunctionExpression(declaration)) {
return null;
} else if (_core.types.isObjectExpression(declaration)) {
return declaration.properties;
} else if (_core.types.isClass(declaration)) {
return [...getInternalStaticThingsOfClass(declaration), ...getOtherPropertiesOfIdentifier(blockScopeNode, declaration.id.name)];
} else if (_core.types.isIdentifier(declaration)) {
return getOtherPropertiesOfIdentifier(blockScopeNode, declaration.name);
} else if (isObjectAssignOrExtendsExpression(declaration)) {
return getPropertiesOfObjectAssignOrExtendHelper(declaration, blockScopeNode);
}
return null;
}
function getInternalStaticThingsOfClass(classNode) {
return classNode.body.body.filter(item => item.static);
}
/**
* Traverse the top-level of the scope (program or function body) looking for either:
* - ObjectExpression assignments to the object variable.
* - Property assignments directly to our object (but not to nested properties).
*
* @return Array<{key, value}>
*/
function getOtherPropertiesOfIdentifier(blockScopeNode, idName) {
return (0, _arrayFlatten.flatten)(blockScopeNode.body.map(node => {
if (_core.types.isExpressionStatement(node)) {
// ID = value | ID.key = value | ID.key.nested = value
const {
left,
right
} = node.expression;
if (_core.types.isAssignmentExpression(node.expression)) {
if (_core.types.isIdentifier(left) && left.name === idName) {
// ID = value
if (_core.types.isObjectExpression(right)) {
// ID = {}
return right.properties; // Array<ObjectProperty>
}
} else {
const {
object,
property: key
} = left;
if (_core.types.isIdentifier(object) && object.name === idName) {
// ID.key = value
return {
key,
value: right
}; // ObjectProperty-like (key, value)
}
}
}
} else if (_core.types.isVariableDeclaration(node)) {
return node.declarations.filter(declaration => declaration.id.name === idName).map(declaration => declaration.init).filter(init => init).filter(init => _core.types.isObjectExpression(init) || isObjectAssignOrExtendsExpression(init)).map(init => _core.types.isObjectExpression(init) ? init.properties : getPropertiesOfObjectAssignOrExtendHelper(init, blockScopeNode));
}
}).filter(item => item));
}
function getPropertiesOfObjectAssignOrExtendHelper(node, blockScopeNode) {
// Check all the args and recursively try to get props of identifiers (although they may be imported)
return (0, _arrayFlatten.flatten)(node.arguments.map(arg => {
if (_core.types.isObjectExpression(arg)) {
return arg.properties;
} else if (_core.types.isIdentifier(arg)) {
// Recursive, although props will be empty if arg is an imported object
return getOtherPropertiesOfIdentifier(blockScopeNode, arg.name);
}
}));
}
function getPropNames(props) {
return props.map(prop => prop.key.name);
}
function groupPropertiesByName(properties) {
return properties && properties.reduce((accumulator, property) => {
accumulator[property.key.name] = property.value;
return accumulator;
}, {});
}
function convertFunctionDeclarationToExpression(declaration) {
return _core.types.functionExpression(declaration.id, declaration.params, declaration.body, declaration.generator, declaration.async);
}
function convertDeclarationToExpression(declaration) {
if (_core.types.isFunctionDeclaration(declaration)) {
return convertFunctionDeclarationToExpression(declaration);
} else {
return declaration;
}
}
function isSuperPrototypeCallOf(expression, superClassName, superMethodName) {
return _core.types.isCallExpression(expression) && (isCallExpressionCalling(expression, `${superClassName}.prototype.${superMethodName}.apply`) || isCallExpressionCalling(expression, `${superClassName}.prototype.${superMethodName}.call`));
}
/**
* Helper to see if a call expression is calling a given method such as sap.ui.define().
* The AST is structured in reverse (defined > ui > sap) so we reverse the method call to compare.
*
* @param {CallExpression} expression
* @param {String} dotNotationString For example, sap.ui.define or Class.prototype.method.apply
*/
function isCallExpressionCalling(expression, dotNotationString) {
if (!_core.types.isCallExpression(expression)) return false;
const callee = expression.callee;
if (callee.type === "Identifier") {
return callee.name === dotNotationString;
} else {
const parts = dotNotationString.split(".");
let node = callee;
for (const nextNamePart of parts.reverse()) {
if (!node) return false;
// property won't be there for an anonymous function
const nodeName = node.name || node.property && node.property.name;
if (nodeName !== nextNamePart) return false;
node = node.object;
}
return true;
}
}
/**
* Recursively search through some parts of a node's for use of 'this'.
* It checks the callee and the arguments and traverses into arrow functions.
*
* True scenarios include:
* - this (ThisExpression)
* - this.a.b (MemberExpression)
* - this.thing() (CallExpression > callee > MemberExpression > ThisExpression)
* - method(this) (CallExpression > arguments > ThisExpression)
* - method(this.a) (CallExpression > arguments > MemberExpression > ThisExpression)
* - () => this
* - () => this()
* - () => fn(this)
* - fn1(() => this)
* - fn1(() => this())
* - fn1(() => fn(this))
*
* Exits w/ false if a non-arrow function is encountered even if 'this' is used within.
* - fn1(function() { this.fn2(); })
*
* @param {*} node
*/
function isThisExpressionUsed(node) {
if (!node) {
return false;
}
if (Array.isArray(node)) {
return node.some(isThisExpressionUsed);
}
if (_core.types.isThisExpression(node)) {
return true;
}
if (_core.types.isFunctionExpression(node)) {
return false;
}
const traversableProps = ["body", "callee", "arguments", "expression", "object", "property"];
return traversableProps.some(prop => node[prop] && isThisExpressionUsed(node[prop]));
}