mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user