Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package com.google.javascript.jscomp; | 5 package com.google.javascript.jscomp; |
| 6 | 6 |
| 7 import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; | 7 import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; |
| 8 import com.google.javascript.rhino.IR; | 8 import com.google.javascript.rhino.IR; |
| 9 import com.google.javascript.rhino.JSDocInfoBuilder; | 9 import com.google.javascript.rhino.JSDocInfoBuilder; |
| 10 import com.google.javascript.rhino.JSTypeExpression; | 10 import com.google.javascript.rhino.JSTypeExpression; |
| 11 import com.google.javascript.rhino.Node; | 11 import com.google.javascript.rhino.Node; |
| 12 import com.google.javascript.rhino.Token; | 12 import com.google.javascript.rhino.Token; |
| 13 | 13 |
| 14 import java.util.ArrayList; | 14 import java.util.ArrayList; |
| 15 import java.util.HashMap; | 15 import java.util.HashMap; |
| 16 import java.util.HashSet; | |
| 16 import java.util.List; | 17 import java.util.List; |
| 17 import java.util.Map; | 18 import java.util.Map; |
| 19 import java.util.Set; | |
| 18 | 20 |
| 19 /** | 21 /** |
| 20 * Compiler pass for Chrome-specific needs. It handles the following Chrome JS f eatures: | 22 * Compiler pass for Chrome-specific needs. It handles the following Chrome JS f eatures: |
| 21 * <ul> | 23 * <ul> |
| 22 * <li>namespace declaration using {@code cr.define()}, | 24 * <li>namespace declaration using {@code cr.define()}, |
| 23 * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}. | 25 * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}. |
| 24 * </ul> | 26 * </ul> |
| 25 * | 27 * |
| 26 * <p>For the details, see tests inside ChromePassTest.java. | 28 * <p>For the details, see tests inside ChromePassTest.java. |
| 27 */ | 29 */ |
| 28 public class ChromePass extends AbstractPostOrderCallback implements CompilerPas s { | 30 public class ChromePass extends AbstractPostOrderCallback implements CompilerPas s { |
| 29 final AbstractCompiler compiler; | 31 final AbstractCompiler compiler; |
| 30 | 32 |
| 33 private Set<String> createdObjects; | |
| 34 | |
| 31 private static final String CR_DEFINE = "cr.define"; | 35 private static final String CR_DEFINE = "cr.define"; |
| 32 | 36 private static final String CR_EXPORT_PATH = "cr.exportPath"; |
| 33 private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty" ; | 37 private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty" ; |
| 34 | |
| 35 private static final String CR_DEFINE_PROPERTY = "cr.defineProperty"; | 38 private static final String CR_DEFINE_PROPERTY = "cr.defineProperty"; |
| 36 | 39 |
| 37 private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be cal led like this: " + | 40 private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be cal led like this: " + |
| 38 "cr.define('name.space', function() '{ ... return {Export: Internal} ; }');"; | 41 "cr.define('name.space', function() '{ ... return {Export: Internal} ; }');"; |
| 39 | 42 |
| 40 static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS = | 43 static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS = |
| 41 DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS", | 44 DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS", |
| 42 "cr.define() should have exactly 2 arguments. " + CR_DEFINE_ COMMON_EXPLANATION); | 45 "cr.define() should have exactly 2 arguments. " + CR_DEFINE_ COMMON_EXPLANATION); |
| 43 | 46 |
| 47 static final DiagnosticType CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS = | |
| 48 DiagnosticType.error("JSC_CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS", | |
| 49 "cr.exportPath() should have exactly 1 argument: namespace n ame."); | |
| 50 | |
| 44 static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT = | 51 static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT = |
| 45 DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT", | 52 DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT", |
| 46 "Invalid first argument for cr.define(). " + CR_DEFINE_COMMO N_EXPLANATION); | 53 "Invalid first argument for cr.define(). " + CR_DEFINE_COMMO N_EXPLANATION); |
| 47 | 54 |
| 48 static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT = | 55 static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT = |
| 49 DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT", | 56 DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT", |
| 50 "Invalid second argument for cr.define(). " + CR_DEFINE_COMM ON_EXPLANATION); | 57 "Invalid second argument for cr.define(). " + CR_DEFINE_COMM ON_EXPLANATION); |
| 51 | 58 |
| 52 static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION = | 59 static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION = |
| 53 DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMEN T", | 60 DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMEN T", |
| 54 "Function passed as second argument of cr.define() should re turn the " + | 61 "Function passed as second argument of cr.define() should re turn the " + |
| 55 "dictionary in its last statement. " + CR_DEFINE_COMMON_EXPL ANATION); | 62 "dictionary in its last statement. " + CR_DEFINE_COMMON_EXPL ANATION); |
| 56 | 63 |
| 57 static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND = | 64 static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND = |
| 58 DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND", | 65 DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND", |
| 59 "Invalid cr.PropertyKind passed to cr.defineProperty(): expe cted ATTR, " + | 66 "Invalid cr.PropertyKind passed to cr.defineProperty(): expe cted ATTR, " + |
| 60 "BOOL_ATTR or JS, found \"{0}\"."); | 67 "BOOL_ATTR or JS, found \"{0}\"."); |
| 61 | 68 |
| 62 public ChromePass(AbstractCompiler compiler) { | 69 public ChromePass(AbstractCompiler compiler) { |
| 63 this.compiler = compiler; | 70 this.compiler = compiler; |
| 71 this.createdObjects = new HashSet<>(); | |
|
Dan Beam
2014/08/14 02:23:02
nit: this.createdObjects = new HashSet<>(Arrays.as
Vitaly Pavlenko
2014/08/14 02:40:59
Done.
| |
| 72 // This variable is declared in ui/webui/resources/js/cr.js | |
|
Dan Beam
2014/08/14 02:23:01
nit: end with .
Vitaly Pavlenko
2014/08/14 02:40:59
Done.
| |
| 73 createdObjects.add("cr"); | |
| 64 } | 74 } |
| 65 | 75 |
| 66 @Override | 76 @Override |
| 67 public void process(Node externs, Node root) { | 77 public void process(Node externs, Node root) { |
| 68 NodeTraversal.traverse(compiler, root, this); | 78 NodeTraversal.traverse(compiler, root, this); |
| 69 } | 79 } |
| 70 | 80 |
| 71 @Override | 81 @Override |
| 72 public void visit(NodeTraversal t, Node node, Node parent) { | 82 public void visit(NodeTraversal t, Node node, Node parent) { |
| 73 if (node.isCall()) { | 83 if (node.isCall()) { |
| 74 Node callee = node.getFirstChild(); | 84 Node callee = node.getFirstChild(); |
| 75 if (callee.matchesQualifiedName(CR_DEFINE)) { | 85 if (callee.matchesQualifiedName(CR_DEFINE)) { |
| 76 visitNamespaceDefinition(node, parent); | 86 visitNamespaceDefinition(node, parent); |
| 77 compiler.reportCodeChange(); | 87 compiler.reportCodeChange(); |
| 88 } else if (callee.matchesQualifiedName(CR_EXPORT_PATH)) { | |
| 89 visitExportPath(node, parent); | |
| 90 compiler.reportCodeChange(); | |
| 78 } else if (callee.matchesQualifiedName(OBJECT_DEFINE_PROPERTY) || | 91 } else if (callee.matchesQualifiedName(OBJECT_DEFINE_PROPERTY) || |
| 79 callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) { | 92 callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) { |
| 80 visitPropertyDefinition(node, parent); | 93 visitPropertyDefinition(node, parent); |
| 81 compiler.reportCodeChange(); | 94 compiler.reportCodeChange(); |
| 82 } | 95 } |
| 83 } | 96 } |
| 84 } | 97 } |
| 85 | 98 |
| 86 private void visitPropertyDefinition(Node call, Node parent) { | 99 private void visitPropertyDefinition(Node call, Node parent) { |
| 87 Node callee = call.getFirstChild(); | 100 Node callee = call.getFirstChild(); |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 115 return null; | 128 return null; |
| 116 } | 129 } |
| 117 } | 130 } |
| 118 | 131 |
| 119 private void setJsDocWithType(Node target, Node type) { | 132 private void setJsDocWithType(Node target, Node type) { |
| 120 JSDocInfoBuilder builder = new JSDocInfoBuilder(false); | 133 JSDocInfoBuilder builder = new JSDocInfoBuilder(false); |
| 121 builder.recordType(new JSTypeExpression(type, "")); | 134 builder.recordType(new JSTypeExpression(type, "")); |
| 122 target.setJSDocInfo(builder.build(target)); | 135 target.setJSDocInfo(builder.build(target)); |
| 123 } | 136 } |
| 124 | 137 |
| 138 private void visitExportPath(Node crExportPathNode, Node parent) { | |
| 139 if (crExportPathNode.getChildCount() != 2) { | |
| 140 compiler.report(JSError.make(crExportPathNode, | |
| 141 CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS)); | |
| 142 return; | |
| 143 } | |
| 144 | |
| 145 createAndInsertObjectsForQualifiedName(parent, | |
| 146 crExportPathNode.getChildAtIndex(1).getString()); | |
| 147 } | |
| 148 | |
| 149 private void createAndInsertObjectsForQualifiedName(Node scriptChild, String namespace) { | |
| 150 List<Node> objectsForQualifiedName = createObjectsForQualifiedName(names pace); | |
| 151 for (Node n : objectsForQualifiedName) { | |
| 152 scriptChild.getParent().addChildBefore(n, scriptChild); | |
| 153 } | |
| 154 } | |
| 155 | |
| 125 private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) { | 156 private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) { |
| 126 if (crDefineCallNode.getChildCount() != 3) { | 157 if (crDefineCallNode.getChildCount() != 3) { |
| 127 compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBE R_OF_ARGUMENTS)); | 158 compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBE R_OF_ARGUMENTS)); |
| 128 } | 159 } |
| 129 | 160 |
| 130 Node namespaceArg = crDefineCallNode.getChildAtIndex(1); | 161 Node namespaceArg = crDefineCallNode.getChildAtIndex(1); |
| 131 Node function = crDefineCallNode.getChildAtIndex(2); | 162 Node function = crDefineCallNode.getChildAtIndex(2); |
| 132 | 163 |
| 133 if (!namespaceArg.isString()) { | 164 if (!namespaceArg.isString()) { |
| 134 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_A RGUMENT)); | 165 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_A RGUMENT)); |
| 135 return; | 166 return; |
| 136 } | 167 } |
| 137 | 168 |
| 138 // TODO(vitalyp): Check namespace name for validity here. It should be a valid chain of | 169 // TODO(vitalyp): Check namespace name for validity here. It should be a valid chain of |
| 139 // identifiers. | 170 // identifiers. |
| 140 String namespace = namespaceArg.getString(); | 171 String namespace = namespaceArg.getString(); |
| 141 | 172 |
| 142 List<Node> objectsForQualifiedName = createObjectsForQualifiedName(names pace); | 173 createAndInsertObjectsForQualifiedName(parent, namespace); |
| 143 for (Node n : objectsForQualifiedName) { | |
| 144 parent.getParent().addChildBefore(n, parent); | |
| 145 } | |
| 146 | 174 |
| 147 Node returnNode, functionBlock, objectLit; | 175 Node returnNode, functionBlock, objectLit; |
| 148 if (!function.isFunction()) { | 176 if (!function.isFunction()) { |
| 149 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_ ARGUMENT)); | 177 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_ ARGUMENT)); |
| 150 return; | 178 return; |
| 151 } | 179 } |
| 152 | 180 |
| 153 if ((functionBlock = function.getLastChild()) == null || | 181 if ((functionBlock = function.getLastChild()) == null || |
| 154 (returnNode = functionBlock.getLastChild()) == null || | 182 (returnNode = functionBlock.getLastChild()) == null || |
| 155 !returnNode.isReturn() || | 183 !returnNode.isReturn() || |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 186 * | 214 * |
| 187 * <p><pre> | 215 * <p><pre> |
| 188 * var a = a || {}; | 216 * var a = a || {}; |
| 189 * a.b = a.b || {}; | 217 * a.b = a.b || {}; |
| 190 * a.b.c = a.b.c || {};</pre> | 218 * a.b.c = a.b.c || {};</pre> |
| 191 */ | 219 */ |
| 192 private List<Node> createObjectsForQualifiedName(String namespace) { | 220 private List<Node> createObjectsForQualifiedName(String namespace) { |
| 193 List<Node> objects = new ArrayList<>(); | 221 List<Node> objects = new ArrayList<>(); |
| 194 String[] parts = namespace.split("\\."); | 222 String[] parts = namespace.split("\\."); |
| 195 | 223 |
| 196 objects.add(createJsNode("var " + parts[0] + " = " + parts[0] + " || {}; ")); | 224 createObjectIfNew(objects, parts[0], true); |
| 197 | 225 |
| 198 if (parts.length >= 2) { | 226 if (parts.length >= 2) { |
| 199 StringBuilder currPrefix = new StringBuilder().append(parts[0]); | 227 StringBuilder currPrefix = new StringBuilder().append(parts[0]); |
| 200 for (int i = 1; i < parts.length; ++i) { | 228 for (int i = 1; i < parts.length; ++i) { |
| 201 currPrefix.append(".").append(parts[i]); | 229 currPrefix.append(".").append(parts[i]); |
| 202 String code = currPrefix + " = " + currPrefix + " || {};"; | 230 createObjectIfNew(objects, currPrefix.toString(), false); |
| 203 objects.add(createJsNode(code)); | |
| 204 } | 231 } |
| 205 } | 232 } |
| 206 | 233 |
| 207 return objects; | 234 return objects; |
| 208 } | 235 } |
| 209 | 236 |
| 237 private void createObjectIfNew(List<Node> objects, String name, boolean need Var) { | |
| 238 if (!createdObjects.contains(name)) { | |
| 239 objects.add(createJsNode((needVar ? "var " : "") + name + " = " + na me + " || {};")); | |
| 240 createdObjects.add(name); | |
| 241 } | |
| 242 } | |
| 243 | |
| 210 private Node createJsNode(String code) { | 244 private Node createJsNode(String code) { |
| 211 // The parent node after parseSyntheticCode() is SCRIPT node, we need to get rid of it. | 245 // The parent node after parseSyntheticCode() is SCRIPT node, we need to get rid of it. |
| 212 return compiler.parseSyntheticCode(code).removeFirstChild(); | 246 return compiler.parseSyntheticCode(code).removeFirstChild(); |
| 213 } | 247 } |
| 214 | 248 |
| 215 private class RenameInternalsToExternalsCallback extends AbstractPostOrderCa llback { | 249 private class RenameInternalsToExternalsCallback extends AbstractPostOrderCa llback { |
| 216 final private String namespaceName; | 250 final private String namespaceName; |
| 217 final private Map<String, String> exports; | 251 final private Map<String, String> exports; |
| 218 final private Node namespaceBlock; | 252 final private Node namespaceBlock; |
| 219 | 253 |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 296 } | 330 } |
| 297 | 331 |
| 298 private Node buildQualifiedName(Node internalName) { | 332 private Node buildQualifiedName(Node internalName) { |
| 299 String externalName = this.exports.get(internalName.getString()); | 333 String externalName = this.exports.get(internalName.getString()); |
| 300 return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), | 334 return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), |
| 301 this.namespaceName + "." + externalName).srcrefTree(internal Name); | 335 this.namespaceName + "." + externalName).srcrefTree(internal Name); |
| 302 } | 336 } |
| 303 } | 337 } |
| 304 | 338 |
| 305 } | 339 } |
| OLD | NEW |