| 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.Arrays; |
| 15 import java.util.HashMap; | 16 import java.util.HashMap; |
| 17 import java.util.HashSet; |
| 16 import java.util.List; | 18 import java.util.List; |
| 17 import java.util.Map; | 19 import java.util.Map; |
| 20 import java.util.Set; |
| 18 | 21 |
| 19 /** | 22 /** |
| 20 * Compiler pass for Chrome-specific needs. It handles the following Chrome JS f
eatures: | 23 * Compiler pass for Chrome-specific needs. It handles the following Chrome JS f
eatures: |
| 21 * <ul> | 24 * <ul> |
| 22 * <li>namespace declaration using {@code cr.define()}, | 25 * <li>namespace declaration using {@code cr.define()}, |
| 23 * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}. | 26 * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}. |
| 24 * </ul> | 27 * </ul> |
| 25 * | 28 * |
| 26 * <p>For the details, see tests inside ChromePassTest.java. | 29 * <p>For the details, see tests inside ChromePassTest.java. |
| 27 */ | 30 */ |
| 28 public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
s { | 31 public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
s { |
| 29 final AbstractCompiler compiler; | 32 final AbstractCompiler compiler; |
| 30 | 33 |
| 34 private Set<String> createdObjects; |
| 35 |
| 31 private static final String CR_DEFINE = "cr.define"; | 36 private static final String CR_DEFINE = "cr.define"; |
| 32 | 37 private static final String CR_EXPORT_PATH = "cr.exportPath"; |
| 33 private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty"
; | 38 private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty"
; |
| 34 | |
| 35 private static final String CR_DEFINE_PROPERTY = "cr.defineProperty"; | 39 private static final String CR_DEFINE_PROPERTY = "cr.defineProperty"; |
| 36 | 40 |
| 37 private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be cal
led like this:" | 41 private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be cal
led like this:" |
| 38 + " cr.define('name.space', function() '{ ... return {Export: Intern
al}; }');"; | 42 + " cr.define('name.space', function() '{ ... return {Export: Intern
al}; }');"; |
| 39 | 43 |
| 40 static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS = | 44 static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS = |
| 41 DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS", | 45 DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS", |
| 42 "cr.define() should have exactly 2 arguments. " + CR_DEFINE_
COMMON_EXPLANATION); | 46 "cr.define() should have exactly 2 arguments. " + CR_DEFINE_
COMMON_EXPLANATION); |
| 43 | 47 |
| 48 static final DiagnosticType CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS = |
| 49 DiagnosticType.error("JSC_CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS", |
| 50 "cr.exportPath() should have exactly 1 argument: namespace n
ame."); |
| 51 |
| 44 static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT = | 52 static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT = |
| 45 DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT", | 53 DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT", |
| 46 "Invalid first argument for cr.define(). " + CR_DEFINE_COMMO
N_EXPLANATION); | 54 "Invalid first argument for cr.define(). " + CR_DEFINE_COMMO
N_EXPLANATION); |
| 47 | 55 |
| 48 static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT = | 56 static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT = |
| 49 DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT", | 57 DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT", |
| 50 "Invalid second argument for cr.define(). " + CR_DEFINE_COMM
ON_EXPLANATION); | 58 "Invalid second argument for cr.define(). " + CR_DEFINE_COMM
ON_EXPLANATION); |
| 51 | 59 |
| 52 static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION = | 60 static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION = |
| 53 DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMEN
T", | 61 DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMEN
T", |
| 54 "Function passed as second argument of cr.define() should re
turn the" | 62 "Function passed as second argument of cr.define() should re
turn the" |
| 55 + " dictionary in its last statement. " + CR_DEFINE_COMMON_E
XPLANATION); | 63 + " dictionary in its last statement. " + CR_DEFINE_COMMON_E
XPLANATION); |
| 56 | 64 |
| 57 static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND = | 65 static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND = |
| 58 DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND", | 66 DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND", |
| 59 "Invalid cr.PropertyKind passed to cr.defineProperty(): expe
cted ATTR," | 67 "Invalid cr.PropertyKind passed to cr.defineProperty(): expe
cted ATTR," |
| 60 + " BOOL_ATTR or JS, found \"{0}\"."); | 68 + " BOOL_ATTR or JS, found \"{0}\"."); |
| 61 | 69 |
| 62 public ChromePass(AbstractCompiler compiler) { | 70 public ChromePass(AbstractCompiler compiler) { |
| 63 this.compiler = compiler; | 71 this.compiler = compiler; |
| 72 // The global variable "cr" is declared in ui/webui/resources/js/cr.js. |
| 73 this.createdObjects = new HashSet<>(Arrays.asList("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 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 120 propertyKind.getQualifiedName())); | 133 propertyKind.getQualifiedName())); |
| 121 return null; | 134 return null; |
| 122 } | 135 } |
| 123 | 136 |
| 124 private void setJsDocWithType(Node target, Node type) { | 137 private void setJsDocWithType(Node target, Node type) { |
| 125 JSDocInfoBuilder builder = new JSDocInfoBuilder(false); | 138 JSDocInfoBuilder builder = new JSDocInfoBuilder(false); |
| 126 builder.recordType(new JSTypeExpression(type, "")); | 139 builder.recordType(new JSTypeExpression(type, "")); |
| 127 target.setJSDocInfo(builder.build(target)); | 140 target.setJSDocInfo(builder.build(target)); |
| 128 } | 141 } |
| 129 | 142 |
| 143 private void visitExportPath(Node crExportPathNode, Node parent) { |
| 144 if (crExportPathNode.getChildCount() != 2) { |
| 145 compiler.report(JSError.make(crExportPathNode, |
| 146 CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS)); |
| 147 return; |
| 148 } |
| 149 |
| 150 createAndInsertObjectsForQualifiedName(parent, |
| 151 crExportPathNode.getChildAtIndex(1).getString()); |
| 152 } |
| 153 |
| 154 private void createAndInsertObjectsForQualifiedName(Node scriptChild, String
namespace) { |
| 155 List<Node> objectsForQualifiedName = createObjectsForQualifiedName(names
pace); |
| 156 for (Node n : objectsForQualifiedName) { |
| 157 scriptChild.getParent().addChildBefore(n, scriptChild); |
| 158 } |
| 159 } |
| 160 |
| 130 private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) { | 161 private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) { |
| 131 if (crDefineCallNode.getChildCount() != 3) { | 162 if (crDefineCallNode.getChildCount() != 3) { |
| 132 compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBE
R_OF_ARGUMENTS)); | 163 compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBE
R_OF_ARGUMENTS)); |
| 133 } | 164 } |
| 134 | 165 |
| 135 Node namespaceArg = crDefineCallNode.getChildAtIndex(1); | 166 Node namespaceArg = crDefineCallNode.getChildAtIndex(1); |
| 136 Node function = crDefineCallNode.getChildAtIndex(2); | 167 Node function = crDefineCallNode.getChildAtIndex(2); |
| 137 | 168 |
| 138 if (!namespaceArg.isString()) { | 169 if (!namespaceArg.isString()) { |
| 139 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_A
RGUMENT)); | 170 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_A
RGUMENT)); |
| 140 return; | 171 return; |
| 141 } | 172 } |
| 142 | 173 |
| 143 // TODO(vitalyp): Check namespace name for validity here. It should be a
valid chain of | 174 // TODO(vitalyp): Check namespace name for validity here. It should be a
valid chain of |
| 144 // identifiers. | 175 // identifiers. |
| 145 String namespace = namespaceArg.getString(); | 176 String namespace = namespaceArg.getString(); |
| 146 | 177 |
| 147 List<Node> objectsForQualifiedName = createObjectsForQualifiedName(names
pace); | 178 createAndInsertObjectsForQualifiedName(parent, namespace); |
| 148 for (Node n : objectsForQualifiedName) { | |
| 149 parent.getParent().addChildBefore(n, parent); | |
| 150 } | |
| 151 | 179 |
| 152 if (!function.isFunction()) { | 180 if (!function.isFunction()) { |
| 153 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_
ARGUMENT)); | 181 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_
ARGUMENT)); |
| 154 return; | 182 return; |
| 155 } | 183 } |
| 156 | 184 |
| 157 Node returnNode, objectLit; | 185 Node returnNode, objectLit; |
| 158 Node functionBlock = function.getLastChild(); | 186 Node functionBlock = function.getLastChild(); |
| 159 if ((returnNode = functionBlock.getLastChild()) == null || | 187 if ((returnNode = functionBlock.getLastChild()) == null || |
| 160 !returnNode.isReturn() || | 188 !returnNode.isReturn() || |
| (...skipping 30 matching lines...) Expand all Loading... |
| 191 * | 219 * |
| 192 * <p><pre> | 220 * <p><pre> |
| 193 * var a = a || {}; | 221 * var a = a || {}; |
| 194 * a.b = a.b || {}; | 222 * a.b = a.b || {}; |
| 195 * a.b.c = a.b.c || {};</pre> | 223 * a.b.c = a.b.c || {};</pre> |
| 196 */ | 224 */ |
| 197 private List<Node> createObjectsForQualifiedName(String namespace) { | 225 private List<Node> createObjectsForQualifiedName(String namespace) { |
| 198 List<Node> objects = new ArrayList<>(); | 226 List<Node> objects = new ArrayList<>(); |
| 199 String[] parts = namespace.split("\\."); | 227 String[] parts = namespace.split("\\."); |
| 200 | 228 |
| 201 objects.add(createJsNode("var " + parts[0] + " = " + parts[0] + " || {};
")); | 229 createObjectIfNew(objects, parts[0], true); |
| 202 | 230 |
| 203 if (parts.length >= 2) { | 231 if (parts.length >= 2) { |
| 204 StringBuilder currPrefix = new StringBuilder().append(parts[0]); | 232 StringBuilder currPrefix = new StringBuilder().append(parts[0]); |
| 205 for (int i = 1; i < parts.length; ++i) { | 233 for (int i = 1; i < parts.length; ++i) { |
| 206 currPrefix.append(".").append(parts[i]); | 234 currPrefix.append(".").append(parts[i]); |
| 207 String code = currPrefix + " = " + currPrefix + " || {};"; | 235 createObjectIfNew(objects, currPrefix.toString(), false); |
| 208 objects.add(createJsNode(code)); | |
| 209 } | 236 } |
| 210 } | 237 } |
| 211 | 238 |
| 212 return objects; | 239 return objects; |
| 213 } | 240 } |
| 214 | 241 |
| 242 private void createObjectIfNew(List<Node> objects, String name, boolean need
Var) { |
| 243 if (!createdObjects.contains(name)) { |
| 244 objects.add(createJsNode((needVar ? "var " : "") + name + " = " + na
me + " || {};")); |
| 245 createdObjects.add(name); |
| 246 } |
| 247 } |
| 248 |
| 215 private Node createJsNode(String code) { | 249 private Node createJsNode(String code) { |
| 216 // The parent node after parseSyntheticCode() is SCRIPT node, we need to
get rid of it. | 250 // The parent node after parseSyntheticCode() is SCRIPT node, we need to
get rid of it. |
| 217 return compiler.parseSyntheticCode(code).removeFirstChild(); | 251 return compiler.parseSyntheticCode(code).removeFirstChild(); |
| 218 } | 252 } |
| 219 | 253 |
| 220 private class RenameInternalsToExternalsCallback extends AbstractPostOrderCa
llback { | 254 private class RenameInternalsToExternalsCallback extends AbstractPostOrderCa
llback { |
| 221 private final String namespaceName; | 255 private final String namespaceName; |
| 222 private final Map<String, String> exports; | 256 private final Map<String, String> exports; |
| 223 private final Node namespaceBlock; | 257 private final Node namespaceBlock; |
| 224 | 258 |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 298 } | 332 } |
| 299 | 333 |
| 300 private Node buildQualifiedName(Node internalName) { | 334 private Node buildQualifiedName(Node internalName) { |
| 301 String externalName = this.exports.get(internalName.getString()); | 335 String externalName = this.exports.get(internalName.getString()); |
| 302 return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), | 336 return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), |
| 303 this.namespaceName + "." + externalName).srcrefTree(internal
Name); | 337 this.namespaceName + "." + externalName).srcrefTree(internal
Name); |
| 304 } | 338 } |
| 305 } | 339 } |
| 306 | 340 |
| 307 } | 341 } |
| OLD | NEW |