imports-visitor
Version:
Babel visitor that makes it easier to work with imports and require calls.
406 lines (400 loc) • 17.3 kB
JavaScript
"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;