1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <android-base/strings.h>
18 #include <hidl-util/StringHelper.h>
19
20 #include <algorithm>
21 #include <string>
22 #include <vector>
23
24 #include "AST.h"
25 #include "DocComment.h"
26 #include "Interface.h"
27 #include "Lint.h"
28 #include "LintRegistry.h"
29 #include "Method.h"
30 #include "Reference.h"
31
32 namespace android {
33
34 // returns true if the line contained a prefix and false otherwise
getFirstWordAfterPrefix(const std::string & str,const std::string & prefix,std::string * out)35 static bool getFirstWordAfterPrefix(const std::string& str, const std::string& prefix,
36 std::string* out) {
37 std::string line = str;
38 if (base::StartsWith(line, prefix)) {
39 line = StringHelper::LTrim(line, prefix);
40
41 // check line has other stuff and that there is a space between return and the rest
42 if (!base::StartsWith(line, " ")) {
43 *out = "";
44 return true;
45 }
46
47 line = StringHelper::LTrimAll(line, " ");
48
49 std::vector<std::string> words = base::Split(line, " ");
50 *out = words.empty() ? "" : words.at(0);
51 return true;
52 }
53
54 return false;
55 }
56
isNameInList(const std::string & name,const std::vector<NamedReference<Type> * > & refs)57 static bool isNameInList(const std::string& name, const std::vector<NamedReference<Type>*>& refs) {
58 return std::any_of(refs.begin(), refs.end(), [&](const NamedReference<Type>* namedRef) -> bool {
59 return namedRef->name() == name;
60 });
61 }
62
isSubsequence(const std::vector<NamedReference<Type> * > & refs,const std::vector<std::string> & subsequence)63 static bool isSubsequence(const std::vector<NamedReference<Type>*>& refs,
64 const std::vector<std::string>& subsequence) {
65 if (subsequence.empty()) return true;
66
67 auto it = subsequence.begin();
68 auto subEnd = subsequence.end();
69 for (const NamedReference<Type>* namedRef : refs) {
70 if (namedRef->name() == *it) it++;
71
72 if (it == subEnd) return true;
73 }
74
75 // Should be false here
76 return it == subEnd;
77 }
78
methodDocComments(const AST & ast,std::vector<Lint> * errors)79 static void methodDocComments(const AST& ast, std::vector<Lint>* errors) {
80 const Interface* iface = ast.getInterface();
81 if (iface == nullptr) {
82 // no interfaces so no methods
83 return;
84 }
85
86 for (const Method* method : iface->isIBase() ? iface->methods() : iface->userDefinedMethods()) {
87 const DocComment* docComment = method->getDocComment();
88 if (docComment == nullptr) continue;
89
90 bool returnRefFound = false;
91
92 std::vector<std::string> dcArgs;
93 std::vector<std::string> dcReturns;
94
95 // want a copy so that it can be mutated
96 for (const std::string& line : docComment->lines()) {
97 std::string returnName;
98
99 if (bool foundPrefix = getFirstWordAfterPrefix(line, "@return", &returnName);
100 foundPrefix) {
101 if (returnName.empty()) {
102 errors->push_back(Lint(WARNING, docComment->location())
103 << "@return should be followed by a return parameter.\n");
104 continue;
105 }
106
107 returnRefFound = true;
108 if (!isNameInList(returnName, method->results())) {
109 errors->push_back(Lint(WARNING, docComment->location())
110 << "@return " << returnName
111 << " is not a return parameter of the method "
112 << method->name() << ".\n");
113 } else {
114 if (std::find(dcReturns.begin(), dcReturns.end(), returnName) !=
115 dcReturns.end()) {
116 errors->push_back(
117 Lint(WARNING, docComment->location())
118 << "@return " << returnName
119 << " was referenced multiple times in the same doc comment.\n");
120 } else {
121 dcReturns.push_back(returnName);
122 }
123 }
124
125 continue;
126 }
127
128 if (bool foundPrefix = getFirstWordAfterPrefix(line, "@param", &returnName);
129 foundPrefix) {
130 if (returnName.empty()) {
131 errors->push_back(Lint(WARNING, docComment->location())
132 << "@param should be followed by a parameter name.\n");
133 continue;
134 }
135
136 if (returnRefFound) {
137 errors->push_back(Lint(WARNING, docComment->location())
138 << "Found @param " << returnName
139 << " after a @return declaration. All @param references "
140 << "should come before @return references.\n");
141 }
142
143 if (!isNameInList(returnName, method->args())) {
144 errors->push_back(Lint(WARNING, docComment->location())
145 << "@param " << returnName
146 << " is not an argument to the method " << method->name()
147 << ".\n");
148 } else {
149 if (std::find(dcArgs.begin(), dcArgs.end(), returnName) != dcArgs.end()) {
150 errors->push_back(
151 Lint(WARNING, docComment->location())
152 << "@param " << returnName
153 << " was referenced multiple times in the same doc comment.\n");
154 } else {
155 dcArgs.push_back(returnName);
156 }
157 }
158 }
159 }
160
161 if (!isSubsequence(method->results(), dcReturns)) {
162 errors->push_back(Lint(WARNING, docComment->location())
163 << "@return references should be ordered the same way they show up "
164 << "in the return parameter list.\n");
165 }
166 if (!isSubsequence(method->args(), dcArgs)) {
167 errors->push_back(Lint(WARNING, docComment->location())
168 << "@param references should be ordered the same way they show up in "
169 << "the argument list.\n");
170 }
171 }
172 }
173
174 REGISTER_LINT(methodDocComments);
175 } // namespace android
176