• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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