UNPKG

imports-visitor

Version:

Babel visitor that makes it easier to work with imports and require calls.

406 lines (400 loc) 17.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var t = require("@babel/types"); var ImportDefinition = /*#__PURE__*/function () { function ImportDefinition(path) { (0, _classCallCheck2["default"])(this, ImportDefinition); this.path = path; } return (0, _createClass2["default"])(ImportDefinition, [{ key: "inspect", value: function inspect() { return "ImportDefinition { variableName: \"".concat(this.variableName, "\", source: \"").concat(this.source, "\", importedExport: { name: \"").concat(this.importedExport.name, "\", isImportedAsCJS: ").concat(this.importedExport.isImportedAsCJS, " }, kind: \"").concat(this.kind, "\", isDynamicImport: ").concat(this.isDynamicImport, " }"); } }, { key: "path", get: function get() { return this._path; }, set: function set(newPath) { var isValidPath = newPath.isVariableDeclarator() || // const `foo = require("bar")`; newPath.isObjectProperty() || // const { `foo` } = require("bar"); newPath.isImportDefaultSpecifier() || // import `foo` from "bar"; newPath.isImportSpecifier() || // import `{ foo }` from "bar"; newPath.isImportNamespaceSpecifier() || // import `* as foo` from "bar"; newPath.isImport() || // `import("bar")`; (old AST structure) newPath.isImportExpression() || // `import("bar")`; (new AST structure) newPath.isImportDeclaration(); // `import "bar";` if (!isValidPath) { throw new Error("Attempted to set the path of an ImportDefinition to an invalid path type: " + newPath.type); } // Define path as a non-enumerable property because otherwise the tests // freeze up when we try to print it on failure which is really annoying if (this._path != null) { this._path = newPath; } else { Object.defineProperty(this, "_path", { value: newPath, writable: true, configurable: true, enumerable: false }); } return newPath; } }, { key: "source", get: function get() { function getSourceForVariableDeclarator(declaratorPath) { var arg = declaratorPath.get("init").get("arguments")[0]; if (arg.isStringLiteral() || arg.isLiteral()) { return arg.node.value; } else { return null; } } var path = this.path; if (path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier() || path.isImportSpecifier()) { return path.parentPath.node.source.value; } else if (path.isVariableDeclarator()) { return getSourceForVariableDeclarator(path); } else if (path.isObjectProperty()) { return getSourceForVariableDeclarator(path.parentPath.parentPath); } else if (path.isImport()) { return path.findParent(function (parent) { return parent.isCallExpression(); }).node.arguments[0].value; } else if (path.isImportDeclaration() || path.isImportExpression()) { return path.node.source.value; } return null; }, set: function set(newSource) { this.fork(); var path = this.path; if (path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier() || path.isImportSpecifier()) { var importDeclaration = path.parentPath; importDeclaration.node.source = t.stringLiteral(newSource); } else if (path.isVariableDeclarator()) { path.node.init.arguments[0] = t.stringLiteral(newSource); } else if (path.isObjectProperty()) { var declarator = path.findParent(function (parent) { return parent.isVariableDeclarator(); }); declarator.node.init.arguments[0] = t.stringLiteral(newSource); } else if (path.isImport()) { var callExpression = path.findParent(function (parent) { return parent.isCallExpression(); }); callExpression.node.arguments[0] = t.stringLiteral(newSource); } else if (path.isImportDeclaration() || path.isImportExpression()) { path.node.source = t.stringLiteral(newSource); } return newSource; } }, { key: "importedExport", get: function get() { var path = this.path; var def; if (path.isImportDefaultSpecifier()) { def = { name: "default", isImportedAsCJS: false }; } else if (path.isImportNamespaceSpecifier()) { def = { name: "*", isImportedAsCJS: false }; } else if (path.isImportSpecifier()) { def = { name: path.node.imported.name, isImportedAsCJS: false }; } else if (path.isVariableDeclarator() && path.get("id").isIdentifier()) { def = { name: "*", isImportedAsCJS: true }; } else if (path.isObjectProperty() && path.get("key").isIdentifier()) { def = { name: path.node.key.name, isImportedAsCJS: true }; } else if (path.isImport() || path.isImportDeclaration() || path.isImportExpression()) { def = { name: "*", isImportedAsCJS: false }; } else { def = { name: null, isImportedAsCJS: true }; } var self = this; return { get name() { return def.name; }, set name(newName) { return self.importedExport = { name: newName, isImportedAsCJS: def.isImportedAsCJS }; }, get isImportedAsCJS() { return def.isImportedAsCJS; }, set isImportedAsCJS(newValue) { return self.importedExport = { name: def.name, isImportedAsCJS: newValue }; } }; }, set: function set(newValue) { var name = newValue.name, isImportedAsCJS = newValue.isImportedAsCJS; var current = this.importedExport; if (name === current.name && isImportedAsCJS === current.isImportedAsCJS) { return; } if (this.path.isImport() || this.path.isImportExpression()) { throw new Error("Attempted to change the importedExport of an ImportDefinition " + "referring to a dynamic import (import(\"".concat(this.source, "\")). ") + "The only property that can be changed programmatically on a dynamic " + "import is source."); } if (this.path.isImportDeclaration()) { throw new Error("Attempted to change the importedExport of an ImportDefinition " + "referring to a bare import statement (import \"".concat(this.source, "\";). ") + "The only property that can be changed programmatically on a bare " + "import is source."); } // We don't technically need to always fork here, but it's just less to // think about if we do. this.fork(); var path = this.path; var statement = path.findParent(function (parent) { return parent.isStatement(); }); if (isImportedAsCJS === false) { var newSpecifier; if (name === "*") { newSpecifier = t.importNamespaceSpecifier(t.identifier(this.variableName)); } else if (name === "default") { newSpecifier = t.importDefaultSpecifier(t.identifier(this.variableName)); } else { newSpecifier = t.importSpecifier(t.identifier(this.variableName), t.identifier(name)); } statement.insertAfter(t.importDeclaration([newSpecifier], t.stringLiteral(this.source))); var newDeclaration = statement.parentPath.get(statement.listKey)[statement.key + 1]; this.remove(); this.path = newDeclaration.get("specifiers")[0]; } else { var id; if (name === "*") { id = t.identifier(this.variableName); } else { var key = name; var value = this.variableName; id = t.objectPattern([t.objectProperty(t.identifier(name), t.identifier(this.variableName), false, key === value)]); } statement.insertAfter(t.variableDeclaration("const", [t.variableDeclarator(id, t.callExpression(t.identifier("require"), [t.stringLiteral(this.source)]))])); var _newDeclaration = statement.parentPath.get(statement.listKey)[statement.key + 1]; this.remove(); if (name === "*") { this.path = _newDeclaration.get("declarations")[0]; } else { this.path = _newDeclaration.get("declarations")[0].get("id").get("properties")[0]; } } return newValue; } }, { key: "variableName", get: function get() { var path = this.path; if (path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier() || path.isImportSpecifier()) { return path.node.local.name; } else if (path.isVariableDeclarator() && path.get("id").isIdentifier()) { return path.node.id.name; } else if (path.isObjectProperty() && path.get("value").isIdentifier()) { return path.node.value.name; } return null; }, set: function set(newName) { if (newName === this.variableName) { return newName; } var path = this.path; if (this.path.isImport() || this.path.isImportExpression()) { throw new Error("Attempted to change the variableName of an ImportDefinition " + "referring to a dynamic import (import(\"".concat(this.source, "\")). ") + "The only property that can be changed programmatically on a dynamic " + "import is source."); } if (this.path.isImportDeclaration()) { throw new Error("Attempted to change the variableName of an ImportDefinition " + "referring to a bare import statement (import(\"".concat(this.source, "\")). ") + "The only property that can be changed programmatically on a bare " + "import is source."); } if (path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier()) { path.node.local = t.identifier(newName); } else if (path.isImportSpecifier()) { path.replaceWith(t.importSpecifier(t.identifier(newName), t.identifier(path.node.imported.name))); } else if (path.isVariableDeclarator()) { path.node.id = t.identifier(newName); } else if (path.isObjectProperty()) { var key = t.identifier(path.node.key.name); var value = t.identifier(newName); path.replaceWith(t.objectProperty(key, value, false, key.name === value.name)); } return newName; } }, { key: "kind", get: function get() { var path = this.path; if (path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier() || path.isImportSpecifier()) { var declaration = path.findParent(function (parent) { return parent.isImportDeclaration(); }); return path.node.importKind || declaration.node.importKind || "value"; } else if (path.isImportDeclaration()) { return path.node.importKind || "value"; } else { return "value"; } }, set: function set(newKind) { if (newKind === this.kind) { return newKind; } if (!(newKind === "value" || newKind === "type" || newKind === "typeof")) { throw new Error("kind can only be set to 'value', 'type', or 'typeof'. Attempted to " + "set it to: ".concat(newKind)); } if (this.path.isImport() || this.path.isImportExpression()) { throw new Error("Attempted to change the kind of an ImportDefinition " + "referring to a dynamic import (import(\"".concat(this.source, "\")). ") + "The only property that can be changed programmatically on a dynamic " + "import is source."); } if (this.path.isImportDeclaration()) { throw new Error("Attempted to change the kind of an ImportDefinition " + "referring to a bare import statement (import \"".concat(this.source, "\";). ") + "This is invalid syntax; a type or typeof import cannot be bare."); } if (this.path.isImportDefaultSpecifier() || this.path.isImportNamespaceSpecifier() || this.path.isImportSpecifier()) { this.fork(); var declaration = this.path.findParent(function (parent) { return parent.isImportDeclaration(); }); declaration.node.importKind = newKind; } else { // Transform into import declaration this.importedExport.isImportedAsCJS = false; // Re-call the setter; this time it'll go down the other path this.kind = newKind; } return newKind; } }, { key: "isDynamicImport", get: function get() { if (this.path.isImport() || this.path.isImportExpression()) { return true; } return false; } }, { key: "remove", value: function remove() { if (this.path.isImport() || this.path.isImportExpression()) { throw new Error("Attempted to remove an ImportDefinition " + "referring to a dynamic import (import(\"".concat(this.source, "\")). ") + "Dynamic imports can be used in a myriad of ways and therefore " + "automated removal is not supported."); } if (this.path.isImportDeclaration()) { this.path.remove(); return; } var statementSiblings = this.path.parentPath.get(this.path.listKey); if (statementSiblings.length === 1) { // We're the only VariableDeclarator/ImportSpecifier/ObjectProperty within // our parent VariableDeclaration/ImportDeclaration/ObjectPattern, so // just removing ourselves would leave an invalid parent. Remove our // parent statement instead. this.path.findParent(function (parent) { return parent.isStatement(); }).remove(); } else { this.path.remove(); } } // Separate this import specifier from others so that it can be changed // without affecting others. // Eg if you have: // import { foo, bar } from "blah"; // and call fork() on the ImportDefinition for foo, you'll get: // import { foo } from "blah"; // import { bar } from "blah"; }, { key: "fork", value: function fork() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$insert = _ref.insert, insert = _ref$insert === void 0 ? "after" : _ref$insert; var path = this.path; var importSiblings = this._getImportSiblings(); if (importSiblings.length === 1) { // We're already alone, so no need to fork. return; } var insertionMethod; var insertionOffset; if (insert === "before") { insertionMethod = "insertBefore"; insertionOffset = -1; } else { insertionMethod = "insertAfter"; insertionOffset = 1; } if (path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier() || path.isImportSpecifier()) { var importDeclaration = path.parentPath; importDeclaration[insertionMethod](t.importDeclaration([path.node], t.stringLiteral(this.source))); var newDeclaration = importDeclaration.parentPath.get(importDeclaration.listKey)[importDeclaration.key + insertionOffset]; this.path = newDeclaration.get("specifiers")[0]; path.remove(); } else if (path.isObjectProperty()) { var declarator = path.findParent(function (parent) { return parent.isVariableDeclarator(); }); var declaration = declarator.parentPath; declaration[insertionMethod](t.variableDeclaration(declaration.node.kind, [t.variableDeclarator(t.objectPattern([path.node]), t.callExpression(t.identifier("require"), [t.stringLiteral(this.source)]))])); var _newDeclaration2 = declaration.parentPath.get(declaration.listKey)[declaration.key + insertionOffset]; this.path = _newDeclaration2.get("declarations")[0].get("id").get("properties")[0]; path.remove(); } } // Returns the paths to all import definitions that are part of the same // statement as this import definition. // For example, in this code: // const { foo, bar } = require("blah"); // foo and bar are import siblings. // And in this code: // import bloo, { qux, qaz } from "quick"; // bloo, qux, and qaz are siblings. }, { key: "_getImportSiblings", value: function _getImportSiblings() { var path = this.path; if (path.isVariableDeclarator()) { // Even though VariableDeclarators are siblings within a // VariableDeclaration, they can be modified as independent imports. return [path]; } else if (path.isImport() || path.isImportExpression() || path.isImportDeclaration()) { // Dynamic imports and bare imports are always alone. return [path]; } else if (path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier() || path.isImportSpecifier() || path.isObjectProperty()) { return path.parentPath.get(path.listKey); } } }]); }(); module.exports = ImportDefinition;