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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java
diff --git a/third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java b/third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java
index a3c91291f6ba8673867b81be5680b7a70545833a..ecd990d48fe3d8580c4b3dd789b21a5319a040f1 100644
--- a/third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java
+++ b/third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java
@@ -6,7 +6,10 @@ package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
+import com.google.javascript.rhino.JSDocInfoBuilder;
+import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
+import com.google.javascript.rhino.Token;
import java.util.ArrayList;
import java.util.HashMap;
@@ -14,73 +17,25 @@ import java.util.List;
import java.util.Map;
/**
- * Compiler pass for Chrome-specific needs. Right now it allows the compiler to check types with
- * methods defined inside Chrome namespaces.
+ * Compiler pass for Chrome-specific needs. It handles the following Chrome JS features:
+ * <ul>
+ * <li>namespace declaration using {@code cr.define()},
+ * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}.
+ * </ul>
*
- * <p>The namespaces in Chrome JS are declared as follows:
- * <pre>
- * cr.define('my.namespace.name', function() {
- * /** @param {number} arg
- * function internalStaticMethod(arg) {}
- *
- * /** @constructor
- * function InternalClass() {}
- *
- * InternalClass.prototype = {
- * /** @param {number} arg
- * method: function(arg) {
- * internalStaticMethod(arg); // let's demonstrate the change of local names after our pass
- * }
- * };
- *
- * return {
- * externalStaticMethod: internalStaticMethod,
- * ExternalClass: InternalClass
- * }
- * });
- * </pre>
- *
- * <p>Outside of cr.define() statement the function can be called like this:
- * {@code my.namespace.name.externalStaticMethod(42);}.
- *
- * <p>We need to check the types of arguments and return values of such functions. However, the
- * function is assigned to its namespace dictionary only at run-time and the original Closure
- * Compiler isn't smart enough to predict behavior in that case. Therefore, we need to modify the
- * AST before any compiler checks. That's how we modify it to tell the compiler what's going on:
- *
- * <pre>
- * var my = my || {};
- * my.namespace = my.namespace || {};
- * my.namespace.name = my.namespace.name || {};
- *
- * cr.define('my.namespace.name', function() {
- * /** @param {number} arg
- * my.namespace.name.externalStaticMethod(arg) {}
- *
- * /** @constructor
- * my.namespace.name.ExternalClass = function() {};
- *
- * my.namespace.name.ExternalClass.prototype = {
- * /** @param {number} arg
- * method: function(arg) {
- * my.namespace.name.externalStaticMethod(arg); // see, it has been changed
- * }
- * };
- *
- * return {
- * externalStaticMethod: my.namespace.name.externalStaticMethod,
- * ExternalClass: my.namespace.name.ExternalClass
- * }
- * });
- * </pre>
+ * <p>For the details, see tests inside ChromePassTest.java.
*/
public class ChromePass extends AbstractPostOrderCallback implements CompilerPass {
final AbstractCompiler compiler;
private static final String CR_DEFINE = "cr.define";
- private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be called like this: " +
- "cr.define('name.space', function() '{ ... return {Export: Internal}; }');";
+ private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty";
+
+ private static final String CR_DEFINE_PROPERTY = "cr.defineProperty";
+
+ private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be called like this:"
+ + " cr.define('name.space', function() '{ ... return {Export: Internal}; }');";
static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS =
DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS",
@@ -96,8 +51,13 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION =
DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMENT",
- "Function passed as second argument of cr.define() should return the " +
- "dictionary in its last statement. " + CR_DEFINE_COMMON_EXPLANATION);
+ "Function passed as second argument of cr.define() should return the"
+ + " dictionary in its last statement. " + CR_DEFINE_COMMON_EXPLANATION);
+
+ static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND =
+ DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND",
+ "Invalid cr.PropertyKind passed to cr.defineProperty(): expected ATTR,"
+ + " BOOL_ATTR or JS, found \"{0}\".");
public ChromePass(AbstractCompiler compiler) {
this.compiler = compiler;
@@ -115,10 +75,57 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
if (callee.matchesQualifiedName(CR_DEFINE)) {
visitNamespaceDefinition(node, parent);
compiler.reportCodeChange();
+ } else if (callee.matchesQualifiedName(OBJECT_DEFINE_PROPERTY) ||
+ callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
+ visitPropertyDefinition(node, parent);
+ compiler.reportCodeChange();
}
}
}
+ private void visitPropertyDefinition(Node call, Node parent) {
+ Node callee = call.getFirstChild();
+ String target = call.getChildAtIndex(1).getQualifiedName();
+ if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY) && !target.endsWith(".prototype")) {
+ target += ".prototype";
+ }
+
+ Node property = call.getChildAtIndex(2);
+
+ Node getPropNode = NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(),
+ target + "." + property.getString()).srcrefTree(call);
+
+ if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
+ setJsDocWithType(getPropNode, getTypeByCrPropertyKind(call.getChildAtIndex(3)));
+ } else {
+ setJsDocWithType(getPropNode, new Node(Token.QMARK));
+ }
+
+ Node definitionNode = IR.exprResult(getPropNode).srcref(parent);
+
+ parent.getParent().addChildAfter(definitionNode, parent);
+ }
+
+ private Node getTypeByCrPropertyKind(Node propertyKind) {
+ if (propertyKind.matchesQualifiedName("cr.PropertyKind.ATTR")) {
+ return IR.string("string");
+ } 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.
+ return IR.string("boolean");
+ } else if (propertyKind.matchesQualifiedName("cr.PropertyKind.JS")) {
+ return new Node(Token.QMARK);
+ } else {
+ compiler.report(JSError.make(propertyKind, CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND,
+ propertyKind.getQualifiedName()));
+ return null;
+ }
+ }
+
+ private void setJsDocWithType(Node target, Node type) {
+ JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
+ builder.recordType(new JSTypeExpression(type, ""));
+ target.setJSDocInfo(builder.build(target));
+ }
+
private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) {
if (crDefineCallNode.getChildCount() != 3) {
compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS));
@@ -141,14 +148,14 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
parent.getParent().addChildBefore(n, parent);
}
- Node returnNode, functionBlock, objectLit;
if (!function.isFunction()) {
compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_ARGUMENT));
return;
}
- if ((functionBlock = function.getLastChild()) == null ||
- (returnNode = functionBlock.getLastChild()) == null ||
+ Node returnNode, objectLit;
+ Node functionBlock = function.getLastChild();
+ if ((returnNode = functionBlock.getLastChild()) == null ||
!returnNode.isReturn() ||
(objectLit = returnNode.getFirstChild()) == null ||
!objectLit.isObjectLit()) {
@@ -223,10 +230,6 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
- if (n == null) {
- return;
- }
-
if (n.isFunction() && parent == this.namespaceBlock &&
this.exports.containsKey(n.getFirstChild().getString())) {
// It's a top-level function/constructor definition.
@@ -245,8 +248,9 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
// externalName.
Node functionTree = n.cloneTree();
Node exprResult = IR.exprResult(
- IR.assign(buildQualifiedName(n.getFirstChild()), functionTree));
- NodeUtil.setDebugInformation(exprResult, n, n.getFirstChild().getString());
+ IR.assign(buildQualifiedName(n.getFirstChild()), functionTree).srcref(n)
+ ).srcref(n);
+
if (n.getJSDocInfo() != null) {
exprResult.getFirstChild().setJSDocInfo(n.getJSDocInfo());
functionTree.removeProp(Node.JSDOC_INFO_PROP);
@@ -266,8 +270,9 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
// my.namespace.name.enum = { 'one': 1, 'two': 2 };
Node varContent = n.removeFirstChild();
Node exprResult = IR.exprResult(
- IR.assign(buildQualifiedName(n), varContent));
- NodeUtil.setDebugInformation(exprResult, parent, n.getString());
+ IR.assign(buildQualifiedName(n), varContent).srcref(parent)
+ ).srcref(parent);
+
if (parent.getJSDocInfo() != null) {
exprResult.getFirstChild().setJSDocInfo(parent.getJSDocInfo().clone());
}
@@ -294,9 +299,8 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
private Node buildQualifiedName(Node internalName) {
String externalName = this.exports.get(internalName.getString());
return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(),
- this.namespaceName + "." + externalName,
- internalName,
- internalName.getString());
+ this.namespaceName + "." + externalName).srcrefTree(internalName);
}
}
+
}

Powered by Google App Engine
This is Rietveld 408576698