From c36fe0f6b656f3cda6732f7be50b8d6197bb1722 Mon Sep 17 00:00:00 2001 From: blickly Date: Fri, 28 Jul 2017 11:52:12 -0700 Subject: [PATCH] Use AST-based dependency finder when a file is already parsed. Also, add support for ES6 modules/goog.module to the AST-based dependency parser. This is a rollforward of [] with fix to ensure that moduleLoader is always initialized, even if parseInputs is not called. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=163498759 --- .../google/javascript/jscomp/Compiler.java | 2 + .../javascript/jscomp/CompilerInput.java | 117 ++++++++++++++---- src/com/google/javascript/jscomp/JsAst.java | 6 +- .../google/javascript/jscomp/NodeUtil.java | 8 ++ 4 files changed, 111 insertions(+), 22 deletions(-) diff --git a/src/com/google/javascript/jscomp/Compiler.java b/src/com/google/javascript/jscomp/Compiler.java index 72633073450..01fa9de8f1b 100644 --- a/src/com/google/javascript/jscomp/Compiler.java +++ b/src/com/google/javascript/jscomp/Compiler.java @@ -356,6 +356,8 @@ public void initOptions(CompilerOptions options) { } } + moduleLoader = ModuleLoader.EMPTY; + reconcileOptionsWithGuards(); // TODO(johnlenz): generally, the compiler should not be changing the options object diff --git a/src/com/google/javascript/jscomp/CompilerInput.java b/src/com/google/javascript/jscomp/CompilerInput.java index 13bb8da0173..8614407c4c8 100644 --- a/src/com/google/javascript/jscomp/CompilerInput.java +++ b/src/com/google/javascript/jscomp/CompilerInput.java @@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableSet; import com.google.javascript.jscomp.deps.DependencyInfo; import com.google.javascript.jscomp.deps.JsFileParser; +import com.google.javascript.jscomp.deps.ModuleLoader; import com.google.javascript.jscomp.deps.ModuleLoader.ModulePath; import com.google.javascript.jscomp.deps.SimpleDependencyInfo; import com.google.javascript.jscomp.parsing.parser.FeatureSet; @@ -231,7 +232,7 @@ private DependencyInfo generateDependencyInfo() { // If the code is a JsAst, then it was originally JS code, and is compatible with the // regex-based parsing of JsFileParser. - if (ast instanceof JsAst && JsFileParser.isSupported()) { + if (ast instanceof JsAst && !((JsAst) ast).isParsed() && JsFileParser.isSupported()) { // Look at the source code. // Note: it's OK to use getName() instead of // getPathRelativeToClosureBase() here because we're not using @@ -251,7 +252,7 @@ private DependencyInfo generateDependencyInfo() { } else { // Otherwise, just look at the AST. - DepsFinder finder = new DepsFinder(compiler.getCodingConvention()); + DepsFinder finder = new DepsFinder(getPath()); Node root = getAstRoot(compiler); if (root == null) { return SimpleDependencyInfo.EMPTY; @@ -277,10 +278,10 @@ private static class DepsFinder { private final Map loadFlags = new TreeMap<>(); private final List provides = new ArrayList<>(); private final List requires = new ArrayList<>(); - private final CodingConvention codingConvention; + private final ModulePath modulePath; - DepsFinder(CodingConvention codingConvention) { - this.codingConvention = codingConvention; + DepsFinder(ModulePath modulePath) { + this.modulePath = modulePath; } void visitTree(Node n) { @@ -300,27 +301,80 @@ void visitTree(Node n) { void visitSubtree(Node n, Node parent) { switch (n.getToken()) { case CALL: - boolean isModuleDetected = codingConvention.extractIsModuleFile(n, parent); - - if (isModuleDetected) { - loadFlags.put("module", "goog"); + if (n.hasTwoChildren() + && n.getFirstChild().isGetProp() + && n.getFirstFirstChild().matchesQualifiedName("goog")) { + + if (!requires.contains("goog")) { + requires.add("goog"); + } + + Node callee = n.getFirstChild(); + Node argument = n.getLastChild(); + switch (callee.getLastChild().getString()) { + + case "module": + loadFlags.put("module", "goog"); + // Fall-through + case "provide": + if (!argument.isString()) { + return; + } + provides.add(argument.getString()); + return; + + case "require": + if (!argument.isString()) { + return; + } + requires.add(argument.getString()); + return; + + case "loadModule": + // Process the block of the loadModule argument + n = argument.getLastChild(); + break; + + default: + return; + } } + break; - String require = codingConvention.extractClassNameIfRequire(n, parent); - if (require != null) { - requires.add(require); + case MODULE_BODY: + if (!parent.getBooleanProp(Node.GOOG_MODULE)) { + provides.add(modulePath.toModuleName()); + loadFlags.put("module", "es6"); } + break; + + case IMPORT: + visitEs6ModuleName(n.getLastChild(), n); + return; - String provide = codingConvention.extractClassNameIfProvide(n, parent); - if (provide != null) { - provides.add(provide); + case EXPORT: + if (NodeUtil.isExportFrom(n)) { + visitEs6ModuleName(n.getLastChild(), n); } return; - default: - if (parent != null && !parent.isExprResult() && !NodeUtil.isTopLevel(parent)) { - return; + + case VAR: + if (n.getFirstChild().matchesQualifiedName("goog") + && NodeUtil.isNamespaceDecl(n.getFirstChild())) { + provides.add("goog"); } break; + + case EXPR_RESULT: + case CONST: + case BLOCK: + case SCRIPT: + case NAME: + case DESTRUCTURING_LHS: + break; + + default: + return; } for (Node child = n.getFirstChild(); @@ -328,6 +382,28 @@ void visitSubtree(Node n, Node parent) { visitSubtree(child, n); } } + + void visitEs6ModuleName(Node n, Node parent) { + checkArgument(n.isString()); + checkArgument(parent.isExport() || parent.isImport()); + + // TODO(blickly): Move this (and the duplicated logic in JsFileParser/Es6RewriteModules) + // into ModuleLoader. + String moduleName = n.getString(); + if (moduleName.startsWith("goog:")) { + requires.add(moduleName.substring(5)); // cut off the "goog:" prefix + return; + } + ModulePath importedModule = + modulePath.resolveJsModule( + moduleName, modulePath.toString(), n.getLineno(), n.getCharno()); + + if (importedModule == null) { + importedModule = modulePath.resolveModuleAsPath(moduleName); + } + + requires.add(importedModule.toModuleName()); + } } public String getCode() throws IOException { @@ -395,9 +471,8 @@ private static Set concat(Iterable first, Iterable second) { ModulePath getPath() { if (modulePath == null) { - // Note: this method will not be called until Es6RewriteModules - // (and similar), after Compiler.moduleLoader is already set. - this.modulePath = compiler.getModuleLoader().resolve(getName()); + ModuleLoader moduleLoader = compiler.getModuleLoader(); + this.modulePath = moduleLoader.resolve(getName()); } return modulePath; } diff --git a/src/com/google/javascript/jscomp/JsAst.java b/src/com/google/javascript/jscomp/JsAst.java index f63e8cb85ee..56b26174308 100644 --- a/src/com/google/javascript/jscomp/JsAst.java +++ b/src/com/google/javascript/jscomp/JsAst.java @@ -50,7 +50,7 @@ public JsAst(SourceFile sourceFile) { @Override public Node getAstRoot(AbstractCompiler compiler) { - if (root == null) { + if (!isParsed()) { parse(compiler); root.setInputId(inputId); } @@ -139,6 +139,10 @@ public void error(String message, String sourceName, int line, int lineOffset) { } } + boolean isParsed() { + return root != null; + } + private void parse(AbstractCompiler compiler) { RecordingReporterProxy reporter = new RecordingReporterProxy( compiler.getDefaultErrorReporter()); diff --git a/src/com/google/javascript/jscomp/NodeUtil.java b/src/com/google/javascript/jscomp/NodeUtil.java index be5233f8ad0..b4afa915b29 100644 --- a/src/com/google/javascript/jscomp/NodeUtil.java +++ b/src/com/google/javascript/jscomp/NodeUtil.java @@ -4160,6 +4160,14 @@ public static void visitPostOrder( visitor.visit(node); } + /** + * @return Whether an EXPORT node has a from clause. + */ + static boolean isExportFrom(Node n) { + checkArgument(n.isExport()); + return n.hasTwoChildren(); + } + /** * @return Whether a TRY node has a finally block. */