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

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

Issue 460163002: Handle property definition by {cr|Object}.defineProperty() in compiler pass (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@C_compiler_pass
Patch Set: define property on prototype, also minor nits Created 6 years, 4 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
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;
10 import com.google.javascript.rhino.JSTypeExpression;
9 import com.google.javascript.rhino.Node; 11 import com.google.javascript.rhino.Node;
12 import com.google.javascript.rhino.Token;
10 13
11 import java.util.ArrayList; 14 import java.util.ArrayList;
12 import java.util.HashMap; 15 import java.util.HashMap;
13 import java.util.List; 16 import java.util.List;
14 import java.util.Map; 17 import java.util.Map;
15 18
16 /** 19 /**
17 * Compiler pass for Chrome-specific needs. Right now it allows the compiler to check types with 20 * Compiler pass for Chrome-specific needs. It handles the following Chrome JS f eatures:
18 * methods defined inside Chrome namespaces. 21 * <ul>
22 * <li>namespace declaration using {@code cr.define()},
23 * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}.
24 * </ul>
19 * 25 *
20 * <p>The namespaces in Chrome JS are declared as follows: 26 * <p>For the details, see tests inside ChromePassTest.java.
21 * <pre>
22 * cr.define('my.namespace.name', function() {
23 * /** @param {number} arg
24 * function internalStaticMethod(arg) {}
25 *
26 * /** @constructor
27 * function InternalClass() {}
28 *
29 * InternalClass.prototype = {
30 * /** @param {number} arg
31 * method: function(arg) {
32 * internalStaticMethod(arg); // let's demonstrate the change of local na mes after our pass
33 * }
34 * };
35 *
36 * return {
37 * externalStaticMethod: internalStaticMethod,
38 * ExternalClass: InternalClass
39 * }
40 * });
41 * </pre>
42 *
43 * <p>Outside of cr.define() statement the function can be called like this:
44 * {@code my.namespace.name.externalStaticMethod(42);}.
45 *
46 * <p>We need to check the types of arguments and return values of such function s. However, the
47 * function is assigned to its namespace dictionary only at run-time and the ori ginal Closure
48 * Compiler isn't smart enough to predict behavior in that case. Therefore, we n eed to modify the
49 * AST before any compiler checks. That's how we modify it to tell the compiler what's going on:
50 *
51 * <pre>
52 * var my = my || {};
53 * my.namespace = my.namespace || {};
54 * my.namespace.name = my.namespace.name || {};
55 *
56 * cr.define('my.namespace.name', function() {
57 * /** @param {number} arg
58 * my.namespace.name.externalStaticMethod(arg) {}
59 *
60 * /** @constructor
61 * my.namespace.name.ExternalClass = function() {};
62 *
63 * my.namespace.name.ExternalClass.prototype = {
64 * /** @param {number} arg
65 * method: function(arg) {
66 * my.namespace.name.externalStaticMethod(arg); // see, it has been chang ed
67 * }
68 * };
69 *
70 * return {
71 * externalStaticMethod: my.namespace.name.externalStaticMethod,
72 * ExternalClass: my.namespace.name.ExternalClass
73 * }
74 * });
75 * </pre>
76 */ 27 */
77 public class ChromePass extends AbstractPostOrderCallback implements CompilerPas s { 28 public class ChromePass extends AbstractPostOrderCallback implements CompilerPas s {
78 final AbstractCompiler compiler; 29 final AbstractCompiler compiler;
79 30
80 private static final String CR_DEFINE = "cr.define"; 31 private static final String CR_DEFINE = "cr.define";
81 32
82 private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be cal led like this: " + 33 private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty" ;
83 "cr.define('name.space', function() '{ ... return {Export: Internal} ; }');"; 34
35 private static final String CR_DEFINE_PROPERTY = "cr.defineProperty";
36
37 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}; }');";
84 39
85 static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS = 40 static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS =
86 DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS", 41 DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS",
87 "cr.define() should have exactly 2 arguments. " + CR_DEFINE_ COMMON_EXPLANATION); 42 "cr.define() should have exactly 2 arguments. " + CR_DEFINE_ COMMON_EXPLANATION);
88 43
89 static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT = 44 static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT =
90 DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT", 45 DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT",
91 "Invalid first argument for cr.define(). " + CR_DEFINE_COMMO N_EXPLANATION); 46 "Invalid first argument for cr.define(). " + CR_DEFINE_COMMO N_EXPLANATION);
92 47
93 static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT = 48 static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT =
94 DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT", 49 DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT",
95 "Invalid second argument for cr.define(). " + CR_DEFINE_COMM ON_EXPLANATION); 50 "Invalid second argument for cr.define(). " + CR_DEFINE_COMM ON_EXPLANATION);
96 51
97 static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION = 52 static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION =
98 DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMEN T", 53 DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMEN T",
99 "Function passed as second argument of cr.define() should re turn the " + 54 "Function passed as second argument of cr.define() should re turn the"
100 "dictionary in its last statement. " + CR_DEFINE_COMMON_EXPL ANATION); 55 + " dictionary in its last statement. " + CR_DEFINE_COMMON_E XPLANATION);
56
57 static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND =
58 DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND",
59 "Invalid cr.PropertyKind passed to cr.defineProperty(): expe cted ATTR,"
60 + " BOOL_ATTR or JS, found \"{0}\".");
101 61
102 public ChromePass(AbstractCompiler compiler) { 62 public ChromePass(AbstractCompiler compiler) {
103 this.compiler = compiler; 63 this.compiler = compiler;
104 } 64 }
105 65
106 @Override 66 @Override
107 public void process(Node externs, Node root) { 67 public void process(Node externs, Node root) {
108 NodeTraversal.traverse(compiler, root, this); 68 NodeTraversal.traverse(compiler, root, this);
109 } 69 }
110 70
111 @Override 71 @Override
112 public void visit(NodeTraversal t, Node node, Node parent) { 72 public void visit(NodeTraversal t, Node node, Node parent) {
113 if (node.isCall()) { 73 if (node.isCall()) {
114 Node callee = node.getFirstChild(); 74 Node callee = node.getFirstChild();
115 if (callee.matchesQualifiedName(CR_DEFINE)) { 75 if (callee.matchesQualifiedName(CR_DEFINE)) {
116 visitNamespaceDefinition(node, parent); 76 visitNamespaceDefinition(node, parent);
117 compiler.reportCodeChange(); 77 compiler.reportCodeChange();
78 } else if (callee.matchesQualifiedName(OBJECT_DEFINE_PROPERTY) ||
79 callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
80 visitPropertyDefinition(node, parent);
81 compiler.reportCodeChange();
118 } 82 }
119 } 83 }
120 } 84 }
121 85
86 private void visitPropertyDefinition(Node call, Node parent) {
87 Node callee = call.getFirstChild();
88 String target = call.getChildAtIndex(1).getQualifiedName();
89 if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY) && !target.endsWith( ".prototype")) {
90 target += ".prototype";
91 }
92
93 Node property = call.getChildAtIndex(2);
94
95 Node getPropNode = NodeUtil.newQualifiedNameNode(compiler.getCodingConve ntion(),
96 target + "." + property.getString()).srcrefTree(call);
97
98 if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
99 setJsDocWithType(getPropNode, getTypeByCrPropertyKind(call.getChildA tIndex(3)));
100 } else {
101 setJsDocWithType(getPropNode, new Node(Token.QMARK));
102 }
103
104 Node definitionNode = IR.exprResult(getPropNode).srcref(parent);
105
106 parent.getParent().addChildAfter(definitionNode, parent);
107 }
108
109 private Node getTypeByCrPropertyKind(Node propertyKind) {
110 if (propertyKind.matchesQualifiedName("cr.PropertyKind.ATTR")) {
111 return IR.string("string");
112 } else if (propertyKind.matchesQualifiedName("cr.PropertyKind.BOOL_ATTR" )) {
Dan Beam 2014/08/13 20:41:29 nit: no else after if + return, i.e. if (proper
Vitaly Pavlenko 2014/08/13 21:12:04 Done.
113 return IR.string("boolean");
114 } else if (propertyKind.matchesQualifiedName("cr.PropertyKind.JS")) {
115 return new Node(Token.QMARK);
116 } else {
117 compiler.report(JSError.make(propertyKind, CR_DEFINE_PROPERTY_INVALI D_PROPERTY_KIND,
118 propertyKind.getQualifiedName()));
119 return null;
120 }
121 }
122
123 private void setJsDocWithType(Node target, Node type) {
124 JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
125 builder.recordType(new JSTypeExpression(type, ""));
126 target.setJSDocInfo(builder.build(target));
127 }
128
122 private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) { 129 private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) {
123 if (crDefineCallNode.getChildCount() != 3) { 130 if (crDefineCallNode.getChildCount() != 3) {
124 compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBE R_OF_ARGUMENTS)); 131 compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBE R_OF_ARGUMENTS));
125 } 132 }
126 133
127 Node namespaceArg = crDefineCallNode.getChildAtIndex(1); 134 Node namespaceArg = crDefineCallNode.getChildAtIndex(1);
128 Node function = crDefineCallNode.getChildAtIndex(2); 135 Node function = crDefineCallNode.getChildAtIndex(2);
129 136
130 if (!namespaceArg.isString()) { 137 if (!namespaceArg.isString()) {
131 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_A RGUMENT)); 138 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_A RGUMENT));
132 return; 139 return;
133 } 140 }
134 141
135 // TODO(vitalyp): Check namespace name for validity here. It should be a valid chain of 142 // TODO(vitalyp): Check namespace name for validity here. It should be a valid chain of
136 // identifiers. 143 // identifiers.
137 String namespace = namespaceArg.getString(); 144 String namespace = namespaceArg.getString();
138 145
139 List<Node> objectsForQualifiedName = createObjectsForQualifiedName(names pace); 146 List<Node> objectsForQualifiedName = createObjectsForQualifiedName(names pace);
140 for (Node n : objectsForQualifiedName) { 147 for (Node n : objectsForQualifiedName) {
141 parent.getParent().addChildBefore(n, parent); 148 parent.getParent().addChildBefore(n, parent);
142 } 149 }
143 150
144 Node returnNode, functionBlock, objectLit;
145 if (!function.isFunction()) { 151 if (!function.isFunction()) {
146 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_ ARGUMENT)); 152 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_ ARGUMENT));
147 return; 153 return;
148 } 154 }
149 155
150 if ((functionBlock = function.getLastChild()) == null || 156 Node returnNode, objectLit;
151 (returnNode = functionBlock.getLastChild()) == null || 157 Node functionBlock = function.getLastChild();
158 if ((returnNode = functionBlock.getLastChild()) == null ||
152 !returnNode.isReturn() || 159 !returnNode.isReturn() ||
153 (objectLit = returnNode.getFirstChild()) == null || 160 (objectLit = returnNode.getFirstChild()) == null ||
154 !objectLit.isObjectLit()) { 161 !objectLit.isObjectLit()) {
155 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_RETURN_ IN_FUNCTION)); 162 compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_RETURN_ IN_FUNCTION));
156 return; 163 return;
157 } 164 }
158 165
159 Map<String, String> exports = objectLitToMap(objectLit); 166 Map<String, String> exports = objectLitToMap(objectLit);
160 167
161 NodeTraversal.traverse(compiler, functionBlock, new RenameInternalsToExt ernalsCallback( 168 NodeTraversal.traverse(compiler, functionBlock, new RenameInternalsToExt ernalsCallback(
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
216 223
217 public RenameInternalsToExternalsCallback(String namespaceName, 224 public RenameInternalsToExternalsCallback(String namespaceName,
218 Map<String, String> exports, Node namespaceBlock) { 225 Map<String, String> exports, Node namespaceBlock) {
219 this.namespaceName = namespaceName; 226 this.namespaceName = namespaceName;
220 this.exports = exports; 227 this.exports = exports;
221 this.namespaceBlock = namespaceBlock; 228 this.namespaceBlock = namespaceBlock;
222 } 229 }
223 230
224 @Override 231 @Override
225 public void visit(NodeTraversal t, Node n, Node parent) { 232 public void visit(NodeTraversal t, Node n, Node parent) {
226 if (n == null) {
227 return;
228 }
229
230 if (n.isFunction() && parent == this.namespaceBlock && 233 if (n.isFunction() && parent == this.namespaceBlock &&
231 this.exports.containsKey(n.getFirstChild().getString())) { 234 this.exports.containsKey(n.getFirstChild().getString())) {
232 // It's a top-level function/constructor definition. 235 // It's a top-level function/constructor definition.
233 // 236 //
234 // Change 237 // Change
235 // 238 //
236 // /** Some doc */ 239 // /** Some doc */
237 // function internalName() {} 240 // function internalName() {}
238 // 241 //
239 // to 242 // to
240 // 243 //
241 // /** Some doc */ 244 // /** Some doc */
242 // my.namespace.name.externalName = function internalName() {} ; 245 // my.namespace.name.externalName = function internalName() {} ;
243 // 246 //
244 // by looking up in this.exports for internalName to find the co rrespondent 247 // by looking up in this.exports for internalName to find the co rrespondent
245 // externalName. 248 // externalName.
246 Node functionTree = n.cloneTree(); 249 Node functionTree = n.cloneTree();
247 Node exprResult = IR.exprResult( 250 Node exprResult = IR.exprResult(
248 IR.assign(buildQualifiedName(n.getFirstChild()), functio nTree)); 251 IR.assign(buildQualifiedName(n.getFirstChild()), fun ctionTree).srcref(n)
249 NodeUtil.setDebugInformation(exprResult, n, n.getFirstChild().ge tString()); 252 ).srcref(n);
253
250 if (n.getJSDocInfo() != null) { 254 if (n.getJSDocInfo() != null) {
251 exprResult.getFirstChild().setJSDocInfo(n.getJSDocInfo()); 255 exprResult.getFirstChild().setJSDocInfo(n.getJSDocInfo());
252 functionTree.removeProp(Node.JSDOC_INFO_PROP); 256 functionTree.removeProp(Node.JSDOC_INFO_PROP);
253 } 257 }
254 this.namespaceBlock.replaceChild(n, exprResult); 258 this.namespaceBlock.replaceChild(n, exprResult);
255 } else if (n.isName() && this.exports.containsKey(n.getString()) && 259 } else if (n.isName() && this.exports.containsKey(n.getString()) &&
256 !parent.isFunction()) { 260 !parent.isFunction()) {
257 if (parent.isVar()) { 261 if (parent.isVar()) {
258 if (parent.getParent() == this.namespaceBlock) { 262 if (parent.getParent() == this.namespaceBlock) {
259 // It's a top-level exported variable definition. 263 // It's a top-level exported variable definition.
260 // Change 264 // Change
261 // 265 //
262 // var enum = { 'one': 1, 'two': 2 }; 266 // var enum = { 'one': 1, 'two': 2 };
263 // 267 //
264 // to 268 // to
265 // 269 //
266 // my.namespace.name.enum = { 'one': 1, 'two': 2 }; 270 // my.namespace.name.enum = { 'one': 1, 'two': 2 };
267 Node varContent = n.removeFirstChild(); 271 Node varContent = n.removeFirstChild();
268 Node exprResult = IR.exprResult( 272 Node exprResult = IR.exprResult(
269 IR.assign(buildQualifiedName(n), varContent)); 273 IR.assign(buildQualifiedName(n), varContent) .srcref(parent)
270 NodeUtil.setDebugInformation(exprResult, parent, n.getSt ring()); 274 ).srcref(parent);
275
271 if (parent.getJSDocInfo() != null) { 276 if (parent.getJSDocInfo() != null) {
272 exprResult.getFirstChild().setJSDocInfo(parent.getJS DocInfo().clone()); 277 exprResult.getFirstChild().setJSDocInfo(parent.getJS DocInfo().clone());
273 } 278 }
274 this.namespaceBlock.replaceChild(parent, exprResult); 279 this.namespaceBlock.replaceChild(parent, exprResult);
275 } 280 }
276 } else { 281 } else {
277 // It's a local name referencing exported entity. Change to its global name. 282 // It's a local name referencing exported entity. Change to its global name.
278 Node newNode = buildQualifiedName(n); 283 Node newNode = buildQualifiedName(n);
279 if (n.getJSDocInfo() != null) { 284 if (n.getJSDocInfo() != null) {
280 newNode.setJSDocInfo(n.getJSDocInfo().clone()); 285 newNode.setJSDocInfo(n.getJSDocInfo().clone());
281 } 286 }
282 287
283 // If we alter the name of a called function, then it gets a n explicit "this" 288 // If we alter the name of a called function, then it gets a n explicit "this"
284 // value. 289 // value.
285 if (parent.isCall()) { 290 if (parent.isCall()) {
286 parent.putBooleanProp(Node.FREE_CALL, false); 291 parent.putBooleanProp(Node.FREE_CALL, false);
287 } 292 }
288 293
289 parent.replaceChild(n, newNode); 294 parent.replaceChild(n, newNode);
290 } 295 }
291 } 296 }
292 } 297 }
293 298
294 private Node buildQualifiedName(Node internalName) { 299 private Node buildQualifiedName(Node internalName) {
295 String externalName = this.exports.get(internalName.getString()); 300 String externalName = this.exports.get(internalName.getString());
296 return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), 301 return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(),
297 this.namespaceName + "." + exte rnalName, 302 this.namespaceName + "." + externalName).srcrefTree(internal Name);
298 internalName,
299 internalName.getString());
300 } 303 }
301 } 304 }
305
302 } 306 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698