Chromium Code Reviews| 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); |
| } |
| } |
| + |
| } |