UNPKG

react-magnetic-di

Version:
238 lines (229 loc) 11.5 kB
"use strict"; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } var _require = require('./constants'), PACKAGE_NAME = _require.PACKAGE_NAME, INJECT_FUNCTION = _require.INJECT_FUNCTION, PACKAGE_FUNCTION = _require.PACKAGE_FUNCTION, HOC_FUNCTION = _require.HOC_FUNCTION; var processDiDeclaration = require('./processor-di'); var processHOCReference = require('./processor-hoc'); var processInjectable = require('./processor-inj'); var _require2 = require('./utils'), assert = _require2.assert, createNamedImport = _require2.createNamedImport, collectReferencePaths = _require2.collectReferencePaths, collectDepsReferencePaths = _require2.collectDepsReferencePaths, isMatchingAny = _require2.isMatchingAny, isEnabledEnv = _require2.isEnabledEnv, hasDisableComment = _require2.hasDisableComment, parseOptions = _require2.parseOptions; var State = /*#__PURE__*/function () { function State(path) { var isExcluded = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; _classCallCheck(this, State); this.locations = new WeakMap(); this.imports = null; this.diIdentifier = null; this.injectIdentifier = null; this.programPath = null; this.programPath = path; this.isExcluded = isExcluded; } return _createClass(State, [{ key: "findPkgIndentifiers", value: function findPkgIndentifiers(t, body, scope) { var diImportNode = body.find(function (n) { return t.isImportDeclaration(n) && n.source.value === PACKAGE_NAME; }); var diImportSpecifier = diImportNode === null || diImportNode === void 0 ? void 0 : diImportNode.specifiers.find(function (s) { return s.imported && s.imported.name === PACKAGE_FUNCTION; }); this.diIdentifier = (diImportSpecifier === null || diImportSpecifier === void 0 ? void 0 : diImportSpecifier.local) || scope.generateUidIdentifier(PACKAGE_FUNCTION); var injectImportSpecifier = diImportNode === null || diImportNode === void 0 ? void 0 : diImportNode.specifiers.find(function (s) { return s.imported && s.imported.name === INJECT_FUNCTION; }); this.injectIdentifier = injectImportSpecifier === null || injectImportSpecifier === void 0 ? void 0 : injectImportSpecifier.local; } }, { key: "getValueForPath", value: function getValueForPath(fnPath) { return this.locations.get(fnPath) || this.locations.get(fnPath.node); } }, { key: "setValueForPath", value: function setValueForPath(fnPath, value) { this.locations.set(fnPath, value); this.locations.set(fnPath.node, value); } }, { key: "getValueOrInit", value: function getValueOrInit(fnPath) { // we need both node and path as either might get replaced if (!this.locations.has(fnPath) && !this.locations.has(fnPath.node)) { this.setValueForPath(fnPath, { diRef: null, dependencyRefs: new Set() }); } return this.getValueForPath(fnPath); } }, { key: "moveValueForPath", value: function moveValueForPath(fnPath, newFnPath) { if (newFnPath && newFnPath.isFunction()) { this.setValueForPath(newFnPath, this.getValueForPath(fnPath)); } } }, { key: "removeValueForPath", value: function removeValueForPath(fnPath) { this.locations["delete"](fnPath); this.locations["delete"](fnPath.node); } }, { key: "getAlias", value: function getAlias(name, scope) { return scope.generateUid(name); } }, { key: "addDi", value: function addDi(diRef) { var parentFnPath = diRef.getFunctionParent(); assert.isValidLocation(parentFnPath, diRef); var value = this.getValueOrInit(parentFnPath); value.diRef = diRef; } }, { key: "addDependency", value: function addDependency(depRef) { var _this = this; depRef.findParent(function (p) { var _p$node, _depRef$parentPath, _p$parentPath; // avoid dynamyc object getters/setters `get [dep]() {}` to be marked as dependencies // on their created scope (odd behaviour of scope.getBinding) var isComputedSelfPath = ((_p$node = p.node) === null || _p$node === void 0 ? void 0 : _p$node.computed) && (depRef.parentPath === p || ((_depRef$parentPath = depRef.parentPath) === null || _depRef$parentPath === void 0 ? void 0 : _depRef$parentPath.parentPath) === p); if (p.isFunction() && ((_p$parentPath = p.parentPath) === null || _p$parentPath === void 0 || (_p$parentPath = _p$parentPath.node) === null || _p$parentPath === void 0 || (_p$parentPath = _p$parentPath.callee) === null || _p$parentPath === void 0 ? void 0 : _p$parentPath.name) !== INJECT_FUNCTION && !isComputedSelfPath) { // add ref for every function scope up to the root one _this.getValueOrInit(p).dependencyRefs.add(depRef); } }); } }, { key: "addImports", value: function addImports(imports) { this.imports = imports; } }, { key: "prependDiImport", value: function prependDiImport(t) { if (this.diIdentifier.loc) return; var statement = createNamedImport(t, PACKAGE_NAME, [PACKAGE_FUNCTION], [this.diIdentifier]); this.programPath.unshiftContainer('body', statement); // after adding, make this function a noop this.prependDiImport = function () {}; } }]); }(); module.exports = function (babel) { var t = babel.types; var stateCache = new WeakMap(); var parsedOpts; return { name: PACKAGE_NAME, pre: function pre(_ref) { var opts = _ref.opts; if (!parsedOpts) { var pluginOpts = opts.plugins.find(function (p) { return p.key === PACKAGE_NAME; }).options; parsedOpts = parseOptions(pluginOpts); } }, visitor: { Program: function Program(path, _ref2) { var file = _ref2.file; var isEnabled = isEnabledEnv(parsedOpts.enabledEnvs); var isExcluded = isMatchingAny(parsedOpts.exclude, file.opts.filename); var state = new State(path, isExcluded); state.findPkgIndentifiers(t, path.node.body, path.scope); // Find all di() calls and store the arguments (to allow di custom vars) // and then remove the di call as it's quicker than trying to manipilate var diRefPaths = collectReferencePaths(t, state.diIdentifier, path.scope); diRefPaths.forEach(function (p, i, arr) { var _arr; var hasMulti = p.getFunctionParent() === ((_arr = arr[i + 1]) === null || _arr === void 0 ? void 0 : _arr.getFunctionParent()); if (isEnabled && !hasMulti) state.addDi(p);else p.parentPath.remove(); }); var alreadyProcessed = diRefPaths.some(function (p) { var _p$parentPath2; return (_p$parentPath2 = p.parentPath) === null || _p$parentPath2 === void 0 || (_p$parentPath2 = _p$parentPath2.parentPath) === null || _p$parentPath2 === void 0 ? void 0 : _p$parentPath2.isVariableDeclarator(); }); if (!isEnabled || alreadyProcessed) return; var _collectDepsReference = collectDepsReferencePaths(t, path.get('body')), references = _collectDepsReference.references, imports = _collectDepsReference.imports; references.forEach(function (p) { return state.addDependency(p); }); state.addImports(imports); // TODO // Should we add collection of globals to di via path.scope.globals? // If we have injectables and we should mock modules, let's find them all // and add relevat jest.mock() calls, before jest babel plugin looks for them if (parsedOpts.mockModules && state.injectIdentifier) { collectReferencePaths(t, state.injectIdentifier, path.scope).forEach(function (p) { return processInjectable(t, p.parentPath, state, parsedOpts); }); } stateCache.set(file, state); }, Function: function Function(path, _ref3) { var file = _ref3.file; var state = stateCache.get(file); var locationValue = state === null || state === void 0 ? void 0 : state.getValueForPath(path); var shouldDi = !(state !== null && state !== void 0 && state.isExcluded) && !hasDisableComment(path) || (locationValue === null || locationValue === void 0 ? void 0 : locationValue.diRef); // process only if function is a candidate to host di if (!state || !locationValue || !shouldDi) return; // convert arrow function returns as di needs a block if (!t.isBlockStatement(path.node.body)) { var bodyPath = path.get('body'); // convert arrow function return to block bodyPath.replaceWith(t.blockStatement([t.returnStatement(path.node.body)])); // we make sure that if body was a function that needs di() // we update the reference as new function path has been created state.moveValueForPath(bodyPath, path.get('body.body.0.argument')); } // create di declaration processDiDeclaration(t, path, locationValue, state); // once done, remove from cache so if babel calls function again we do not reprocess state.removeValueForPath(path); }, ImportDeclaration: function ImportDeclaration(path) { // first we look at the imports: // if not our package and not the right function, ignore if (path.node.source.value !== PACKAGE_NAME) return; var importHOCSpecifier = path.node.specifiers.find(function (s) { return s.imported && s.imported.name === HOC_FUNCTION; }); if (!importHOCSpecifier) return; // then we locate all usages of the method // ensuring we affect only locations where it is called var methodIdentifier = importHOCSpecifier.local.name; var binding = path.scope.getBinding(methodIdentifier); if (!binding) return; var references = binding.referencePaths.filter(function (ref) { return t.isCallExpression(ref.container); }); // for each of that location we apply a tranformation references.forEach(function (ref) { return processHOCReference(t, ref); }); } } }; };