UNPKG

babel-plugin-transform-modules-ui5

Version:
212 lines (203 loc) 8.99 kB
"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; }