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

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: Address comments from sergeyv 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..3b8c300dd79626a03ec90b75721b2ccfa1aa34b8
--- /dev/null
+++ b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ReturnAnnotationChecker.java
@@ -0,0 +1,145 @@
+package org.chromium.devtools.jsdoc.checks;
+
+import com.google.javascript.rhino.head.Token;
+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 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;
+ default:
+ 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 function) {
+ String functionName = getFunctionName(function.functionNode);
+ if (functionName == null) {
+ return;
+ }
+ boolean isApiFunction = !functionName.startsWith("_")
+ && (function.isTopLevelFunction()
+ || (function.enclosingType != null
+ && isPlainTopLevelFunction(function.enclosingFunctionRecord)));
+ Comment jsDocNode = AstUtil.getJsDocNode(function.functionNode);
+
+ boolean isReturningFunction = valueReturningFunctions.contains(function);
+ boolean isInterfaceFunction =
+ function.enclosingType != null && function.enclosingType.isInterface;
+ int invalidAnnotationIndex =
+ invalidReturnsAnnotationIndex(getState().getNodeText(jsDocNode));
+ if (invalidAnnotationIndex != -1) {
+ String suggestedResolution = (isReturningFunction || isInterfaceFunction)
+ ? "should be @return instead"
+ : "please remove, as function does not return value";
+ getContext().reportErrorInNode(jsDocNode, invalidAnnotationIndex,
+ String.format("invalid @returns annotation found - %s", suggestedResolution));
+ return;
+ }
+ AstNode functionNameNode = getFunctionNameNode(function.functionNode);
+ if (functionNameNode == null) {
+ return;
+ }
+
+ if (isReturningFunction) {
+ if (!function.hasReturnAnnotation() && isApiFunction) {
+ getContext().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 (function.hasReturnAnnotation()
+ && !isInterfaceFunction
+ && !throwingFunctions.contains(function)) {
+ getContext().reportErrorInNode(functionNameNode, 0,
+ "@return annotation found, yet function does not return value");
+ }
+ }
+ }
+
+ private static boolean isPlainTopLevelFunction(FunctionRecord record) {
+ return record != null && record.isTopLevelFunction()
+ && (record.enclosingType == null && !record.isConstructor);
+ }
+
+ private String getFunctionName(FunctionNode functionNode) {
+ AstNode nameNode = getFunctionNameNode(functionNode);
+ return nameNode == null ? null : 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;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698