blob: 1005c417159c16de9dad9d98ac84fc704597913f [file] [log] [blame]
Jooyung Hand4fe00e2021-01-11 16:21:53 +09001/*
2 * Copyright (C) 2021, 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#include "comments.h"
17
18#include <android-base/result.h>
19#include <android-base/strings.h>
20
21#include <optional>
Jooyung Han24effbf2021-01-16 10:24:03 +090022#include <regex>
Jooyung Hand4fe00e2021-01-11 16:21:53 +090023#include <string>
24#include <vector>
25
26#include "logging.h"
27
28using android::base::EndsWith;
29using android::base::Error;
30using android::base::Join;
31using android::base::Result;
32using android::base::Split;
33using android::base::StartsWith;
34using android::base::Trim;
35
36namespace android {
37namespace aidl {
38
39namespace {
40
41static const std::string_view kLineCommentBegin = "//";
42static const std::string_view kBlockCommentBegin = "/*";
43static const std::string_view kBlockCommentEnd = "*/";
Jooyung Han24effbf2021-01-16 10:24:03 +090044static const std::string kTagDeprecated = "@deprecated";
45static const std::regex kTagHideRegex{"@hide\\b"};
Jooyung Hand4fe00e2021-01-11 16:21:53 +090046
47std::string ConsumePrefix(const std::string& s, std::string_view prefix) {
48 AIDL_FATAL_IF(!StartsWith(s, prefix), AIDL_LOCATION_HERE);
49 return s.substr(prefix.size());
50}
51
52std::string ConsumeSuffix(const std::string& s, std::string_view suffix) {
53 AIDL_FATAL_IF(!EndsWith(s, suffix), AIDL_LOCATION_HERE);
54 return s.substr(0, s.size() - suffix.size());
55}
56
57struct BlockTag {
58 std::string name;
59 std::string description;
60};
61
Jooyung Han24effbf2021-01-16 10:24:03 +090062struct Comment {
Jooyung Hand4fe00e2021-01-11 16:21:53 +090063 enum class Type { LINE, BLOCK };
64 Type type;
65 std::string body;
66
67 std::vector<std::string> TrimmedLines() const;
68 std::vector<BlockTag> BlockTags() const;
69};
70
Jooyung Han24effbf2021-01-16 10:24:03 +090071// Removes comment markers: //, /*, */, optional leading "*" in doc/block comments
Jooyung Hand4fe00e2021-01-11 16:21:53 +090072// - keeps leading spaces, but trims trailing spaces
73// - keeps empty lines
Jooyung Han24effbf2021-01-16 10:24:03 +090074std::vector<std::string> Comment::TrimmedLines() const {
Jooyung Hand4fe00e2021-01-11 16:21:53 +090075 if (type == Type::LINE) return std::vector{ConsumePrefix(body, kLineCommentBegin)};
76
77 std::string stripped = ConsumeSuffix(ConsumePrefix(body, kBlockCommentBegin), kBlockCommentEnd);
78
79 std::vector<std::string> lines;
80 bool found_first_line = false;
81
82 for (auto& line : Split(stripped, "\n")) {
83 // Delete prefixes like " * ", " *", or " ".
84 size_t idx = 0;
85 for (; idx < line.size() && isspace(line[idx]); idx++)
86 ;
87 if (idx < line.size() && line[idx] == '*') idx++;
88 if (idx < line.size() && line[idx] == ' ') idx++;
89
90 const std::string& sanitized_line = line.substr(idx);
91 size_t i = sanitized_line.size();
92 for (; i > 0 && isspace(sanitized_line[i - 1]); i--)
93 ;
94
95 // Either the size is 0 or everything was whitespace.
96 bool is_empty_line = i == 0;
97
98 found_first_line = found_first_line || !is_empty_line;
99 if (!found_first_line) continue;
100
101 // if is_empty_line, i == 0 so substr == ""
102 lines.push_back(sanitized_line.substr(0, i));
103 }
104 // remove trailing empty lines
105 while (!lines.empty() && Trim(lines.back()).empty()) {
106 lines.pop_back();
107 }
108 return lines;
109}
110
Jooyung Han24effbf2021-01-16 10:24:03 +0900111std::vector<BlockTag> Comment::BlockTags() const {
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900112 std::vector<BlockTag> tags;
113
114 // current tag and paragraph
115 std::string tag;
116 std::vector<std::string> paragraph;
117
118 auto end_paragraph = [&]() {
119 if (tag.empty()) {
120 paragraph.clear();
121 return;
122 }
123 // paragraph lines are trimed at both ends
124 tags.push_back({tag, Join(paragraph, " ")});
125 tag.clear();
126 paragraph.clear();
127 };
128
129 for (const auto& line : TrimmedLines()) {
130 size_t idx = 0;
131 // skip leading spaces
132 for (; idx < line.size() && isspace(line[idx]); idx++)
133 ;
134
135 if (idx == line.size()) {
136 // skip empty lines
137 } else if (line[idx] == '@') {
138 // end the current paragraph before reading a new block tag (+ description paragraph)
139 end_paragraph();
140
141 size_t end_idx = idx + 1;
142 for (; end_idx < line.size() && isalpha(line[end_idx]); end_idx++)
143 ;
144
145 tag = line.substr(idx, end_idx - idx);
146
147 if (end_idx < line.size() && line[end_idx] == ' ') end_idx++;
148 // skip empty line
149 if (end_idx < line.size()) {
150 paragraph.push_back(line.substr(end_idx));
151 }
152 } else {
153 // gather paragraph lines with leading spaces trimmed
154 paragraph.push_back(line.substr(idx));
155 }
156 }
157
158 end_paragraph();
159
160 return tags;
161}
162
163// TODO(b/177276676) remove this when comments are kept as parsed in AST
Jooyung Han24effbf2021-01-16 10:24:03 +0900164Result<std::vector<Comment>> ParseComments(const std::string& comments) {
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900165 enum ParseState {
166 INITIAL,
167 SLASH,
168 SLASHSLASH,
169 SLASHSTAR,
170 STAR,
171 };
172 ParseState st = INITIAL;
173 std::string body;
Jooyung Han24effbf2021-01-16 10:24:03 +0900174 std::vector<Comment> result;
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900175 for (const auto& c : comments) {
176 switch (st) {
177 case INITIAL: // trim ws & newlines
178 if (c == '/') {
179 st = SLASH;
180 body += c;
181 } else if (std::isspace(c)) {
182 // skip whitespaces outside comments
183 } else {
184 return Error() << "expecing / or space, but got unknown: " << c;
185 }
186 break;
187 case SLASH:
188 if (c == '/') {
189 st = SLASHSLASH;
190 body += c;
191 } else if (c == '*') {
192 st = SLASHSTAR;
193 body += c;
194 } else {
195 return Error() << "expecting / or *, but got unknown: " << c;
196 }
197 break;
198 case SLASHSLASH:
199 if (c == '\n') {
200 st = INITIAL;
Jooyung Han24effbf2021-01-16 10:24:03 +0900201 result.push_back({Comment::Type::LINE, std::move(body)});
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900202 body.clear();
203 } else {
204 body += c;
205 }
206 break;
207 case SLASHSTAR:
208 body += c;
209 if (c == '*') {
210 st = STAR;
211 }
212 break;
213 case STAR: // read "*", about to close
214 body += c;
215 if (c == '/') { // close!
216 st = INITIAL;
Jooyung Han24effbf2021-01-16 10:24:03 +0900217 result.push_back({Comment::Type::BLOCK, std::move(body)});
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900218 body.clear();
219 } else if (c == '*') {
220 // about to close...
221 } else {
222 st = SLASHSTAR;
223 }
224 break;
225 default:
226 return Error() << "unexpected state: " << st;
227 }
228 }
229 return result;
230}
231
232} // namespace
233
Jooyung Han24effbf2021-01-16 10:24:03 +0900234bool HasHideInComments(const std::string& comments) {
235 auto result = ParseComments(comments);
236 AIDL_FATAL_IF(!result.ok(), AIDL_LOCATION_HERE) << result.error();
237
238 for (const auto& c : *result) {
239 if (c.type == Comment::Type::LINE) continue;
240 if (std::regex_search(c.body, kTagHideRegex)) {
241 return true;
242 }
243 }
244 return false;
245}
246
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900247// Finds @deprecated tag and returns it with optional note which follows the tag.
248std::optional<Deprecated> FindDeprecated(const std::string& comments) {
249 auto result = ParseComments(comments);
250 AIDL_FATAL_IF(!result.ok(), AIDL_LOCATION_HERE) << result.error();
251
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900252 for (const auto& c : *result) {
Jooyung Han24effbf2021-01-16 10:24:03 +0900253 if (c.type == Comment::Type::LINE) continue;
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900254 for (const auto& [name, description] : c.BlockTags()) {
255 // take the first @deprecated
256 if (kTagDeprecated == name) {
257 return Deprecated{description};
258 }
259 }
260 }
261 return std::nullopt;
262}
263
264} // namespace aidl
265} // namespace android