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 |