Upstream version 5.34.98.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / scripts / jsdoc-validator / src / org / chromium / devtools / jsdoc / checks / RequiredReturnAnnotationCheck.java
1 package org.chromium.devtools.jsdoc.checks;
2
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;
10
11 import java.util.ArrayDeque;
12 import java.util.Deque;
13 import java.util.HashMap;
14 import java.util.Map;
15
16 public final class RequiredReturnAnnotationCheck extends ValidationCheck {
17
18     private final Deque<FunctionNode> functionStack = new ArrayDeque<>(16);
19     private final Map<FunctionNode, Boolean> returningFunctions = new HashMap<>();
20
21     @Override
22     public void doVisit(AstNode node) {
23         switch (node.getType()) {
24         case Token.RETURN:
25             if (((ReturnStatement) node).getReturnValue() != null &&
26                     !AstUtil.hasParentOfType(node, Token.ASSIGN)) {
27                 FunctionNode topFunctionNode = functionStack.peekFirst();
28                 if (topFunctionNode == null) {
29                     return;
30                 }
31                 AstNode nameNode = getFunctionNameNode(topFunctionNode);
32                 if (nameNode == null) {
33                     return;
34                 }
35                 String name = getContext().getNodeText(nameNode);
36                 boolean isApiFunction = functionStack.size() == 1 && !name.startsWith("_");
37                 returningFunctions.put(topFunctionNode, isApiFunction);
38             }
39             break;
40         case Token.FUNCTION:
41             functionStack.push((FunctionNode) node);
42             break;
43         }
44     }
45
46     @Override
47     public void didVisit(AstNode node) {
48         if (node.getType() != Token.FUNCTION) {
49             return;
50         }
51
52         FunctionNode functionNode = functionStack.remove();
53         checkFunctionAnnotation(functionNode);
54         returningFunctions.remove(functionNode);
55     }
56
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;
64
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));
72             return;
73         }
74         AstNode functionNameNode = getFunctionNameNode(functionNode);
75         if (functionNameNode == null) {
76             return;
77         }
78
79         if (isReturningFunction) {
80             if (!hasReturnAnnotation(jsDoc) && isApiFunction) {
81                 getContext().reportErrorInNode(functionNameNode, 0,
82                         "@return annotation is required for API functions that return value");
83             }
84         } else {
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");
89             }
90         }
91     }
92
93     private static boolean hasReturnAnnotation(String jsDoc) {
94         return jsDoc != null && jsDoc.contains("@return");
95     }
96
97     private static int invalidReturnsAnnotationIndex(String jsDoc) {
98         return jsDoc == null ? -1 : jsDoc.indexOf("@returns");
99     }
100
101     private static AstNode getFunctionNameNode(FunctionNode functionNode) {
102         AstNode nameNode = functionNode.getFunctionName();
103         if (nameNode != null) {
104             return nameNode;
105         }
106
107         if (AstUtil.hasParentOfType(functionNode, Token.COLON)) {
108             return ((ObjectProperty) functionNode.getParent()).getLeft();
109         }
110         // Do not require annotation for assignment-RHS functions.
111         return null;
112     }
113
114     private static Comment getJsDocNode(FunctionNode functionNode) {
115         Comment jsDocNode = functionNode.getJsDocNode();
116         if (jsDocNode != null) {
117             return jsDocNode;
118         }
119
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) {
126                     return jsDocNode;
127                 }
128             }
129         }
130
131         if (AstUtil.hasParentOfType(functionNode, Token.COLON)) {
132             jsDocNode = ((ObjectProperty) functionNode.getParent()).getLeft().getJsDocNode();
133             if (jsDocNode != null) {
134                 return jsDocNode;
135             }
136         }
137         return null;
138     }
139 }