1 package org.chromium.devtools.jsdoc.checks;
3 import com.google.javascript.rhino.head.Token;
4 import com.google.javascript.rhino.head.ast.Assignment;
5 import com.google.javascript.rhino.head.ast.AstNode;
6 import com.google.javascript.rhino.head.ast.Comment;
7 import com.google.javascript.rhino.head.ast.FunctionNode;
8 import com.google.javascript.rhino.head.ast.ObjectProperty;
9 import com.google.javascript.rhino.head.ast.ReturnStatement;
11 import java.util.ArrayDeque;
12 import java.util.Deque;
13 import java.util.HashMap;
16 public final class RequiredReturnAnnotationCheck extends ValidationCheck {
18 private final Deque<FunctionNode> functionStack = new ArrayDeque<>(16);
19 private final Map<FunctionNode, Boolean> returningFunctions = new HashMap<>();
22 public void doVisit(AstNode node) {
23 switch (node.getType()) {
25 if (((ReturnStatement) node).getReturnValue() != null &&
26 !AstUtil.hasParentOfType(node, Token.ASSIGN)) {
27 FunctionNode topFunctionNode = functionStack.peekFirst();
28 if (topFunctionNode == null) {
31 AstNode nameNode = getFunctionNameNode(topFunctionNode);
32 if (nameNode == null) {
35 String name = getContext().getNodeText(nameNode);
36 boolean isApiFunction = functionStack.size() == 1 && !name.startsWith("_");
37 returningFunctions.put(topFunctionNode, isApiFunction);
41 functionStack.push((FunctionNode) node);
47 public void didVisit(AstNode node) {
48 if (node.getType() != Token.FUNCTION) {
52 FunctionNode functionNode = functionStack.remove();
53 checkFunctionAnnotation(functionNode);
54 returningFunctions.remove(functionNode);
57 @SuppressWarnings("unused")
58 private void checkFunctionAnnotation(FunctionNode functionNode) {
59 Boolean returnsValueBoolean = returningFunctions.get(functionNode);
60 boolean isReturningFunction = returnsValueBoolean != null;
61 boolean isApiFunction = isReturningFunction && returnsValueBoolean.booleanValue();
62 Comment jsDocNode = getJsDocNode(functionNode);
63 String jsDoc = jsDocNode != null ? jsDocNode.getValue() : null;
65 int invalidAnnotationIndex = invalidReturnsAnnotationIndex(jsDoc);
66 if (invalidAnnotationIndex != -1) {
67 // FIXME: Report that no @return should be present for non-returning functions,
68 // once @interface methods with @return are handled correctly.
69 String suggestedResolution = "should be @return instead";
70 getContext().reportErrorInNode(jsDocNode, invalidAnnotationIndex,
71 String.format("invalid @returns annotation found - %s", suggestedResolution));
74 AstNode functionNameNode = getFunctionNameNode(functionNode);
75 if (functionNameNode == null) {
79 if (isReturningFunction) {
80 if (!hasReturnAnnotation(jsDoc) && isApiFunction) {
81 getContext().reportErrorInNode(functionNameNode, 0,
82 "@return annotation is required for API functions that return value");
85 // FIXME: Enable this once @interface methods with @return are handled correctly.
86 if (false && hasReturnAnnotation(jsDoc)) {
87 getContext().reportErrorInNode(functionNameNode, 0,
88 "@return annotation found, yet function does not return value");
93 private static boolean hasReturnAnnotation(String jsDoc) {
94 return jsDoc != null && jsDoc.contains("@return");
97 private static int invalidReturnsAnnotationIndex(String jsDoc) {
98 return jsDoc == null ? -1 : jsDoc.indexOf("@returns");
101 private static AstNode getFunctionNameNode(FunctionNode functionNode) {
102 AstNode nameNode = functionNode.getFunctionName();
103 if (nameNode != null) {
107 if (AstUtil.hasParentOfType(functionNode, Token.COLON)) {
108 return ((ObjectProperty) functionNode.getParent()).getLeft();
110 // Do not require annotation for assignment-RHS functions.
114 private static Comment getJsDocNode(FunctionNode functionNode) {
115 Comment jsDocNode = functionNode.getJsDocNode();
116 if (jsDocNode != null) {
120 // reader.onloadend = function() {...}
121 if (AstUtil.hasParentOfType(functionNode, Token.ASSIGN)) {
122 Assignment assignment = (Assignment) functionNode.getParent();
123 if (assignment.getRight() == functionNode) {
124 jsDocNode = assignment.getJsDocNode();
125 if (jsDocNode != null) {
131 if (AstUtil.hasParentOfType(functionNode, Token.COLON)) {
132 jsDocNode = ((ObjectProperty) functionNode.getParent()).getLeft().getJsDocNode();
133 if (jsDocNode != null) {