From 6cea010800169f3a77fec8d66f8a08021ec2f00a Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Sun, 25 Feb 2018 17:27:46 -0500 Subject: [PATCH] Use Reify Visitor to reimplement findImportedModuleIdentifiers. Now possible because of https://github.com/benjamn/reify/pull/202. Similar in spirit to a62a2adf77147bfd4c04ea93412890b2585ff673. --- tools/isobuild/js-analyze.js | 253 ++++++++++++++--------------------- 1 file changed, 104 insertions(+), 149 deletions(-) diff --git a/tools/isobuild/js-analyze.js b/tools/isobuild/js-analyze.js index bc36d061a6..2c35ed06e1 100644 --- a/tools/isobuild/js-analyze.js +++ b/tools/isobuild/js-analyze.js @@ -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) {