Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1640)

Unified Diff: Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ReturnAnnotationChecker.java

Issue 137553005: DevTools: [JsDocValidator] Refactor JsDoc annotation checkers (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Limit the thread count by the number of validated files Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ReturnAnnotationChecker.java
diff --git a/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ReturnAnnotationChecker.java b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ReturnAnnotationChecker.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c663ae934cbb93f34299ed96fe8059ca0639a40
--- /dev/null
+++ b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ReturnAnnotationChecker.java
@@ -0,0 +1,179 @@
+package org.chromium.devtools.jsdoc.checks;
+
+import com.google.javascript.rhino.head.Token;
+import com.google.javascript.rhino.head.ast.Assignment;
+import com.google.javascript.rhino.head.ast.AstNode;
+import com.google.javascript.rhino.head.ast.Comment;
+import com.google.javascript.rhino.head.ast.FunctionNode;
+import com.google.javascript.rhino.head.ast.ObjectProperty;
+import com.google.javascript.rhino.head.ast.ReturnStatement;
+
+import org.chromium.devtools.jsdoc.ValidatorContext;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public final class ReturnAnnotationChecker extends ContextTrackingChecker {
+
+ private final Set<FunctionRecord> valueReturningFunctions = new HashSet<>();
+ private final Set<FunctionRecord> throwingFunctions = new HashSet<>();
+
+ @Override
+ public void enterNode(AstNode node) {
+ switch (node.getType()) {
+ case Token.RETURN:
+ handleReturn((ReturnStatement) node);
+ break;
+ case Token.THROW:
+ handleThrow();
+ break;
+ }
+ }
+
+ private void handleReturn(ReturnStatement node) {
+ if (node.getReturnValue() == null || AstUtil.hasParentOfType(node, Token.ASSIGN)) {
+ return;
+ }
+
+ FunctionRecord record = getState().getCurrentFunctionRecord();
+ if (record == null) {
+ return;
+ }
+ AstNode nameNode = getFunctionNameNode(record.functionNode);
+ if (nameNode == null) {
+ return;
+ }
+ valueReturningFunctions.add(record);
+ }
+
+ private void handleThrow() {
+ FunctionRecord record = getState().getCurrentFunctionRecord();
+ if (record == null) {
+ return;
+ }
+ AstNode nameNode = getFunctionNameNode(record.functionNode);
+ if (nameNode == null) {
+ return;
+ }
+ throwingFunctions.add(record);
+ }
+
+ @Override
+ public void leaveNode(AstNode node) {
+ if (node.getType() != Token.FUNCTION) {
+ return;
+ }
+
+ FunctionRecord record = getState().getCurrentFunctionRecord();
+ if (record != null) {
+ checkFunctionAnnotation(record);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void checkFunctionAnnotation(FunctionRecord record) {
+ boolean isReturningFunction = valueReturningFunctions.contains(record);
+ String functionName = getFunctionName(record.functionNode);
+ if (functionName == null) {
+ return;
+ }
+ boolean isApiFunction = !functionName.startsWith("_")
+ && (record.isTopLevelFunction()
+ || (record.enclosingType != null
+ && isPlainTopLevelFunction(record.enclosingFunctionRecord)));
+ boolean isInterfaceFunction =
+ record.enclosingType != null && record.enclosingType.isInterface;
+ Comment jsDocNode = getJsDocNode(record.functionNode);
+ String jsDoc = jsDocNode != null ? jsDocNode.getValue() : null;
+
+ int invalidAnnotationIndex = invalidReturnsAnnotationIndex(jsDoc);
+ ValidatorContext context = getState().getContext();
+ if (invalidAnnotationIndex != -1) {
+ String suggestedResolution = (isReturningFunction || isInterfaceFunction)
+ ? "should be @return instead"
+ : "please remove, as function does not return value";
+ context.reportErrorInNode(jsDocNode, invalidAnnotationIndex,
+ String.format("invalid @returns annotation found - %s", suggestedResolution));
+ return;
+ }
+ AstNode functionNameNode = getFunctionNameNode(record.functionNode);
+ if (functionNameNode == null) {
+ return;
+ }
+
+ if (isReturningFunction) {
+ if (!record.hasReturnAnnotation() && isApiFunction) {
+ context.reportErrorInNode(functionNameNode, 0,
+ "@return annotation is required for API functions that return value");
+ }
+ } else {
+ // A @return-function that does not actually return anything and
+ // is intended to be overridden in subclasses must throw.
+ if (record.hasReturnAnnotation()
+ && !isInterfaceFunction
+ && !throwingFunctions.contains(record)) {
+ context.reportErrorInNode(functionNameNode, 0,
+ "@return annotation found, yet function does not return value");
+ }
+ }
+ }
+
+ private boolean isPlainTopLevelFunction(FunctionRecord record) {
+ if (record == null) {
+ return false;
+ }
+ return record.isTopLevelFunction()
+ && (record.enclosingType == null && !record.isConstructor);
+ }
+
+ private String getFunctionName(FunctionNode functionNode) {
+ AstNode nameNode = getFunctionNameNode(functionNode);
+ if (nameNode == null) {
+ return null;
+ }
+ return getState().getNodeText(nameNode);
+ }
+
+ private static int invalidReturnsAnnotationIndex(String jsDoc) {
+ return jsDoc == null ? -1 : jsDoc.indexOf("@returns");
+ }
+
+ private static AstNode getFunctionNameNode(FunctionNode functionNode) {
+ AstNode nameNode = functionNode.getFunctionName();
+ if (nameNode != null) {
+ return nameNode;
+ }
+
+ if (AstUtil.hasParentOfType(functionNode, Token.COLON)) {
+ return ((ObjectProperty) functionNode.getParent()).getLeft();
+ }
+ // Do not require annotation for assignment-RHS functions.
+ return null;
+ }
+
+ private static Comment getJsDocNode(FunctionNode functionNode) {
+ Comment jsDocNode = functionNode.getJsDocNode();
+ if (jsDocNode != null) {
+ return jsDocNode;
+ }
+
+ // reader.onloadend = function() {...}
+ if (AstUtil.hasParentOfType(functionNode, Token.ASSIGN)) {
+ Assignment assignment = (Assignment) functionNode.getParent();
+ if (assignment.getRight() == functionNode) {
+ jsDocNode = assignment.getJsDocNode();
+ if (jsDocNode != null) {
+ return jsDocNode;
+ }
+ }
+ }
+
+ if (AstUtil.hasParentOfType(functionNode, Token.COLON)) {
+ jsDocNode = ((ObjectProperty) functionNode.getParent()).getLeft().getJsDocNode();
+ if (jsDocNode != null) {
+ return jsDocNode;
+ }
+ }
+ return null;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698