16e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
26e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
36e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// found in the LICENSE file.
46e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
56e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)package com.google.javascript.jscomp;
66e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
76e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
86e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import com.google.javascript.rhino.IR;
96e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import com.google.javascript.rhino.JSDocInfoBuilder;
106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import com.google.javascript.rhino.JSTypeExpression;
116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import com.google.javascript.rhino.Node;
126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import com.google.javascript.rhino.Token;
136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import java.util.ArrayList;
156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import java.util.Arrays;
166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import java.util.HashMap;
176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import java.util.HashSet;
186e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import java.util.List;
196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import java.util.Map;
206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import java.util.Set;
216e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
236e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * Compiler pass for Chrome-specific needs. It handles the following Chrome JS features:
246e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * <ul>
256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * <li>namespace declaration using {@code cr.define()},
266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * <li>unquoted property declaration using {@code {cr|Object}.defineProperty()}.
276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * </ul>
286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) *
296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * <p>For the details, see tests inside ChromePassTest.java.
306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)public class ChromePass extends AbstractPostOrderCallback implements CompilerPass {
326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    final AbstractCompiler compiler;
336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private Set<String> createdObjects;
356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private static final String CR_DEFINE = "cr.define";
376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private static final String CR_EXPORT_PATH = "cr.exportPath";
386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty";
396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private static final String CR_DEFINE_PROPERTY = "cr.defineProperty";
401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    private static final String CR_MAKE_PUBLIC = "cr.makePublic";
416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be called like this:"
436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            + " cr.define('name.space', function() '{ ... return {Export: Internal}; }');";
446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    static final DiagnosticType CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS =
466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            DiagnosticType.error("JSC_CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS",
476e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    "cr.define() should have exactly 2 arguments. " + CR_DEFINE_COMMON_EXPLANATION);
486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    static final DiagnosticType CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS =
506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            DiagnosticType.error("JSC_CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS",
516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    "cr.exportPath() should have exactly 1 argument: namespace name.");
526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    static final DiagnosticType CR_DEFINE_INVALID_FIRST_ARGUMENT =
546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            DiagnosticType.error("JSC_CR_DEFINE_INVALID_FIRST_ARGUMENT",
556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    "Invalid first argument for cr.define(). " + CR_DEFINE_COMMON_EXPLANATION);
566e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
576e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    static final DiagnosticType CR_DEFINE_INVALID_SECOND_ARGUMENT =
586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            DiagnosticType.error("JSC_CR_DEFINE_INVALID_SECOND_ARGUMENT",
596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    "Invalid second argument for cr.define(). " + CR_DEFINE_COMMON_EXPLANATION);
606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    static final DiagnosticType CR_DEFINE_INVALID_RETURN_IN_FUNCTION =
626e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            DiagnosticType.error("JSC_CR_DEFINE_INVALID_RETURN_IN_SECOND_ARGUMENT",
636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    "Function passed as second argument of cr.define() should return the"
646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    + " dictionary in its last statement. " + CR_DEFINE_COMMON_EXPLANATION);
656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    static final DiagnosticType CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND =
676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            DiagnosticType.error("JSC_CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND",
686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    "Invalid cr.PropertyKind passed to cr.defineProperty(): expected ATTR,"
696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    + " BOOL_ATTR or JS, found \"{0}\".");
706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    static final DiagnosticType CR_MAKE_PUBLIC_HAS_NO_JSDOC =
721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            DiagnosticType.error("JSC_CR_MAKE_PUBLIC_HAS_NO_JSDOC",
731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    "Private method exported by cr.makePublic() has no JSDoc.");
741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    static final DiagnosticType CR_MAKE_PUBLIC_MISSED_DECLARATION =
761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            DiagnosticType.error("JSC_CR_MAKE_PUBLIC_MISSED_DECLARATION",
771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    "Method \"{1}_\" exported by cr.makePublic() on \"{0}\" has no declaration.");
781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    static final DiagnosticType CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT =
801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            DiagnosticType.error("JSC_CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT",
811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    "Invalid second argument passed to cr.makePublic(): should be array of " +
821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    "strings.");
831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    public ChromePass(AbstractCompiler compiler) {
856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        this.compiler = compiler;
866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        // The global variable "cr" is declared in ui/webui/resources/js/cr.js.
876e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        this.createdObjects = new HashSet<>(Arrays.asList("cr"));
886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
896e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    @Override
916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    public void process(Node externs, Node root) {
926e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        NodeTraversal.traverse(compiler, root, this);
936e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
946e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
956e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    @Override
966e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    public void visit(NodeTraversal t, Node node, Node parent) {
976e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (node.isCall()) {
986e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            Node callee = node.getFirstChild();
996e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            if (callee.matchesQualifiedName(CR_DEFINE)) {
1006e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                visitNamespaceDefinition(node, parent);
1016e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                compiler.reportCodeChange();
1026e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            } else if (callee.matchesQualifiedName(CR_EXPORT_PATH)) {
1036e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                visitExportPath(node, parent);
1046e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                compiler.reportCodeChange();
1056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            } else if (callee.matchesQualifiedName(OBJECT_DEFINE_PROPERTY) ||
1066e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
1076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                visitPropertyDefinition(node, parent);
1086e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                compiler.reportCodeChange();
1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            } else if (callee.matchesQualifiedName(CR_MAKE_PUBLIC)) {
1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                if (visitMakePublic(node, parent)) {
1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    compiler.reportCodeChange();
1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                }
1136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            }
1146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
1156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
1166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private void visitPropertyDefinition(Node call, Node parent) {
1186e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node callee = call.getFirstChild();
1196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        String target = call.getChildAtIndex(1).getQualifiedName();
1206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY) && !target.endsWith(".prototype")) {
1216e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            target += ".prototype";
1226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
1236e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1246e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node property = call.getChildAtIndex(2);
1256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node getPropNode = NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(),
1276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                target + "." + property.getString()).srcrefTree(call);
1286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
1306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            setJsDocWithType(getPropNode, getTypeByCrPropertyKind(call.getChildAtIndex(3)));
1316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        } else {
1326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            setJsDocWithType(getPropNode, new Node(Token.QMARK));
1336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
1346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node definitionNode = IR.exprResult(getPropNode).srcref(parent);
1366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        parent.getParent().addChildAfter(definitionNode, parent);
1386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
1396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private Node getTypeByCrPropertyKind(Node propertyKind) {
1416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (propertyKind == null || propertyKind.matchesQualifiedName("cr.PropertyKind.JS")) {
1426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return new Node(Token.QMARK);
1436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
1446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (propertyKind.matchesQualifiedName("cr.PropertyKind.ATTR")) {
1456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return IR.string("string");
1466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
1476e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (propertyKind.matchesQualifiedName("cr.PropertyKind.BOOL_ATTR")) {
1486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return IR.string("boolean");
1496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
1506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        compiler.report(JSError.make(propertyKind, CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND,
1516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                propertyKind.getQualifiedName()));
1526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        return null;
1536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
1546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private void setJsDocWithType(Node target, Node type) {
1566e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
1576e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        builder.recordType(new JSTypeExpression(type, ""));
1586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        target.setJSDocInfo(builder.build(target));
1596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
1606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    private boolean visitMakePublic(Node call, Node exprResult) {
1621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        boolean changesMade = false;
1631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Node scope = exprResult.getParent();
1641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        String className = call.getChildAtIndex(1).getQualifiedName();
1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        String prototype = className  + ".prototype";
1661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Node methods = call.getChildAtIndex(2);
1671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        if (methods == null || !methods.isArrayLit()) {
1691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            compiler.report(JSError.make(exprResult, CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT));
1701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            return changesMade;
1711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        }
1721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Set<String> methodNames = new HashSet<>();
1741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        for (Node methodName: methods.children()) {
1751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            if (!methodName.isString()) {
1761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                compiler.report(JSError.make(methodName, CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT));
1771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                return changesMade;
1781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            }
1791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            methodNames.add(methodName.getString());
1801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        }
1811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        for (Node child: scope.children()) {
1831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            if (isAssignmentToPrototype(child, prototype)) {
1841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                Node objectLit = child.getFirstChild().getChildAtIndex(1);
1851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                for (Node stringKey : objectLit.children()) {
1861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    String field = stringKey.getString();
1871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    changesMade |= maybeAddPublicDeclaration(field, methodNames, className,
1881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                                             stringKey, scope, exprResult);
1891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                }
1901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            } else if (isAssignmentToPrototypeMethod(child, prototype)) {
1911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                Node assignNode = child.getFirstChild();
1921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                String qualifiedName = assignNode.getFirstChild().getQualifiedName();
1931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                String field = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
1941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                changesMade |= maybeAddPublicDeclaration(field, methodNames, className,
1951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                                         assignNode, scope, exprResult);
1961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            } else if (isDummyPrototypeMethodDeclaration(child, prototype)) {
1971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                String qualifiedName = child.getFirstChild().getQualifiedName();
1981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                String field = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
1991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                changesMade |= maybeAddPublicDeclaration(field, methodNames, className,
2001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                                         child.getFirstChild(), scope, exprResult);
2011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            }
2021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        }
2031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        for (String missedDeclaration : methodNames) {
2051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            compiler.report(JSError.make(exprResult, CR_MAKE_PUBLIC_MISSED_DECLARATION, className,
2061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    missedDeclaration));
2071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        }
2081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return changesMade;
2101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }
2111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    private boolean isAssignmentToPrototype(Node node, String prototype) {
2131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Node assignNode;
2141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return node.isExprResult() && (assignNode = node.getFirstChild()).isAssign() &&
2151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                assignNode.getFirstChild().getQualifiedName().equals(prototype);
2161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }
2171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    private boolean isAssignmentToPrototypeMethod(Node node, String prototype) {
2191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Node assignNode;
2201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return node.isExprResult() && (assignNode = node.getFirstChild()).isAssign() &&
2211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                assignNode.getFirstChild().getQualifiedName().startsWith(prototype + ".");
2221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }
2231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    private boolean isDummyPrototypeMethodDeclaration(Node node, String prototype) {
2251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Node getPropNode;
2261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return node.isExprResult() && (getPropNode = node.getFirstChild()).isGetProp() &&
2271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                getPropNode.getQualifiedName().startsWith(prototype + ".");
2281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }
2291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    private boolean maybeAddPublicDeclaration(String field, Set<String> publicAPIStrings,
2311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            String className, Node jsDocSourceNode, Node scope, Node exprResult) {
2321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        boolean changesMade = false;
2331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        if (field.endsWith("_")) {
2341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            String publicName = field.substring(0, field.length() - 1);
2351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            if (publicAPIStrings.contains(publicName)) {
2361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                Node methodDeclaration = NodeUtil.newQualifiedNameNode(
2371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                        compiler.getCodingConvention(), className + "." + publicName);
2381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                if (jsDocSourceNode.getJSDocInfo() != null) {
2391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    methodDeclaration.setJSDocInfo(jsDocSourceNode.getJSDocInfo());
2401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    scope.addChildBefore(
2411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                            IR.exprResult(methodDeclaration).srcrefTree(exprResult),
2421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                            exprResult);
2431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    changesMade = true;
2441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                } else {
2451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                    compiler.report(JSError.make(jsDocSourceNode, CR_MAKE_PUBLIC_HAS_NO_JSDOC));
2461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                }
2471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                publicAPIStrings.remove(publicName);
2481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            }
2491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        }
2501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return changesMade;
2511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }
2521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private void visitExportPath(Node crExportPathNode, Node parent) {
2546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (crExportPathNode.getChildCount() != 2) {
2556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            compiler.report(JSError.make(crExportPathNode,
2566e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS));
2576e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return;
2586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
2596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        createAndInsertObjectsForQualifiedName(parent,
2616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                crExportPathNode.getChildAtIndex(1).getString());
2626e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
2636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private void createAndInsertObjectsForQualifiedName(Node scriptChild, String namespace) {
2656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        List<Node> objectsForQualifiedName = createObjectsForQualifiedName(namespace);
2666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        for (Node n : objectsForQualifiedName) {
2676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            scriptChild.getParent().addChildBefore(n, scriptChild);
2686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
2696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
2706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private void visitNamespaceDefinition(Node crDefineCallNode, Node parent) {
2726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (crDefineCallNode.getChildCount() != 3) {
2736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            compiler.report(JSError.make(crDefineCallNode, CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS));
2746e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
2756e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2766e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node namespaceArg = crDefineCallNode.getChildAtIndex(1);
2776e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node function = crDefineCallNode.getChildAtIndex(2);
2786e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (!namespaceArg.isString()) {
2806e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_FIRST_ARGUMENT));
2816e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return;
2826e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
2836e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        // TODO(vitalyp): Check namespace name for validity here. It should be a valid chain of
2856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        // identifiers.
2866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        String namespace = namespaceArg.getString();
2876e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        createAndInsertObjectsForQualifiedName(parent, namespace);
2896e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (!function.isFunction()) {
2916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_SECOND_ARGUMENT));
2926e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return;
2936e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
2946e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2956e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node returnNode, objectLit;
2966e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Node functionBlock = function.getLastChild();
2976e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if ((returnNode = functionBlock.getLastChild()) == null ||
2986e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                !returnNode.isReturn() ||
2996e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                (objectLit = returnNode.getFirstChild()) == null ||
3006e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                !objectLit.isObjectLit()) {
3016e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            compiler.report(JSError.make(namespaceArg, CR_DEFINE_INVALID_RETURN_IN_FUNCTION));
3026e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return;
3036e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
3046e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Map<String, String> exports = objectLitToMap(objectLit);
3066e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        NodeTraversal.traverse(compiler, functionBlock, new RenameInternalsToExternalsCallback(
3086e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                namespace, exports, functionBlock));
3096e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
3106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private Map<String, String> objectLitToMap(Node objectLit) {
3126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        Map<String, String> res = new HashMap<String, String>();
3136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        for (Node keyNode : objectLit.children()) {
3156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            String key = keyNode.getString();
3166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            // TODO(vitalyp): Can dict value be other than a simple NAME? What if NAME doesn't
3186e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            // refer to a function/constructor?
3196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            String value = keyNode.getFirstChild().getString();
3206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3216e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            res.put(value, key);
3226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
3236e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3246e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        return res;
3256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
3266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    /**
3286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)     * For a string "a.b.c" produce the following JS IR:
3296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)     *
3306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)     * <p><pre>
3316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)     * var a = a || {};
3326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)     * a.b = a.b || {};
3336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)     * a.b.c = a.b.c || {};</pre>
3346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)     */
3356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private List<Node> createObjectsForQualifiedName(String namespace) {
3366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        List<Node> objects = new ArrayList<>();
3376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        String[] parts = namespace.split("\\.");
3386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        createObjectIfNew(objects, parts[0], true);
3406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (parts.length >= 2) {
3426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            StringBuilder currPrefix = new StringBuilder().append(parts[0]);
3436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            for (int i = 1; i < parts.length; ++i) {
3446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                currPrefix.append(".").append(parts[i]);
3456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                createObjectIfNew(objects, currPrefix.toString(), false);
3466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            }
3476e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
3486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        return objects;
3506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
3516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private void createObjectIfNew(List<Node> objects, String name, boolean needVar) {
3536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (!createdObjects.contains(name)) {
3546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            objects.add(createJsNode((needVar ? "var " : "") + name + " = " + name + " || {};"));
3556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            createdObjects.add(name);
3566e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
3576e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
3586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private Node createJsNode(String code) {
3606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        // The parent node after parseSyntheticCode() is SCRIPT node, we need to get rid of it.
3616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        return compiler.parseSyntheticCode(code).removeFirstChild();
3626e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
3636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    private class RenameInternalsToExternalsCallback extends AbstractPostOrderCallback {
3656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        private final String namespaceName;
3666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        private final Map<String, String> exports;
3676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        private final Node namespaceBlock;
3686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        public RenameInternalsToExternalsCallback(String namespaceName,
3706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                Map<String, String> exports, Node namespaceBlock) {
3716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            this.namespaceName = namespaceName;
3726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            this.exports = exports;
3736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            this.namespaceBlock = namespaceBlock;
3746e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
3756e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3766e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        @Override
3776e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        public void visit(NodeTraversal t, Node n, Node parent) {
3786e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            if (n.isFunction() && parent == this.namespaceBlock &&
3796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    this.exports.containsKey(n.getFirstChild().getString())) {
3806e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                // It's a top-level function/constructor definition.
3816e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //
3826e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                // Change
3836e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //
3846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //   /** Some doc */
3856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //   function internalName() {}
3866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //
3876e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                // to
3886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //
3896e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //   /** Some doc */
3906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //   my.namespace.name.externalName = function internalName() {};
3916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                //
3926e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                // by looking up in this.exports for internalName to find the correspondent
3936e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                // externalName.
3946e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                Node functionTree = n.cloneTree();
3956e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                Node exprResult = IR.exprResult(
3966e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                            IR.assign(buildQualifiedName(n.getFirstChild()), functionTree).srcref(n)
3976e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        ).srcref(n);
3986e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
3996e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                if (n.getJSDocInfo() != null) {
4006e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    exprResult.getFirstChild().setJSDocInfo(n.getJSDocInfo());
4016e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    functionTree.removeProp(Node.JSDOC_INFO_PROP);
4026e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                }
4036e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                this.namespaceBlock.replaceChild(n, exprResult);
4046e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            } else if (n.isName() && this.exports.containsKey(n.getString()) &&
4056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    !parent.isFunction()) {
4066e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                if (parent.isVar()) {
4076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    if (parent.getParent() == this.namespaceBlock) {
40803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                        // It's a top-level exported variable definition (maybe without an
40903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                        // assignment).
4106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        // Change
4116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        //
4126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        //   var enum = { 'one': 1, 'two': 2 };
4136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        //
4146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        // to
4156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        //
4166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        //   my.namespace.name.enum = { 'one': 1, 'two': 2 };
4176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        Node varContent = n.removeFirstChild();
41803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                        Node exprResult;
41903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                        if (varContent == null) {
42003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                            exprResult = IR.exprResult(buildQualifiedName(n)).srcref(parent);
42103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                        } else {
42203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                            exprResult = IR.exprResult(
42303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                        IR.assign(buildQualifiedName(n), varContent).srcref(parent)
42403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                                    ).srcref(parent);
42503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)                        }
4266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        if (parent.getJSDocInfo() != null) {
4276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                            exprResult.getFirstChild().setJSDocInfo(parent.getJSDocInfo().clone());
4286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        }
4296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        this.namespaceBlock.replaceChild(parent, exprResult);
4306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    }
4316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                } else {
4326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    // It's a local name referencing exported entity. Change to its global name.
4336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    Node newNode = buildQualifiedName(n);
4346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    if (n.getJSDocInfo() != null) {
4356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        newNode.setJSDocInfo(n.getJSDocInfo().clone());
4366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    }
4376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
4386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    // If we alter the name of a called function, then it gets an explicit "this"
4396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    // value.
4406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    if (parent.isCall()) {
4416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        parent.putBooleanProp(Node.FREE_CALL, false);
4426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    }
4436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
4446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    parent.replaceChild(n, newNode);
4456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                }
4466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            }
4476e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
4486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
4496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        private Node buildQualifiedName(Node internalName) {
4506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            String externalName = this.exports.get(internalName.getString());
4516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            return NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(),
4526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    this.namespaceName + "." + externalName).srcrefTree(internalName);
4536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
4546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
4556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)}
456