Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / scripts / jsdoc-validator / src / org / chromium / devtools / jsdoc / checks / MethodAnnotationChecker.java
1 package org.chromium.devtools.jsdoc.checks;
2
3 import com.google.common.base.Joiner;
4 import com.google.javascript.jscomp.NodeUtil;
5 import com.google.javascript.rhino.JSDocInfo;
6 import com.google.javascript.rhino.Node;
7 import com.google.javascript.rhino.Token;
8
9 import java.util.HashSet;
10 import java.util.Set;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13
14 public final class MethodAnnotationChecker extends ContextTrackingChecker {
15
16     private static final Pattern PARAM_PATTERN =
17             Pattern.compile("^[^@\n]*@param\\s+(\\{.+\\}\\s+)?([^\\s]+).*$", Pattern.MULTILINE);
18
19     private static final Pattern INVALID_RETURN_PATTERN =
20             Pattern.compile("^[^@\n]*(@)return(?:s.*|\\s+[^{]*)$", Pattern.MULTILINE);
21
22     private final Set<FunctionRecord> valueReturningFunctions = new HashSet<>();
23     private final Set<FunctionRecord> throwingFunctions = new HashSet<>();
24
25     @Override
26     public void enterNode(Node node) {
27         switch (node.getType()) {
28         case Token.FUNCTION:
29             handleFunction(node);
30             break;
31         case Token.RETURN:
32             handleReturn(node);
33             break;
34         case Token.THROW:
35             handleThrow();
36             break;
37         default:
38             break;
39         }
40     }
41
42     private void handleFunction(Node functionNode) {
43         FunctionRecord function = getState().getCurrentFunctionRecord();
44         if (function == null || function.parameterNames.size() == 0) {
45             return;
46         }
47         String[] nonAnnotatedParams = getNonAnnotatedParamData(function);
48         if (nonAnnotatedParams.length > 0
49             && function.parameterNames.size() != nonAnnotatedParams.length) {
50             reportErrorAtOffset(function.info.getOriginalCommentPosition(),
51                     String.format(
52                             "No @param JSDoc tag found for parameters: [%s]",
53                             Joiner.on(',').join(nonAnnotatedParams)));
54         }
55     }
56
57     private String[] getNonAnnotatedParamData(FunctionRecord function) {
58         if (function.info == null) {
59             return new String[0];
60         }
61         Set<String> formalParamNames = new HashSet<>();
62         for (int i = 0; i < function.parameterNames.size(); ++i) {
63             String paramName = function.parameterNames.get(i);
64             if (!formalParamNames.add(paramName)) {
65                 reportErrorAtNodeStart(function.functionNode,
66                         String.format("Duplicate function argument name: %s", paramName));
67             }
68         }
69         Matcher m = PARAM_PATTERN.matcher(function.info.getOriginalCommentString());
70         while (m.find()) {
71             String paramType = m.group(1);
72             if (paramType == null) {
73                 reportErrorAtOffset(function.info.getOriginalCommentPosition() + m.start(2),
74                         String.format(
75                                 "Invalid @param annotation found -"
76                                 + " should be \"@param {<type>} paramName\""));
77             } else {
78                 formalParamNames.remove(m.group(2));
79             }
80         }
81         return formalParamNames.toArray(new String[formalParamNames.size()]);
82     }
83
84     private void handleReturn(Node node) {
85         if (node.getFirstChild() == null || AstUtil.parentOfType(node, Token.ASSIGN) != null) {
86             return;
87         }
88
89         FunctionRecord record = getState().getCurrentFunctionRecord();
90         if (record == null) {
91             return;
92         }
93         Node nameNode = getFunctionNameNode(record.functionNode);
94         if (nameNode == null) {
95             return;
96         }
97         valueReturningFunctions.add(record);
98     }
99
100     private void handleThrow() {
101         FunctionRecord record = getState().getCurrentFunctionRecord();
102         if (record == null) {
103             return;
104         }
105         Node nameNode = getFunctionNameNode(record.functionNode);
106         if (nameNode == null) {
107             return;
108         }
109         throwingFunctions.add(record);
110     }
111
112     @Override
113     public void leaveNode(Node node) {
114         if (node.getType() != Token.FUNCTION) {
115             return;
116         }
117
118         FunctionRecord record = getState().getCurrentFunctionRecord();
119         if (record != null) {
120             checkFunctionAnnotation(record);
121         }
122     }
123
124     @SuppressWarnings("unused")
125     private void checkFunctionAnnotation(FunctionRecord function) {
126         String functionName = getFunctionName(function.functionNode);
127         if (functionName == null) {
128             return;
129         }
130         String[] parts = functionName.split("\\.");
131         functionName = parts[parts.length - 1];
132         boolean isApiFunction = !functionName.startsWith("_")
133                 && (function.isTopLevelFunction()
134                         || (function.enclosingType != null
135                                 && isPlainTopLevelFunction(function.enclosingFunctionRecord)));
136
137         boolean isReturningFunction = valueReturningFunctions.contains(function);
138         boolean isInterfaceFunction =
139                 function.enclosingType != null && function.enclosingType.isInterface();
140         int invalidAnnotationIndex =
141                 invalidReturnAnnotationIndex(function.info);
142         if (invalidAnnotationIndex != -1) {
143             String suggestedResolution = (isReturningFunction || isInterfaceFunction)
144                     ? "should be \"@return {<type>}\""
145                     : "please remove, as function does not return value";
146             getContext().reportErrorAtOffset(
147                     function.info.getOriginalCommentPosition() + invalidAnnotationIndex,
148                     String.format(
149                             "invalid return type annotation found - %s", suggestedResolution));
150             return;
151         }
152         Node functionNameNode = getFunctionNameNode(function.functionNode);
153         if (functionNameNode == null) {
154             return;
155         }
156
157         if (isReturningFunction) {
158             if (!function.isConstructor() && !function.hasReturnAnnotation() && isApiFunction) {
159                 reportErrorAtNodeStart(
160                         functionNameNode,
161                         "@return annotation is required for API functions that return value");
162             }
163         } else {
164             // A @return-function that does not actually return anything and
165             // is intended to be overridden in subclasses must throw.
166             if (function.hasReturnAnnotation()
167                     && !isInterfaceFunction
168                     && !throwingFunctions.contains(function)) {
169                 reportErrorAtNodeStart(functionNameNode,
170                         "@return annotation found, yet function does not return value");
171             }
172         }
173     }
174
175     private static boolean isPlainTopLevelFunction(FunctionRecord record) {
176         return record != null && record.isTopLevelFunction()
177                 && (record.enclosingType == null && !record.isConstructor());
178     }
179
180     private String getFunctionName(Node functionNode) {
181         Node nameNode = getFunctionNameNode(functionNode);
182         return nameNode == null ? null : getState().getNodeText(nameNode);
183     }
184
185     private static int invalidReturnAnnotationIndex(JSDocInfo info) {
186         if (info == null) {
187             return -1;
188         }
189         Matcher m = INVALID_RETURN_PATTERN.matcher(info.getOriginalCommentString());
190         return m.find() ? m.start(1) : -1;
191     }
192
193     private static Node getFunctionNameNode(Node functionNode) {
194         // FIXME: Do not require annotation for assignment-RHS functions.
195         return AstUtil.getFunctionNameNode(functionNode);
196     }
197 }