summaryrefslogtreecommitdiffstats
path: root/third_party
diff options
context:
space:
mode:
authorvitalyp <vitalyp@chromium.org>2014-09-17 20:59:54 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-18 04:03:50 +0000
commitb38e60a8e1162458f9c4d787c44cf17c32eff414 (patch)
tree7f2db776bfc8daddfabbe16c72ea9ab4735840a1 /third_party
parentf1f754bf3385a6f90c45ea4131844e671147f44f (diff)
downloadchromium_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.chromium4
-rw-r--r--third_party/closure_compiler/runner/runner.jarbin17234 -> 18794 bytes
-rw-r--r--third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java111
-rw-r--r--third_party/closure_compiler/runner/test/com/google/javascript/jscomp/ChromePassTest.java162
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
index 72cb0ea..1535afc 100644
--- a/third_party/closure_compiler/runner/runner.jar
+++ b/third_party/closure_compiler/runner/runner.jar
Binary files differ
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);
+ }
+
}