diff options
author | vitalyp <vitalyp@chromium.org> | 2014-09-17 20:59:54 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-18 04:03:50 +0000 |
commit | b38e60a8e1162458f9c4d787c44cf17c32eff414 (patch) | |
tree | 7f2db776bfc8daddfabbe16c72ea9ab4735840a1 /third_party | |
parent | f1f754bf3385a6f90c45ea4131844e671147f44f (diff) | |
download | chromium_src-b38e60a8e1162458f9c4d787c44cf17c32eff414.zip chromium_src-b38e60a8e1162458f9c4d787c44cf17c32eff414.tar.gz chromium_src-b38e60a8e1162458f9c4d787c44cf17c32eff414.tar.bz2 |
Add public API generation with cr.makePublic() and handle it in compiler pass
R=dbeam@chromium.org,tbreisacher@chromium.org
BUG=393873
TEST=third_party/closure_compiler/runner/how_to_test_compiler_pass.md
Committed: https://crrev.com/730652530d108372ef14f62b89665197628f7880
Cr-Commit-Position: refs/heads/master@{#295173}
Review URL: https://codereview.chromium.org/557633002
Cr-Commit-Position: refs/heads/master@{#295410}
Diffstat (limited to 'third_party')
-rw-r--r-- | third_party/closure_compiler/README.chromium | 4 | ||||
-rw-r--r-- | third_party/closure_compiler/runner/runner.jar | bin | 17234 -> 18794 bytes | |||
-rw-r--r-- | third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java | 111 | ||||
-rw-r--r-- | third_party/closure_compiler/runner/test/com/google/javascript/jscomp/ChromePassTest.java | 162 |
4 files changed, 274 insertions, 3 deletions
diff --git a/third_party/closure_compiler/README.chromium b/third_party/closure_compiler/README.chromium index 6aef43d..9bcf4e4 100644 --- a/third_party/closure_compiler/README.chromium +++ b/third_party/closure_compiler/README.chromium @@ -19,7 +19,7 @@ Local modifications: - Chrome-specific coding conventions to understand cr.addSingletonGetter(). - third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java Added pass to handle namespace definition with cr.define(), object chain - creation with cr.exportPath() and property definition with - {cr|Object}.defineProperty(). + creation with cr.exportPath(), property definition with + {cr|Object}.defineProperty() and public API generation with cr.makePublic(). See third_party/closure_compiler/runner/how_to_test_compiler_pass.md for testing instructions on this pass. diff --git a/third_party/closure_compiler/runner/runner.jar b/third_party/closure_compiler/runner/runner.jar Binary files differindex 72cb0ea..1535afc 100644 --- a/third_party/closure_compiler/runner/runner.jar +++ b/third_party/closure_compiler/runner/runner.jar 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 c7f3c12..be8da2e 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); } } - } diff --git a/third_party/closure_compiler/runner/test/com/google/javascript/jscomp/ChromePassTest.java b/third_party/closure_compiler/runner/test/com/google/javascript/jscomp/ChromePassTest.java index 7367570..d6264c1 100644 --- a/third_party/closure_compiler/runner/test/com/google/javascript/jscomp/ChromePassTest.java +++ b/third_party/closure_compiler/runner/test/com/google/javascript/jscomp/ChromePassTest.java @@ -363,4 +363,166 @@ public class ChromePassTest extends CompilerTestCase { test("cr.exportPath();", null, ChromePass.CR_EXPORT_PATH_WRONG_NUMBER_OF_ARGUMENTS); } + public void testCrMakePublicWorksOnOneMethodDefinedInPrototypeObject() throws Exception { + test( + "/** @constructor */\n" + + "function Class() {};\n" + + "\n" + + "Class.prototype = {\n" + + " /** @return {number} */\n" + + " method_: function() { return 42; }\n" + + "};\n" + + "\n" + + "cr.makePublic(Class, ['method']);", + "/** @constructor */\n" + + "function Class() {};\n" + + "\n" + + "Class.prototype = {\n" + + " /** @return {number} */\n" + + " method_: function() { return 42; }\n" + + "};\n" + + "\n" + + "/** @return {number} */\n" + + "Class.method;\n" + + "\n" + + "cr.makePublic(Class, ['method']);"); + } + + public void testCrMakePublicWorksOnTwoMethods() throws Exception { + test( + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "Class.prototype = {\n" + + " /** @return {number} */\n" + + " m1_: function() { return 42; },\n" + + "\n" + + " /** @return {string} */\n" + + " m2_: function() { return ''; }\n" + + "};\n" + + "\n" + + "cr.makePublic(Class, ['m1', 'm2']);", + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "Class.prototype = {\n" + + " /** @return {number} */\n" + + " m1_: function() { return 42; },\n" + + "\n" + + " /** @return {string} */\n" + + " m2_: function() { return ''; }\n" + + "}\n" + + "\n" + + "/** @return {number} */\n" + + "Class.m1;\n" + + "\n" + + "/** @return {string} */\n" + + "Class.m2;\n" + + "\n" + + "cr.makePublic(Class, ['m1', 'm2']);"); + } + + public void testCrMakePublicRequiresMethodsToHaveJSDoc() throws Exception { + test("/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "Class.prototype = {\n" + + " method_: function() {}\n" + + "}\n" + + "\n" + + "cr.makePublic(Class, ['method']);", null, ChromePass.CR_MAKE_PUBLIC_HAS_NO_JSDOC); + } + + public void testCrMakePublicDoesNothingWithMethodsNotInAPI() throws Exception { + test( + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "Class.prototype = {\n" + + " method_: function() {}\n" + + "}\n" + + "\n" + + "cr.makePublic(Class, []);", + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "Class.prototype = {\n" + + " method_: function() {}\n" + + "}\n" + + "\n" + + "cr.makePublic(Class, []);"); + } + + public void testCrMakePublicRequiresExportedMethodToBeDeclared() throws Exception { + test( + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "Class.prototype = {\n" + + "}\n" + + "\n" + + "cr.makePublic(Class, ['method']);", null, + ChromePass.CR_MAKE_PUBLIC_MISSED_DECLARATION); + } + + public void testCrMakePublicWorksOnOneMethodDefinedDirectlyOnPrototype() throws Exception { + test( + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "/** @return {number} */\n" + + "Class.prototype.method_ = function() {};\n" + + "\n" + + "cr.makePublic(Class, ['method']);", + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "/** @return {number} */\n" + + "Class.prototype.method_ = function() {};\n" + + "\n" + + "/** @return {number} */\n" + + "Class.method;\n" + + "\n" + + "cr.makePublic(Class, ['method']);"); + } + + public void testCrMakePublicWorksOnDummyDeclaration() throws Exception { + test( + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "/** @return {number} */\n" + + "Class.prototype.method_;\n" + + "\n" + + "cr.makePublic(Class, ['method']);", + "/** @constructor */\n" + + "function Class() {}\n" + + "\n" + + "/** @return {number} */\n" + + "Class.prototype.method_;\n" + + "\n" + + "/** @return {number} */\n" + + "Class.method;\n" + + "\n" + + "cr.makePublic(Class, ['method']);"); + } + + public void testCrMakePublicReportsInvalidSecondArgumentMissing() throws Exception { + test( + "cr.makePublic(Class);", null, + ChromePass.CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT); + } + + public void testCrMakePublicReportsInvalidSecondArgumentNotAnArray() throws Exception { + test( + "cr.makePublic(Class, 42);", null, + ChromePass.CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT); + } + + public void testCrMakePublicReportsInvalidSecondArgumentArrayWithNotAString() throws Exception { + test( + "cr.makePublic(Class, [42]);", null, + ChromePass.CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT); + } + } |