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..480a73bddfdfdfd86ea70137e980ffddca294401 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,71 +17,23 @@ 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 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}; }');"; |
@@ -99,6 +54,11 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas |
"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, " + |
Tyler Breisacher (Chromium)
2014/08/12 15:57:29
If you define PropertyKind as an @enum then the ty
Vitaly Pavlenko
2014/08/12 17:44:17
Acknowledged.
|
+ "BOOL_ATTR or JS, found \"{0}\"."); |
Tyler Breisacher (Chromium)
2014/08/12 15:57:29
nit: Put the space and the '+' on the next line:
Vitaly Pavlenko
2014/08/12 17:44:17
How does one select between two possibilities?
|
+ |
public ChromePass(AbstractCompiler compiler) { |
this.compiler = compiler; |
} |
@@ -115,10 +75,53 @@ 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(); |
+ Node target = call.getChildAtIndex(1); |
+ Node property = call.getChildAtIndex(2); |
+ |
+ Node getPropNode = NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), |
+ target.getQualifiedName() + "." + property.getString()); |
+ |
+ if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) { |
+ setJsDocWithType(getPropNode, getTypeByCrPropertyKind(call.getChildAtIndex(3))); |
+ } else { |
+ setJsDocWithType(getPropNode, new Node(Token.STAR)); |
+ } |
+ |
+ Node definitionNode = IR.exprResult(getPropNode).srcrefTree(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")) { |
+ return IR.string("boolean"); |
+ } else if (propertyKind.matchesQualifiedName("cr.PropertyKind.JS")) { |
+ return new Node(Token.STAR); |
Tyler Breisacher (Chromium)
2014/08/12 16:32:49
I was discussing this with dimvar@ a little bit an
Vitaly Pavlenko
2014/08/12 17:44:17
Done.
|
+ } 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)); |
@@ -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) |
+ ).srcrefTree(n); |
+ |
if (n.getJSDocInfo() != null) { |
exprResult.getFirstChild().setJSDocInfo(n.getJSDocInfo()); |
functionTree.removeProp(Node.JSDOC_INFO_PROP); |
@@ -266,8 +270,8 @@ 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)).srcrefTree(parent); |
+ |
if (parent.getJSDocInfo() != null) { |
exprResult.getFirstChild().setJSDocInfo(parent.getJSDocInfo().clone()); |
} |
@@ -294,9 +298,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); |
} |
} |
+ |
} |