blob: 4b724b2d0e2d3fa3072fa4c99e8cde36ac4b9a27 [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) {
Jooyung Han161bb0f2021-01-21 12:30:16 +090048 AIDL_FATAL_IF(!StartsWith(s, prefix), AIDL_LOCATION_HERE)
49 << "'" << s << "' has no prefix '" << prefix << "'";
Jooyung Hand4fe00e2021-01-11 16:21:53 +090050 return s.substr(prefix.size());
51}
52
53std::string ConsumeSuffix(const std::string& s, std::string_view suffix) {
54 AIDL_FATAL_IF(!EndsWith(s, suffix), AIDL_LOCATION_HERE);
55 return s.substr(0, s.size() - suffix.size());
56}
57
58struct BlockTag {
59 std::string name;
60 std::string description;
61};
62
Jooyung Han8451a202021-01-16 03:07:06 +090063// Removes comment markers: //, /*, */, optional leading "*" in block comments
Jooyung Hand4fe00e2021-01-11 16:21:53 +090064// - keeps leading spaces, but trims trailing spaces
65// - keeps empty lines
Jooyung Han8451a202021-01-16 03:07:06 +090066std::vector<std::string> TrimmedLines(const Comment& c) {
Jooyung Han161bb0f2021-01-21 12:30:16 +090067 if (c.type == Comment::Type::LINE) {
68 return std::vector{ConsumePrefix(c.body, kLineCommentBegin)};
69 }
Jooyung Hand4fe00e2021-01-11 16:21:53 +090070
Jooyung Han8451a202021-01-16 03:07:06 +090071 std::string stripped = ConsumeSuffix(ConsumePrefix(c.body, kBlockCommentBegin), kBlockCommentEnd);
Jooyung Hand4fe00e2021-01-11 16:21:53 +090072
73 std::vector<std::string> lines;
74 bool found_first_line = false;
75
76 for (auto& line : Split(stripped, "\n")) {
77 // Delete prefixes like " * ", " *", or " ".
78 size_t idx = 0;
79 for (; idx < line.size() && isspace(line[idx]); idx++)
80 ;
81 if (idx < line.size() && line[idx] == '*') idx++;
82 if (idx < line.size() && line[idx] == ' ') idx++;
83
84 const std::string& sanitized_line = line.substr(idx);
85 size_t i = sanitized_line.size();
86 for (; i > 0 && isspace(sanitized_line[i - 1]); i--)
87 ;
88
89 // Either the size is 0 or everything was whitespace.
90 bool is_empty_line = i == 0;
91
92 found_first_line = found_first_line || !is_empty_line;
93 if (!found_first_line) continue;
94
95 // if is_empty_line, i == 0 so substr == ""
96 lines.push_back(sanitized_line.substr(0, i));
97 }
98 // remove trailing empty lines
99 while (!lines.empty() && Trim(lines.back()).empty()) {
100 lines.pop_back();
101 }
102 return lines;
103}
104
Jooyung Han161bb0f2021-01-21 12:30:16 +0900105// Parses a block comment and returns block tags in the comment.
Jooyung Han8451a202021-01-16 03:07:06 +0900106std::vector<BlockTag> BlockTags(const Comment& c) {
Jooyung Han161bb0f2021-01-21 12:30:16 +0900107 AIDL_FATAL_IF(c.type != Comment::Type::BLOCK, AIDL_LOCATION_HERE);
108
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900109 std::vector<BlockTag> tags;
110
111 // current tag and paragraph
112 std::string tag;
113 std::vector<std::string> paragraph;
114
115 auto end_paragraph = [&]() {
116 if (tag.empty()) {
117 paragraph.clear();
118 return;
119 }
120 // paragraph lines are trimed at both ends
121 tags.push_back({tag, Join(paragraph, " ")});
122 tag.clear();
123 paragraph.clear();
124 };
125
Jooyung Han8451a202021-01-16 03:07:06 +0900126 for (const auto& line : TrimmedLines(c)) {
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900127 size_t idx = 0;
128 // skip leading spaces
129 for (; idx < line.size() && isspace(line[idx]); idx++)
130 ;
131
132 if (idx == line.size()) {
133 // skip empty lines
134 } else if (line[idx] == '@') {
135 // end the current paragraph before reading a new block tag (+ description paragraph)
136 end_paragraph();
137
138 size_t end_idx = idx + 1;
139 for (; end_idx < line.size() && isalpha(line[end_idx]); end_idx++)
140 ;
141
142 tag = line.substr(idx, end_idx - idx);
143
144 if (end_idx < line.size() && line[end_idx] == ' ') end_idx++;
145 // skip empty line
146 if (end_idx < line.size()) {
147 paragraph.push_back(line.substr(end_idx));
148 }
149 } else {
150 // gather paragraph lines with leading spaces trimmed
151 paragraph.push_back(line.substr(idx));
152 }
153 }
154
155 end_paragraph();
156
157 return tags;
158}
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900159
Jooyung Han161bb0f2021-01-21 12:30:16 +0900160} // namespace
161
162Comment::Comment(const std::string& body) : body(body) {
163 if (StartsWith(body, kLineCommentBegin)) {
164 type = Type::LINE;
165 } else if (StartsWith(body, kBlockCommentBegin) && EndsWith(body, kBlockCommentEnd)) {
166 type = Type::BLOCK;
167 } else {
168 AIDL_FATAL(AIDL_LOCATION_HERE) << "invalid comments body:" << body;
Jooyung Han24effbf2021-01-16 10:24:03 +0900169 }
Jooyung Han24effbf2021-01-16 10:24:03 +0900170}
171
Jooyung Han161bb0f2021-01-21 12:30:16 +0900172// Returns the immediate block comment from the list of comments.
173// Only the last/block comment can have the tag.
174//
175// /* @hide */
176// int x;
177//
178// But tags in line or distant comments don't count. In the following,
179// the variable 'x' is not hidden.
180//
181// // @hide
182// int x;
183//
184// /* @hide */
185// /* this is the immemediate comment to 'x' */
186// int x;
187//
188static std::optional<Comment> GetValidComment(const Comments& comments) {
189 if (!comments.empty() && comments.back().type == Comment::Type::BLOCK) {
190 return comments.back();
191 }
192 return std::nullopt;
193}
194
195// Sees if comments have the @hide tag.
196// Example: /** @hide */
197bool HasHideInComments(const Comments& comments) {
198 const auto valid_comment = GetValidComment(comments);
199 return valid_comment && std::regex_search(valid_comment->body, kTagHideRegex);
200}
201
202// Finds the @deprecated tag in comments and returns it with optional note which
203// follows the tag.
204// Example: /** @deprecated reason */
Jooyung Han8451a202021-01-16 03:07:06 +0900205std::optional<Deprecated> FindDeprecated(const Comments& comments) {
Jooyung Han161bb0f2021-01-21 12:30:16 +0900206 if (const auto valid_comment = GetValidComment(comments); valid_comment) {
207 for (const auto& [name, description] : BlockTags(comments.back())) {
Jooyung Hand4fe00e2021-01-11 16:21:53 +0900208 // take the first @deprecated
209 if (kTagDeprecated == name) {
210 return Deprecated{description};
211 }
212 }
213 }
214 return std::nullopt;
215}
216
217} // namespace aidl
218} // namespace android