babel-plugin-transform-modules-ui5
Version:
An unofficial babel plugin for SAP UI5.
212 lines (203 loc) • 8.99 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ClassTransformVisitor = void 0;
var _core = require("@babel/core");
var th = _interopRequireWildcard(require("../utils/templates"));
var ast = _interopRequireWildcard(require("../utils/ast"));
var classes = _interopRequireWildcard(require("./helpers/classes"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const CONSTRUCTOR = "constructor";
const ClassTransformVisitor = exports.ClassTransformVisitor = {
ImportDeclaration(path) {
this.importDeclarationPaths.push(path);
},
ImportDefaultSpecifier(path) {
this.importNames.push(path.node.local.name);
},
/**
* ClassDeclaration visitor.
* Use both enter() and exit() to track when the visitor is inside a UI5 class,
* in order to convert the super calls.
* No changes for non-UI5 classes.
*/
Class: {
enter(path, {
file,
opts = {}
}) {
var _node$id, _node$decorators;
const {
node
} = path;
const className = node === null || node === void 0 || (_node$id = node.id) === null || _node$id === void 0 ? void 0 : _node$id.name;
if (!className || opts.neverConvertClass) {
return;
}
if (!doesClassExtendFromImport(node, [...this.importNames])) {
// If it doesn't extend from an import, treat it as plain ES2015 class.
return;
}
// If the super class is one of the imports, we'll assume it's a UI5 managed class,
// and therefore may need to be transformed to .extend() syntax.
const classInfo = classes.getClassInfo(path, node, path.parent, opts);
// filter found decorators (to be not handled by other plugins)
node.decorators = (_node$decorators = node.decorators) === null || _node$decorators === void 0 ? void 0 : _node$decorators.filter(d => {
var _exp$callee;
const exp = d.expression;
const name = exp.name || ((_exp$callee = exp.callee) === null || _exp$callee === void 0 ? void 0 : _exp$callee.name);
return classInfo[name] === undefined;
});
if (shouldConvertClass(file, node, opts, classInfo)) {
// Save super class name for converting super calls
this.superClassName = classInfo.superClassName;
// store the classinfo
if (className) {
this.classInfo = this.classInfo || {};
this.classInfo[className] = classInfo;
}
}
},
exit(path, {
opts = {}
}) {
var _node$id2, _this$classInfo;
const {
node,
parent,
parentPath
} = path;
const className = node === null || node === void 0 || (_node$id2 = node.id) === null || _node$id2 === void 0 ? void 0 : _node$id2.name;
// Only if classinfo has been found we process this file
const classInfo = (_this$classInfo = this.classInfo) === null || _this$classInfo === void 0 ? void 0 : _this$classInfo[className];
if (!classInfo || !node.superClass) {
return;
}
// Find the Block scoped parent (Program or Function body) and search for assigned properties within that (eg. MyClass.X = "X").
const blockParent = path.findParent(path => path.isBlock()).node;
const staticProps = ast.groupPropertiesByName(ast.getOtherPropertiesOfIdentifier(blockParent, className));
// TODO: flag metadata and renderer for removal if applicable
const ui5ExtendClass = classes.convertClassToUI5Extend(path, node, classInfo, staticProps, this.importDeclarationPaths, opts);
if (path.isClassDeclaration()) {
if (_core.types.isExportDefaultDeclaration(parent)) {
path.parentPath.replaceWithMultiple([...ui5ExtendClass, th.buildExportDefault({
VALUE: node.id
})]);
} else {
// e.g. class X {}
path.replaceWithMultiple(ui5ExtendClass);
}
} else if (path.isClassExpression()) {
//e.g. return class X {}
if (_core.types.isReturnStatement(parent)) {
// Add the return statement back before calling replace
ui5ExtendClass.push(th.buildReturn({
ID: _core.types.identifier(classInfo.localName)
}));
}
parentPath.replaceWithMultiple(ui5ExtendClass);
}
this.superClassName = null;
}
},
/*!
* Visits function calls.
*/
"OptionalCallExpression|CallExpression"(path) {
const {
node
} = path;
const {
callee
} = node;
// If the file already has sap.ui.define, get the names of variables it creates to use for the class logic.
if (ast.isCallExpressionCalling(node, "sap.ui.define")) {
this.importNames.push(...getRequiredParamsOfSAPUIDefine(path, node).map(req => req.name));
return;
} else if (this.superClassName) {
if (_core.types.isSuper(callee)) {
replaceConstructorSuperCall(path, node, this.superClassName);
} else if (_core.types.isSuper(callee.object)) {
replaceObjectSuperCall(path, node, this.superClassName, path.isOptionalCallExpression());
} else if (isSuperApply(callee)) {
replaceSuperApplyCall(path, node, this.superClassName, path.isOptionalCallExpression());
}
}
},
/**
* Convert object method constructor() to constructor: function constructor(),
* since a UI5 class is not a real class.
*/
ObjectMethod(path) {
const {
node
} = path;
if (node.key.name === CONSTRUCTOR) {
// The keyword 'constructor' should not be used as a shorthand
// method name in an object. It might(?) work on some objects,
// but it doesn't work with X.extend(...) inheritance.
path.replaceWith(_core.types.objectProperty(_core.types.identifier(CONSTRUCTOR), _core.types.functionExpression(_core.types.identifier(CONSTRUCTOR), node.params, node.body)));
}
}
};
function isSuperApply(callee) {
return _core.types.isIdentifier(callee.property, {
name: "apply"
}) && _core.types.isSuper(callee.object.object);
}
function getRequiredParamsOfSAPUIDefine(path, node) {
const defineArgs = node.arguments;
const callbackNode = defineArgs.find(argNode => _core.types.isFunction(argNode));
return (callbackNode === null || callbackNode === void 0 ? void 0 : callbackNode.params) || []; // Identifier
}
/**
* Replace super() call
*/
function replaceConstructorSuperCall(path, node, superClassName) {
replaceSuperNamedCall(path, node, superClassName, CONSTRUCTOR);
}
/**
* Replace super.method() call
*/
function replaceObjectSuperCall(path, node, superClassName, optional = false) {
replaceSuperNamedCall(path, node, superClassName, node.callee.property.name, optional);
}
/**
* Replace super.method.apply() call
*/
function replaceSuperApplyCall(path, node, superClassName, optional = false) {
const methodName = node.callee.object.property.name;
const op = optional ? "?." : ".";
path.replaceWith(_core.types.callExpression(_core.types.identifier(`${superClassName}.prototype.${methodName}${op}apply`), node.arguments));
}
function replaceSuperNamedCall(path, node, superClassName, methodName, optional = false) {
// .call() is better for simple args (or not args) but doesn't work right for spread args
// if it gets further transpiled by babel spread args transform (will be .call.apply(...).
const thisEx = _core.types.thisExpression();
const hasSpread = node.arguments.some(_core.types.isSpreadElement);
const caller = hasSpread ? "apply" : "call";
const callArgs = hasSpread ? [thisEx, _core.types.arrayExpression(node.arguments)] : [thisEx, ...node.arguments];
const op = optional ? "?." : ".";
path.replaceWith(_core.types.callExpression(_core.types.identifier(`${superClassName}.prototype.${methodName}${op}${caller}`), callArgs));
}
function doesClassExtendFromImport(node, imports) {
const superClass = node.superClass;
return superClass && imports.some(imported => imported === superClass.name);
}
function shouldConvertClass(file, node, opts, classInfo) {
if (classInfo.nonUI5) {
return false;
}
if (opts.autoConvertAllExtendClasses == true) {
return true;
}
if (classInfo.name || classInfo.alias || classInfo.controller || classInfo.namespace) {
return true;
}
// Convert controller classes
if (/.*[.]controller[.](js|ts)$/.test(file.opts.filename) && opts.autoConvertControllerClass !== false) {
return true;
}
return false;
}