Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(313)

Side by Side Diff: third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java

Issue 2629323002: Remove Chromium's custom closure runner. (Closed)
Patch Set: merge Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package com.google.javascript.jscomp;
6
7 import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
8 import com.google.javascript.rhino.IR;
9 import com.google.javascript.rhino.JSDocInfoBuilder;
10 import com.google.javascript.rhino.JSTypeExpression;
11 import com.google.javascript.rhino.Node;
12 import com.google.javascript.rhino.Token;
13
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21
22 /**
23 * Compiler pass for Chrome-specific needs. It handles the following Chrome JS f eatures:
24 * <ul>
25 * <li>namespace declaration using {@code cr.define()},
26 * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}.
27 * </ul>
28 *
29 * <p>For the details, see tests inside ChromePassTest.java.
30 */
31 public class ChromePass extends AbstractPostOrderCallback implements CompilerPas s {
32 final AbstractCompiler compiler;
33
34 private Set<String> createdObjects;
35
36 private static final String CR_DEFINE = "cr.define";
37 private static final String CR_EXPORT_PATH = "cr.exportPath";
38 private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty" ;
39 private static final String CR_DEFINE_PROPERTY = "cr.defineProperty";
40 private static final String CR_MAKE_PUBLIC = "cr.makePublic";
41
42 private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be cal led like this:"
43 + " cr.define('name.space', function() '{ ... return {Export: Intern al}; }');";
44
45 static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS =
46 DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS",
47 "cr.define() should have exactly 2 arguments. " + CR_DEFINE_ COMMON_EXPLANATION);
48
49 static final DiagnosticType CR_EXPORT_PATH_TOO_FEW_ARGUMENTS =
50 DiagnosticType.error("JSC_CR_EXPORT_PATH_TOO_FEW_ARGUMENTS",
51 "cr.exportPath() should have at least 1 argument: path name. ");
52
53 static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT =
54 DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT",
55 "Invalid first argument for cr.define(). " + CR_DEFINE_COMMO N_EXPLANATION);
56
57 static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT =
58 DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT",
59 "Invalid second argument for cr.define(). " + CR_DEFINE_COMM ON_EXPLANATION);
60
61 static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION =
62 DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMEN T",
63 "Function passed as second argument of cr.define() should re turn the"
64 + " dictionary in its last statement. " + CR_DEFINE_COMMON_E XPLANATION);
65
66 static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND =
67 DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND",
68 "Invalid cr.PropertyKind passed to cr.defineProperty(): expe cted ATTR,"
69 + " BOOL_ATTR or JS, found \"{0}\".");
70
71 static final DiagnosticType CR_MAKE_PUBLIC_HAS_NO_JSDOC =
72 DiagnosticType.error("JSC_CR_MAKE_PUBLIC_HAS_NO_JSDOC",
73 "Private method exported by cr.makePublic() has no JSDoc.");
74
75 static final DiagnosticType CR_MAKE_PUBLIC_MISSED_DECLARATION =
76 DiagnosticType.error("JSC_CR_MAKE_PUBLIC_MISSED_DECLARATION",
77 "Method \"{1}_\" exported by cr.makePublic() on \"{0}\" has no declaration.");
78
79 static final DiagnosticType CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT =
80 DiagnosticType.error("JSC_CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT",
81 "Invalid second argument passed to cr.makePublic(): should b e array of " +
82 "strings.");
83
84 public ChromePass(AbstractCompiler compiler) {
85 this.compiler = compiler;
86 // The global variable "cr" is declared in ui/webui/resources/js/cr.js.
87 this.createdObjects = new HashSet<>(Arrays.asList("cr"));
88 }
89
90 @Override
91 public void process(Node externs, Node root) {
92 NodeTraversal.traverse(compiler, root, this);
93 }
94
95 @Override
96 public void visit(NodeTraversal t, Node node, Node parent) {
97 if (node.isCall()) {
98 Node callee = node.getFirstChild();
99 if (callee.matchesQualifiedName(CR_DEFINE)) {
100 visitNamespaceDefinition(node, parent);
101 compiler.reportCodeChange();
102 } else if (callee.matchesQualifiedName(CR_EXPORT_PATH)) {
103 visitExportPath(node, parent);
104 compiler.reportCodeChange();
105 } else if (callee.matchesQualifiedName(OBJECT_DEFINE_PROPERTY) ||
106 callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
107 visitPropertyDefinition(node, parent);
108 compiler.reportCodeChange();
109 } else if (callee.matchesQualifiedName(CR_MAKE_PUBLIC)) {
110 if (visitMakePublic(node, parent)) {
111 compiler.reportCodeChange();
112 }
113 }
114 }
115 }
116
117 private void visitPropertyDefinition(Node call, Node parent) {
118 Node callee = call.getFirstChild();
119 String target = call.getChildAtIndex(1).getQualifiedName();
120 if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY) && !target.endsWith( ".prototype")) {
121 target += ".prototype";
122 }
123
124 Node property = call.getChildAtIndex(2);
125
126 Node getPropNode = NodeUtil.newQName(
127 compiler, target + "." + property.getString()).srcrefTree(call);
128
129 if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
130 setJsDocWithType(getPropNode, getTypeByCrPropertyKind(call.getChildA tIndex(3)));
131 } else {
132 setJsDocWithType(getPropNode, new Node(Token.QMARK));
133 }
134
135 Node definitionNode = IR.exprResult(getPropNode).srcref(parent);
136
137 parent.getParent().addChildAfter(definitionNode, parent);
138 }
139
140 private Node getTypeByCrPropertyKind(Node propertyKind) {
141 if (propertyKind == null || propertyKind.matchesQualifiedName("cr.Proper tyKind.JS")) {
142 return new Node(Token.QMARK);
143 }
144 if (propertyKind.matchesQualifiedName("cr.PropertyKind.ATTR")) {
145 return IR.string("string");
146 }
147 if (propertyKind.matchesQualifiedName("cr.PropertyKind.BOOL_ATTR")) {
148 return IR.string("boolean");
149 }
150 compiler.report(JSError.make(propertyKind, CR_DEFINE_PROPERTY_INVALID_PR OPERTY_KIND,
151 propertyKind.getQualifiedName()));
152 return null;
153 }
154
155 private void setJsDocWithType(Node target, Node type) {
156 JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
157 builder.recordType(new JSTypeExpression(type, ""));
158 target.setJSDocInfo(builder.build());
159 }
160
161 private boolean visitMakePublic(Node call, Node exprResult) {
162 boolean changesMade = false;
163 Node scope = exprResult.getParent();
164 String className = call.getChildAtIndex(1).getQualifiedName();
165 String prototype = className + ".prototype";
166 Node methods = call.getChildAtIndex(2);
167
168 if (methods == null || !methods.isArrayLit()) {
169 compiler.report(JSError.make(exprResult, CR_MAKE_PUBLIC_INVALID_SECO ND_ARGUMENT));
170 return changesMade;
171 }
172
173 Set<String> methodNames = new HashSet<>();
174 for (Node methodName: methods.children()) {
175 if (!methodName.isString()) {
176 compiler.report(JSError.make(methodName, CR_MAKE_PUBLIC_INVALID_ SECOND_ARGUMENT));
177 return changesMade;
178 }
179 methodNames.add(methodName.getString());
180 }
181
182 for (Node child: scope.children()) {
183 if (isAssignmentToPrototype(child, prototype)) {
184 Node objectLit = child.getFirstChild().getChildAtIndex(1);
185 for (Node stringKey : objectLit.children()) {
186 String field = stringKey.getString();
187 changesMade |= maybeAddPublicDeclaration(field, methodNames, className,
188 stringKey, scope, e xprResult);
189 }
190 } else if (isAssignmentToPrototypeMethod(child, prototype)) {
191 Node assignNode = child.getFirstChild();
192 String qualifiedName = assignNode.getFirstChild().getQualifiedNa me();
193 String field = qualifiedName.substring(qualifiedName.lastIndexOf ('.') + 1);
194 changesMade |= maybeAddPublicDeclaration(field, methodNames, cla ssName,
195 assignNode, scope, expr Result);
196 } else if (isDummyPrototypeMethodDeclaration(child, prototype)) {
197 String qualifiedName = child.getFirstChild().getQualifiedName();
198 String field = qualifiedName.substring(qualifiedName.lastIndexOf ('.') + 1);
199 changesMade |= maybeAddPublicDeclaration(field, methodNames, cla ssName,
200 child.getFirstChild(), scope, exprResult);
201 }
202 }
203
204 for (String missedDeclaration : methodNames) {
205 compiler.report(JSError.make(exprResult, CR_MAKE_PUBLIC_MISSED_DECLA RATION, className,
206 missedDeclaration));
207 }
208
209 return changesMade;
210 }
211
212 private boolean isAssignmentToPrototype(Node node, String prototype) {
213 Node assignNode;
214 return node.isExprResult() && (assignNode = node.getFirstChild()).isAssi gn() &&
215 assignNode.getFirstChild().getQualifiedName().equals(prototype);
216 }
217
218 private boolean isAssignmentToPrototypeMethod(Node node, String prototype) {
219 Node assignNode;
220 return node.isExprResult() && (assignNode = node.getFirstChild()).isAssi gn() &&
221 assignNode.getFirstChild().getQualifiedName().startsWith(prototy pe + ".");
222 }
223
224 private boolean isDummyPrototypeMethodDeclaration(Node node, String prototyp e) {
225 Node getPropNode;
226 return node.isExprResult() && (getPropNode = node.getFirstChild()).isGet Prop() &&
227 getPropNode.getQualifiedName().startsWith(prototype + ".");
228 }
229
230 private boolean maybeAddPublicDeclaration(String field, Set<String> publicAP IStrings,
231 String className, Node jsDocSourceNode, Node scope, Node exprResult) {
232 boolean changesMade = false;
233 if (field.endsWith("_")) {
234 String publicName = field.substring(0, field.length() - 1);
235 if (publicAPIStrings.contains(publicName)) {
236 Node methodDeclaration = NodeUtil.newQName(compiler, className + "." + publicName);
237 if (jsDocSourceNode.getJSDocInfo() != null) {
238 methodDeclaration.setJSDocInfo(jsDocSourceNode.getJSDocInfo( ));
239 scope.addChildBefore(
240 IR.exprResult(methodDeclaration).srcrefTree(exprResu lt),
241 exprResult);
242 changesMade = true;
243 } else {
244 compiler.report(JSError.make(jsDocSourceNode, CR_MAKE_PUBLIC _HAS_NO_JSDOC));
245 }
246 publicAPIStrings.remove(publicName);
247 }
248 }
249 return changesMade;
250 }
251
252 private void visitExportPath(Node crExportPathNode, Node parent) {
253 if (crExportPathNode.getChildCount() < 2) {
254 compiler.report(JSError.make(crExportPathNode, CR_EXPORT_PATH_TOO_FE W_ARGUMENTS));
255 return;
256 }
257
258 Node pathArg = crExportPathNode.getChildAtIndex(1);
259 if (pathArg.isString()) {
260 // TODO(dbeam): support cr.exportPath('ns').value.
261 createAndInsertObjectsForQualifiedName(parent, pathArg.getString());
262 }
263 }
264
265 private void createAndInsertObjectsForQualifiedName(Node scriptChild, String namespace) {
266 List<Node> objectsForQualifiedName = createObjectsForQualifiedName(names pace);
267 for (Node n : objectsForQualifiedName) {
268 scriptChild.getParent().addChildBefore(n, scriptChild);
269 }
270 }
271
272 private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) {
273 if (crDefineCallNode.getChildCount() != 3) {
274 compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBE R_OF_ARGUMENTS));
275 }
276
277 Node namespaceArg = crDefineCallNode.getChildAtIndex(1);
278 Node function = crDefineCallNode.getChildAtIndex(2);
279
280 if (!namespaceArg.isString()) {
281 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_A RGUMENT));
282 return;
283 }
284
285 // TODO(vitalyp): Check namespace name for validity here. It should be a valid chain of
286 // identifiers.
287 String namespace = namespaceArg.getString();
288
289 createAndInsertObjectsForQualifiedName(parent, namespace);
290
291 if (!function.isFunction()) {
292 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_ ARGUMENT));
293 return;
294 }
295
296 Node returnNode, objectLit;
297 Node functionBlock = function.getLastChild();
298 if ((returnNode = functionBlock.getLastChild()) == null ||
299 !returnNode.isReturn() ||
300 (objectLit = returnNode.getFirstChild()) == null ||
301 !objectLit.isObjectLit()) {
302 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_RETURN_ IN_FUNCTION));
303 return;
304 }
305
306 Map<String, String> exports = objectLitToMap(objectLit);
307
308 NodeTraversal.traverse(compiler, functionBlock, new RenameInternalsToExt ernalsCallback(
309 namespace, exports, functionBlock));
310 }
311
312 private Map<String, String> objectLitToMap(Node objectLit) {
313 Map<String, String> res = new HashMap<String, String>();
314
315 for (Node keyNode : objectLit.children()) {
316 String key = keyNode.getString();
317
318 Node valueNode = keyNode.getFirstChild();
319 if (valueNode.isName()) {
320 String value = keyNode.getFirstChild().getString();
321 res.put(value, key);
322 }
323 }
324
325 return res;
326 }
327
328 /**
329 * For a string "a.b.c" produce the following JS IR:
330 *
331 * <p><pre>
332 * var a = a || {};
333 * a.b = a.b || {};
334 * a.b.c = a.b.c || {};</pre>
335 */
336 private List<Node> createObjectsForQualifiedName(String namespace) {
337 List<Node> objects = new ArrayList<>();
338 String[] parts = namespace.split("\\.");
339
340 createObjectIfNew(objects, parts[0], true);
341
342 if (parts.length >= 2) {
343 StringBuilder currPrefix = new StringBuilder().append(parts[0]);
344 for (int i = 1; i < parts.length; ++i) {
345 currPrefix.append(".").append(parts[i]);
346 createObjectIfNew(objects, currPrefix.toString(), false);
347 }
348 }
349
350 return objects;
351 }
352
353 private void createObjectIfNew(List<Node> objects, String name, boolean need Var) {
354 if (!createdObjects.contains(name)) {
355 objects.add(createJsNode((needVar ? "var " : "") + name + " = " + na me + " || {};"));
356 createdObjects.add(name);
357 }
358 }
359
360 private Node createJsNode(String code) {
361 // The parent node after parseSyntheticCode() is SCRIPT node, we need to get rid of it.
362 return compiler.parseSyntheticCode(code).removeFirstChild();
363 }
364
365 private class RenameInternalsToExternalsCallback extends AbstractPostOrderCa llback {
366 private final String namespaceName;
367 private final Map<String, String> exports;
368 private final Node namespaceBlock;
369
370 public RenameInternalsToExternalsCallback(String namespaceName,
371 Map<String, String> exports, Node namespaceBlock) {
372 this.namespaceName = namespaceName;
373 this.exports = exports;
374 this.namespaceBlock = namespaceBlock;
375 }
376
377 @Override
378 public void visit(NodeTraversal t, Node n, Node parent) {
379 if (n.isFunction() && parent == this.namespaceBlock &&
380 this.exports.containsKey(n.getFirstChild().getString())) {
381 // It's a top-level function/constructor definition.
382 //
383 // Change
384 //
385 // /** Some doc */
386 // function internalName() {}
387 //
388 // to
389 //
390 // /** Some doc */
391 // my.namespace.name.externalName = function internalName() {} ;
392 //
393 // by looking up in this.exports for internalName to find the co rrespondent
394 // externalName.
395 Node functionTree = n.cloneTree();
396 Node exprResult = IR.exprResult(
397 IR.assign(buildQualifiedName(n.getFirstChild()), fun ctionTree).srcref(n)
398 ).srcref(n);
399
400 if (n.getJSDocInfo() != null) {
401 exprResult.getFirstChild().setJSDocInfo(n.getJSDocInfo());
402 functionTree.removeProp(Node.JSDOC_INFO_PROP);
403 }
404 this.namespaceBlock.replaceChild(n, exprResult);
405 } else if (n.isName() && this.exports.containsKey(n.getString()) &&
406 !parent.isFunction()) {
407 if (parent.isVar()) {
408 if (parent.getParent() == this.namespaceBlock) {
409 // It's a top-level exported variable definition (maybe without an
410 // assignment).
411 // Change
412 //
413 // var enum = { 'one': 1, 'two': 2 };
414 //
415 // to
416 //
417 // my.namespace.name.enum = { 'one': 1, 'two': 2 };
418 Node varContent = n.removeFirstChild();
419 Node exprResult;
420 if (varContent == null) {
421 exprResult = IR.exprResult(buildQualifiedName(n)).sr cref(parent);
422 } else {
423 exprResult = IR.exprResult(
424 IR.assign(buildQualifiedName(n), varCont ent).srcref(parent)
425 ).srcref(parent);
426 }
427 if (parent.getJSDocInfo() != null) {
428 exprResult.getFirstChild().setJSDocInfo(parent.getJS DocInfo().clone());
429 }
430 this.namespaceBlock.replaceChild(parent, exprResult);
431 }
432 } else {
433 // It's a local name referencing exported entity. Change to its global name.
434 Node newNode = buildQualifiedName(n);
435 if (n.getJSDocInfo() != null) {
436 newNode.setJSDocInfo(n.getJSDocInfo().clone());
437 }
438
439 // If we alter the name of a called function, then it gets a n explicit "this"
440 // value.
441 if (parent.isCall()) {
442 parent.putBooleanProp(Node.FREE_CALL, false);
443 }
444
445 parent.replaceChild(n, newNode);
446 }
447 }
448 }
449
450 private Node buildQualifiedName(Node internalName) {
451 String externalName = this.exports.get(internalName.getString());
452 return NodeUtil.newQName(compiler, this.namespaceName + "." + extern alName).srcrefTree(
453 internalName);
454 }
455 }
456 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698