Use Reify Visitor to reimplement findImportedModuleIdentifiers.

Now possible because of https://github.com/benjamn/reify/pull/202.

Similar in spirit to a62a2adf77.
This commit is contained in:
Ben Newman
2018-02-25 17:27:46 -05:00
parent b19f9fadf2
commit 6cea010800

View File

@@ -2,6 +2,9 @@ import { parse } from 'meteor-babel';
import { analyze as analyzeScope } from 'escope';
import LRU from "lru-cache";
import Visitor from "reify/lib/visitor.js";
import { findPossibleIndexes } from "reify/lib/utils.js";
const hasOwn = Object.prototype.hasOwnProperty;
var AST_CACHE = new LRU({
@@ -35,9 +38,6 @@ function tryToParse(source, hash) {
return ast;
}
var dependencyKeywordPattern =
/\b(?:require|import|importSync|dynamicImport|export)\b/g;
/**
* The `findImportedModuleIdentifiers` function takes a string of module
* source code and returns a map from imported module identifiers to AST
@@ -53,85 +53,44 @@ var dependencyKeywordPattern =
* function call, or an `import` or `export` statement).
*/
export function findImportedModuleIdentifiers(source, hash) {
const identifiers = {};
const possibleIndexes = [];
let match;
const possibleIndexes = findPossibleIndexes(source, [
"require",
"import",
"export",
"dynamicImport",
]);
dependencyKeywordPattern.lastIndex = 0;
while ((match = dependencyKeywordPattern.exec(source))) {
possibleIndexes.push(match.index);
}
if (!possibleIndexes.length) {
if (possibleIndexes.length === 0) {
return {};
}
const ast = tryToParse(source, hash);
importedIdentifierVisitor.visit(ast, source, possibleIndexes);
return importedIdentifierVisitor.identifiers;
}
function walk(node, left, right, requireIsBound) {
if (left >= right) {
// The window of possible indexes is empty, so we can ignore
// the entire subtree rooted at this node.
} else if (Array.isArray(node)) {
for (var i = 0, len = node.length; i < len; ++i) {
walk(node[i], left, right, requireIsBound);
}
} else if (isNode(node)) {
const start = node.start;
const end = node.end;
const importedIdentifierVisitor = new (class extends Visitor {
reset(rootPath, code, possibleIndexes) {
this.requireIsBound = false;
this.identifiers = Object.create(null);
// Narrow the left-right window to exclude possible indexes
// that fall outside of the current node.
while (left < right && possibleIndexes[left] < start) ++left;
while (left < right && end < possibleIndexes[right - 1]) --right;
if (left < right) {
if (! requireIsBound &&
isFunctionWithParameter(node, "require")) {
requireIsBound = true;
}
let id = getRequiredModuleId(node);
if (typeof id === "string") {
return addIdentifier(id, "require", requireIsBound);
}
const importInfo = getImportedModuleInfo(node);
if (importInfo) {
return addIdentifier(
importInfo.id,
"import",
requireIsBound,
importInfo.dynamic
);
}
// Continue traversing the children of this node.
for (const key of Object.keys(node)) {
switch (key) {
case "type":
case "loc":
case "start":
case "end":
// Ignore common keys that are never nodes.
continue;
}
walk(node[key], left, right, requireIsBound);
}
}
}
// Defining this.possibleIndexes causes the Visitor to ignore any
// subtrees of the AST that do not contain any indexes of identifiers
// that we care about. Note that findPossibleIndexes uses a RegExp to
// scan for the given identifiers, so there may be false positives,
// but that's fine because it just means scanning more of the AST.
this.possibleIndexes = possibleIndexes;
}
function addIdentifier(id, type, requireIsBound, isDynamic) {
const entry = hasOwn.call(identifiers, id)
? identifiers[id]
: identifiers[id] = {
addIdentifier(id, type, dynamic) {
const entry = hasOwn.call(this.identifiers, id)
? this.identifiers[id]
: this.identifiers[id] = {
possiblySpurious: true,
dynamic: !! isDynamic
dynamic: !! dynamic
};
if (! isDynamic) {
if (! dynamic) {
entry.dynamic = false;
}
@@ -139,7 +98,7 @@ export function findImportedModuleIdentifiers(source, hash) {
// If the identifier comes from a require call, but require is not a
// free variable, then this dependency might be spurious.
entry.possiblySpurious =
entry.possiblySpurious && requireIsBound;
entry.possiblySpurious && this.requireIsBound;
} else {
// The import keyword can't be shadowed, so any dependencies
// registered by import statements should be trusted absolutely.
@@ -147,66 +106,48 @@ export function findImportedModuleIdentifiers(source, hash) {
}
}
walk(ast, 0, possibleIndexes.length, false);
return identifiers;
}
function isNode(value) {
return value
&& typeof value === "object"
&& typeof value.type === "string"
&& typeof value.start === "number"
&& typeof value.end === "number";
}
function isIdWithName(node, name) {
return node &&
node.type === "Identifier" &&
node.name === name;
}
function isFunctionWithParameter(node, name) {
if (node.type === "FunctionExpression" ||
node.type === "FunctionDeclaration" ||
node.type === "ArrowFunctionExpression") {
return node.params.some(param => isIdWithName(param, name));
visitFunctionExpression(path) {
return this._functionParamRequireHelper(path);
}
}
function getRequiredModuleId(node) {
if (node.type === "CallExpression" &&
isIdWithName(node.callee, "require")) {
const args = node.arguments;
const argc = args.length;
if (argc > 0) {
const arg = args[0];
if (isStringLiteral(arg)) {
return arg.value;
}
visitFunctionDeclaration(path) {
return this._functionParamRequireHelper(path);
}
visitArrowFunctionExpression(path) {
return this._functionParamRequireHelper(path);
}
_functionParamRequireHelper(path) {
const node = path.getValue();
if (node.params.some(param => isIdWithName(param, "require"))) {
const { requireIsBound } = this;
this.requireIsBound = true;
this.visitChildren(path);
this.requireIsBound = requireIsBound;
} else {
this.visitChildren(path);
}
}
}
function isStringLiteral(node) {
return node && (
node.type === "StringLiteral" ||
(node.type === "Literal" &&
typeof node.value === "string"));
}
visitCallExpression(path) {
const node = path.getValue();
const args = node.arguments;
const argc = args.length;
const firstArg = args[0];
function getImportedModuleInfo(node) {
switch (node.type) {
case "CallExpression":
if (node.callee.type === "Import" ||
isIdWithName(node.callee, "import")) {
const firstArg = node.arguments[0];
if (isStringLiteral(firstArg)) {
return {
id: firstArg.value,
dynamic: true,
};
}
this.visitChildren(path);
if (! isStringLiteral(firstArg)) {
return;
}
if (isIdWithName(node.callee, "require")) {
this.addIdentifier(firstArg.value, "require");
} else if (node.callee.type === "Import" ||
isIdWithName(node.callee, "import")) {
this.addIdentifier(firstArg.value, "import", true);
} else if (node.callee.type === "MemberExpression" &&
isIdWithName(node.callee.object, "module")) {
@@ -216,38 +157,52 @@ function getImportedModuleInfo(node) {
isPropertyWithName(node.callee.property, "dynamicImport");
if (propertyName) {
const dynamic = propertyName === "dynamicImport";
const args = node.arguments;
const argc = args.length;
if (argc > 0) {
const arg = args[0];
if (isStringLiteral(arg)) {
return {
id: arg.value,
dynamic,
};
}
}
this.addIdentifier(
firstArg.value,
"import",
propertyName === "dynamicImport"
);
}
}
}
return null;
visitImportDeclaration(path) {
return this._importExportSourceHelper(path);
}
case "ImportDeclaration":
case "ExportAllDeclaration":
case "ExportNamedDeclaration":
visitExportAllDeclaration(path) {
return this._importExportSourceHelper(path);
}
visitExportNamedDeclaration(path) {
return this._importExportSourceHelper(path);
}
_importExportSourceHelper(path) {
const node = path.getValue();
// The .source of an ImportDeclaration or Export{Named,All}Declaration
// is always a string-valued Literal node, if not null.
if (isNode(node.source)) {
return {
id: node.source.value,
dynamic: false,
};
if (isStringLiteral(node.source)) {
this.addIdentifier(
node.source.value,
"import",
false
);
}
return null;
}
});
function isIdWithName(node, name) {
return node &&
node.type === "Identifier" &&
node.name === name;
}
function isStringLiteral(node) {
return node && (
node.type === "StringLiteral" ||
(node.type === "Literal" &&
typeof node.value === "string"));
}
function isPropertyWithName(node, name) {