| 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 c7f3c12daccb071f705a6a6a9c896653b3045a37..be8da2eaa45fe5f55389941158795b06da76a36e 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
|
| @@ -37,6 +37,7 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
|
| private static final String CR_EXPORT_PATH = "cr.exportPath";
|
| private static final String OBJECT_DEFINE_PROPERTY = "Object.defineProperty";
|
| private static final String CR_DEFINE_PROPERTY = "cr.defineProperty";
|
| + private static final String CR_MAKE_PUBLIC = "cr.makePublic";
|
|
|
| private static final String CR_DEFINE_COMMON_EXPLANATION = "It should be called like this:"
|
| + " cr.define('name.space', function() '{ ... return {Export: Internal}; }');";
|
| @@ -67,6 +68,19 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
|
| "Invalid cr.PropertyKind passed to cr.defineProperty(): expected ATTR,"
|
| + " BOOL_ATTR or JS, found \"{0}\".");
|
|
|
| + static final DiagnosticType CR_MAKE_PUBLIC_HAS_NO_JSDOC =
|
| + DiagnosticType.error("JSC_CR_MAKE_PUBLIC_HAS_NO_JSDOC",
|
| + "Private method exported by cr.makePublic() has no JSDoc.");
|
| +
|
| + static final DiagnosticType CR_MAKE_PUBLIC_MISSED_DECLARATION =
|
| + DiagnosticType.error("JSC_CR_MAKE_PUBLIC_MISSED_DECLARATION",
|
| + "Method \"{1}_\" exported by cr.makePublic() on \"{0}\" has no declaration.");
|
| +
|
| + static final DiagnosticType CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT =
|
| + DiagnosticType.error("JSC_CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT",
|
| + "Invalid second argument passed to cr.makePublic(): should be array of " +
|
| + "strings.");
|
| +
|
| public ChromePass(AbstractCompiler compiler) {
|
| this.compiler = compiler;
|
| // The global variable "cr" is declared in ui/webui/resources/js/cr.js.
|
| @@ -92,6 +106,10 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
|
| callee.matchesQualifiedName(CR_DEFINE_PROPERTY)) {
|
| visitPropertyDefinition(node, parent);
|
| compiler.reportCodeChange();
|
| + } else if (callee.matchesQualifiedName(CR_MAKE_PUBLIC)) {
|
| + if (visitMakePublic(node, parent)) {
|
| + compiler.reportCodeChange();
|
| + }
|
| }
|
| }
|
| }
|
| @@ -140,6 +158,98 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
|
| target.setJSDocInfo(builder.build(target));
|
| }
|
|
|
| + private boolean visitMakePublic(Node call, Node exprResult) {
|
| + boolean changesMade = false;
|
| + Node scope = exprResult.getParent();
|
| + String className = call.getChildAtIndex(1).getQualifiedName();
|
| + String prototype = className + ".prototype";
|
| + Node methods = call.getChildAtIndex(2);
|
| +
|
| + if (methods == null || !methods.isArrayLit()) {
|
| + compiler.report(JSError.make(exprResult, CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT));
|
| + return changesMade;
|
| + }
|
| +
|
| + Set<String> methodNames = new HashSet<>();
|
| + for (Node methodName: methods.children()) {
|
| + if (!methodName.isString()) {
|
| + compiler.report(JSError.make(methodName, CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT));
|
| + return changesMade;
|
| + }
|
| + methodNames.add(methodName.getString());
|
| + }
|
| +
|
| + for (Node child: scope.children()) {
|
| + if (isAssignmentToPrototype(child, prototype)) {
|
| + Node objectLit = child.getFirstChild().getChildAtIndex(1);
|
| + for (Node stringKey : objectLit.children()) {
|
| + String field = stringKey.getString();
|
| + changesMade |= maybeAddPublicDeclaration(field, methodNames, className,
|
| + stringKey, scope, exprResult);
|
| + }
|
| + } else if (isAssignmentToPrototypeMethod(child, prototype)) {
|
| + Node assignNode = child.getFirstChild();
|
| + String qualifiedName = assignNode.getFirstChild().getQualifiedName();
|
| + String field = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
|
| + changesMade |= maybeAddPublicDeclaration(field, methodNames, className,
|
| + assignNode, scope, exprResult);
|
| + } else if (isDummyPrototypeMethodDeclaration(child, prototype)) {
|
| + String qualifiedName = child.getFirstChild().getQualifiedName();
|
| + String field = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
|
| + changesMade |= maybeAddPublicDeclaration(field, methodNames, className,
|
| + child.getFirstChild(), scope, exprResult);
|
| + }
|
| + }
|
| +
|
| + for (String missedDeclaration : methodNames) {
|
| + compiler.report(JSError.make(exprResult, CR_MAKE_PUBLIC_MISSED_DECLARATION, className,
|
| + missedDeclaration));
|
| + }
|
| +
|
| + return changesMade;
|
| + }
|
| +
|
| + private boolean isAssignmentToPrototype(Node node, String prototype) {
|
| + Node assignNode;
|
| + return node.isExprResult() && (assignNode = node.getFirstChild()).isAssign() &&
|
| + assignNode.getFirstChild().getQualifiedName().equals(prototype);
|
| + }
|
| +
|
| + private boolean isAssignmentToPrototypeMethod(Node node, String prototype) {
|
| + Node assignNode;
|
| + return node.isExprResult() && (assignNode = node.getFirstChild()).isAssign() &&
|
| + assignNode.getFirstChild().getQualifiedName().startsWith(prototype + ".");
|
| + }
|
| +
|
| + private boolean isDummyPrototypeMethodDeclaration(Node node, String prototype) {
|
| + Node getPropNode;
|
| + return node.isExprResult() && (getPropNode = node.getFirstChild()).isGetProp() &&
|
| + getPropNode.getQualifiedName().startsWith(prototype + ".");
|
| + }
|
| +
|
| + private boolean maybeAddPublicDeclaration(String field, Set<String> publicAPIStrings,
|
| + String className, Node jsDocSourceNode, Node scope, Node exprResult) {
|
| + boolean changesMade = false;
|
| + if (field.endsWith("_")) {
|
| + String publicName = field.substring(0, field.length() - 1);
|
| + if (publicAPIStrings.contains(publicName)) {
|
| + Node methodDeclaration = NodeUtil.newQualifiedNameNode(
|
| + compiler.getCodingConvention(), className + "." + publicName);
|
| + if (jsDocSourceNode.getJSDocInfo() != null) {
|
| + methodDeclaration.setJSDocInfo(jsDocSourceNode.getJSDocInfo());
|
| + scope.addChildBefore(
|
| + IR.exprResult(methodDeclaration).srcrefTree(exprResult),
|
| + exprResult);
|
| + changesMade = true;
|
| + } else {
|
| + compiler.report(JSError.make(jsDocSourceNode, CR_MAKE_PUBLIC_HAS_NO_JSDOC));
|
| + }
|
| + publicAPIStrings.remove(publicName);
|
| + }
|
| + }
|
| + return changesMade;
|
| + }
|
| +
|
| private void visitExportPath(Node crExportPathNode, Node parent) {
|
| if (crExportPathNode.getChildCount() != 2) {
|
| compiler.report(JSError.make(crExportPathNode,
|
| @@ -342,5 +452,4 @@ public class ChromePass extends AbstractPostOrderCallback implements CompilerPas
|
| this.namespaceName + "." + externalName).srcrefTree(internalName);
|
| }
|
| }
|
| -
|
| }
|
|
|