aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJooyung Han <jooyung@google.com>2021-01-11 16:21:53 +0900
committerJooyung Han <jooyung@google.com>2021-01-16 00:43:20 +0000
commitd4fe00eaba940095f1a31f6c37bffee7d226da4f (patch)
tree54b368d942a70d90b6ba3a33c6a46015386c4959
parentb2f4fe66d452f48bd6a608286682604373c53a93 (diff)
downloadaidl-d4fe00eaba940095f1a31f6c37bffee7d226da4f.tar.gz
Emit deprecation note
@deprecated tag in comments can have optional note to explain the rationale for deprecation and/or to suggest a replacing entity. /** @deprecated use bar() */ void foo(); In this change, the deprecation note is emitted when specified. For example, in C++, deprecation note is emitted as following: void foo() __attribute__((deprecated("use bar()")) ... Bug: 174514415 Test: aidl_unittests Merged-In: I271af4d636e755a4d97f60d2daa6528acbe76a4e Change-Id: I271af4d636e755a4d97f60d2daa6528acbe76a4e (cherry picked from commit 6ec07c5482939803229445726044457383dbdaa9)
-rw-r--r--Android.bp1
-rw-r--r--aidl_language.cpp7
-rw-r--r--aidl_to_cpp_common.cpp11
-rw-r--r--aidl_to_cpp_common.h16
-rw-r--r--aidl_unittest.cpp59
-rw-r--r--code_writer.cpp19
-rw-r--r--code_writer.h2
-rw-r--r--comments.cpp249
-rw-r--r--comments.h31
-rw-r--r--generate_rust.cpp12
10 files changed, 373 insertions, 34 deletions
diff --git a/Android.bp b/Android.bp
index 22a0e2f5..7ef8746d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -71,6 +71,7 @@ cc_library_static {
"ast_cpp.cpp",
"ast_java.cpp",
"code_writer.cpp",
+ "comments.cpp",
"diagnostics.cpp",
"generate_aidl_mappings.cpp",
"generate_cpp.cpp",
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 287243a3..b52fc123 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -33,6 +33,7 @@
#include <android-base/strings.h>
#include "aidl_language_y.h"
+#include "comments.h"
#include "logging.h"
#include "aidl.h"
@@ -76,10 +77,6 @@ void AddHideComment(CodeWriter* writer) {
inline bool HasHideComment(const std::string& comment) {
return std::regex_search(comment, std::regex("@hide\\b"));
}
-
-inline bool HasDeprecatedComment(const std::string& comment) {
- return std::regex_search(comment, std::regex("@deprecated\\b"));
-}
} // namespace
AidlNode::AidlNode(const AidlLocation& location) : location_(location) {}
@@ -768,7 +765,7 @@ bool AidlCommentable::IsHidden() const {
}
bool AidlCommentable::IsDeprecated() const {
- return HasDeprecatedComment(GetComments());
+ return android::aidl::FindDeprecated(GetComments()).has_value();
}
AidlMember::AidlMember(const AidlLocation& location, const std::string& comments)
diff --git a/aidl_to_cpp_common.cpp b/aidl_to_cpp_common.cpp
index aa4bb1e8..435fa5f9 100644
--- a/aidl_to_cpp_common.cpp
+++ b/aidl_to_cpp_common.cpp
@@ -23,6 +23,7 @@
#include <unordered_map>
#include "ast_cpp.h"
+#include "comments.h"
#include "logging.h"
#include "os.h"
@@ -422,6 +423,16 @@ void GenerateToString(CodeWriter& out, const AidlUnionDecl& parcelable) {
out << "}\n";
}
+std::string GetDeprecatedAttribute(const AidlCommentable& type) {
+ if (auto deprecated = FindDeprecated(type.GetComments()); deprecated.has_value()) {
+ if (deprecated->note.empty()) {
+ return "__attribute__((deprecated))";
+ }
+ return "__attribute__((deprecated(" + QuotedEscape(deprecated->note) + ")))";
+ }
+ return "";
+}
+
const vector<string> UnionWriter::headers{
"cassert", // __assert for logging
"type_traits", // std::is_same_v
diff --git a/aidl_to_cpp_common.h b/aidl_to_cpp_common.h
index fe5043b6..368e724a 100644
--- a/aidl_to_cpp_common.h
+++ b/aidl_to_cpp_common.h
@@ -85,19 +85,13 @@ void GenerateParcelableComparisonOperators(CodeWriter& out, const AidlParcelable
void GenerateToString(CodeWriter& out, const AidlStructuredParcelable& parcelable);
void GenerateToString(CodeWriter& out, const AidlUnionDecl& parcelable);
-template <typename Stream, typename Type>
-void GenerateDeprecated(Stream& out, const Type& type) {
- if (type.IsDeprecated()) {
- out << " __attribute__((deprecated))";
- }
-}
+std::string GetDeprecatedAttribute(const AidlCommentable& type);
-template <typename Type>
-std::string GetDeprecatedAttribute(const Type& type) {
- if (type.IsDeprecated()) {
- return "__attribute__((deprecated))";
+template <typename Stream>
+void GenerateDeprecated(Stream& out, const AidlCommentable& type) {
+ if (auto deprecated = GetDeprecatedAttribute(type); !deprecated.empty()) {
+ out << " " + deprecated;
}
- return "";
}
struct ParcelWriterContext {
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 8e721731..da4f035e 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -740,14 +740,14 @@ TEST_P(AidlTest, SupportDeprecated) {
};
auto CheckDeprecated = [&](const std::string& filename, const std::string& contents,
- std::map<Options::Language, TestCase> expectation) {
+ std::vector<std::pair<Options::Language, TestCase>> expectations) {
io_delegate_.SetFileContents(filename, contents);
auto options = Options::From("aidl --lang=" + to_string(GetLanguage()) + " " + filename +
" --out=out --header_out=out");
EXPECT_EQ(0, ::android::aidl::compile_aidl(options, io_delegate_));
- if (auto it = expectation.find(GetLanguage()); it != expectation.end()) {
- const auto& test_case = it->second;
+ for (const auto& [lang, test_case] : expectations) {
+ if (lang != GetLanguage()) continue;
string output;
EXPECT_TRUE(io_delegate_.GetWrittenContents(test_case.output_file, &output))
<< base::Join(io_delegate_.ListOutputFiles(), ",");
@@ -755,17 +755,48 @@ TEST_P(AidlTest, SupportDeprecated) {
}
};
- CheckDeprecated("IFoo.aidl",
- "interface IFoo {\n"
- " /** @deprecated use bar() */\n"
- " List<String> foo();\n"
- "}",
- {
- {Options::Language::JAVA, {"out/IFoo.java", "@Deprecated"}},
- {Options::Language::CPP, {"out/IFoo.h", "__attribute__((deprecated"}},
- {Options::Language::NDK, {"out/aidl/IFoo.h", "__attribute__((deprecated"}},
- {Options::Language::RUST, {"out/IFoo.rs", "#[deprecated"}},
- });
+ // Emit escaped string for notes
+ CheckDeprecated(
+ "IFoo.aidl",
+ R"(interface IFoo {
+ /**
+ * @note asdf
+ * @deprecated a really long deprecation message
+ *
+ * which is really long
+ * @param foo bar
+ */
+ List<String> foo();
+ })",
+ {
+ {Options::Language::JAVA, {"out/IFoo.java", "@Deprecated"}},
+ {Options::Language::CPP,
+ {"out/IFoo.h",
+ R"(__attribute__((deprecated("a really long deprecation message which is really long"))))"}},
+ {Options::Language::NDK,
+ {"out/aidl/IFoo.h",
+ R"(__attribute__((deprecated("a really long deprecation message which is really long"))))"}},
+ {Options::Language::RUST,
+ {"out/IFoo.rs",
+ R"(#[deprecated = "a really long deprecation message which is really long"])"}},
+ });
+
+ // In AIDL @deprecated can be in any style of comments
+ CheckDeprecated(
+ "IFoo.aidl",
+ "interface IFoo {\n"
+ " // @deprecated use bar()\n"
+ " List<String> foo();\n"
+ "}",
+ {
+ {Options::Language::JAVA, {"out/IFoo.java", "@Deprecated"}},
+ // TODO(b/177276893) @deprecated should be in javadoc style comments
+ // {Options::Language::JAVA, {"out/IFoo.java", "/** @deprecated use bar() */"}},
+ {Options::Language::CPP, {"out/IFoo.h", "__attribute__((deprecated(\"use bar()\")))"}},
+ {Options::Language::NDK,
+ {"out/aidl/IFoo.h", "__attribute__((deprecated(\"use bar()\")))"}},
+ {Options::Language::RUST, {"out/IFoo.rs", "#[deprecated = \"use bar()\"]"}},
+ });
CheckDeprecated("Foo.aidl",
"parcelable Foo {\n"
diff --git a/code_writer.cpp b/code_writer.cpp
index 447b91f5..773a598f 100644
--- a/code_writer.cpp
+++ b/code_writer.cpp
@@ -21,6 +21,7 @@
#include <fstream>
#include <iostream>
#include <sstream>
+#include <unordered_map>
#include <vector>
#include <android-base/stringprintf.h>
@@ -132,5 +133,23 @@ CodeWriterPtr CodeWriter::ForString(std::string* buf) {
return CodeWriterPtr(new StringCodeWriter(buf));
}
+std::string QuotedEscape(const std::string& str) {
+ std::string result;
+ result += '"';
+ static const std::unordered_map<char, std::string> escape = {
+ {'"', "\\\""}, {'\\', "\\\\"}, {'\n', "\\n"}, {'\r', "\\r"}, {'\t', "\\t"}, {'\v', "\\v"},
+ };
+ for (auto c : str) {
+ auto it = escape.find(c);
+ if (it != escape.end()) {
+ result += it->second;
+ } else {
+ result += c;
+ }
+ }
+ result += '"';
+ return result;
+}
+
} // namespace aidl
} // namespace android
diff --git a/code_writer.h b/code_writer.h
index 870a2cd5..75013856 100644
--- a/code_writer.h
+++ b/code_writer.h
@@ -59,5 +59,7 @@ class CodeWriter {
bool start_of_line_ {true};
};
+std::string QuotedEscape(const std::string& str);
+
} // namespace aidl
} // namespace android
diff --git a/comments.cpp b/comments.cpp
new file mode 100644
index 00000000..5c25c671
--- /dev/null
+++ b/comments.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "comments.h"
+
+#include <android-base/result.h>
+#include <android-base/strings.h>
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "logging.h"
+
+using android::base::EndsWith;
+using android::base::Error;
+using android::base::Join;
+using android::base::Result;
+using android::base::Split;
+using android::base::StartsWith;
+using android::base::Trim;
+
+namespace android {
+namespace aidl {
+
+namespace {
+
+static const std::string_view kLineCommentBegin = "//";
+static const std::string_view kBlockCommentBegin = "/*";
+static const std::string_view kBlockCommentEnd = "*/";
+
+std::string ConsumePrefix(const std::string& s, std::string_view prefix) {
+ AIDL_FATAL_IF(!StartsWith(s, prefix), AIDL_LOCATION_HERE);
+ return s.substr(prefix.size());
+}
+
+std::string ConsumeSuffix(const std::string& s, std::string_view suffix) {
+ AIDL_FATAL_IF(!EndsWith(s, suffix), AIDL_LOCATION_HERE);
+ return s.substr(0, s.size() - suffix.size());
+}
+
+struct BlockTag {
+ std::string name;
+ std::string description;
+};
+
+struct Comments {
+ enum class Type { LINE, BLOCK };
+ Type type;
+ std::string body;
+
+ std::vector<std::string> TrimmedLines() const;
+ std::vector<BlockTag> BlockTags() const;
+};
+
+// Removes comment markers: //, /*, /**, */, optional leading "*" in doc/block comments
+// - keeps leading spaces, but trims trailing spaces
+// - keeps empty lines
+std::vector<std::string> Comments::TrimmedLines() const {
+ if (type == Type::LINE) return std::vector{ConsumePrefix(body, kLineCommentBegin)};
+
+ std::string stripped = ConsumeSuffix(ConsumePrefix(body, kBlockCommentBegin), kBlockCommentEnd);
+
+ std::vector<std::string> lines;
+ bool found_first_line = false;
+
+ for (auto& line : Split(stripped, "\n")) {
+ // Delete prefixes like " * ", " *", or " ".
+ size_t idx = 0;
+ for (; idx < line.size() && isspace(line[idx]); idx++)
+ ;
+ if (idx < line.size() && line[idx] == '*') idx++;
+ if (idx < line.size() && line[idx] == ' ') idx++;
+
+ const std::string& sanitized_line = line.substr(idx);
+ size_t i = sanitized_line.size();
+ for (; i > 0 && isspace(sanitized_line[i - 1]); i--)
+ ;
+
+ // Either the size is 0 or everything was whitespace.
+ bool is_empty_line = i == 0;
+
+ found_first_line = found_first_line || !is_empty_line;
+ if (!found_first_line) continue;
+
+ // if is_empty_line, i == 0 so substr == ""
+ lines.push_back(sanitized_line.substr(0, i));
+ }
+ // remove trailing empty lines
+ while (!lines.empty() && Trim(lines.back()).empty()) {
+ lines.pop_back();
+ }
+ return lines;
+}
+
+std::vector<BlockTag> Comments::BlockTags() const {
+ std::vector<BlockTag> tags;
+
+ // current tag and paragraph
+ std::string tag;
+ std::vector<std::string> paragraph;
+
+ auto end_paragraph = [&]() {
+ if (tag.empty()) {
+ paragraph.clear();
+ return;
+ }
+ // paragraph lines are trimed at both ends
+ tags.push_back({tag, Join(paragraph, " ")});
+ tag.clear();
+ paragraph.clear();
+ };
+
+ for (const auto& line : TrimmedLines()) {
+ size_t idx = 0;
+ // skip leading spaces
+ for (; idx < line.size() && isspace(line[idx]); idx++)
+ ;
+
+ if (idx == line.size()) {
+ // skip empty lines
+ } else if (line[idx] == '@') {
+ // end the current paragraph before reading a new block tag (+ description paragraph)
+ end_paragraph();
+
+ size_t end_idx = idx + 1;
+ for (; end_idx < line.size() && isalpha(line[end_idx]); end_idx++)
+ ;
+
+ tag = line.substr(idx, end_idx - idx);
+
+ if (end_idx < line.size() && line[end_idx] == ' ') end_idx++;
+ // skip empty line
+ if (end_idx < line.size()) {
+ paragraph.push_back(line.substr(end_idx));
+ }
+ } else {
+ // gather paragraph lines with leading spaces trimmed
+ paragraph.push_back(line.substr(idx));
+ }
+ }
+
+ end_paragraph();
+
+ return tags;
+}
+
+// TODO(b/177276676) remove this when comments are kept as parsed in AST
+Result<std::vector<Comments>> ParseComments(const std::string& comments) {
+ enum ParseState {
+ INITIAL,
+ SLASH,
+ SLASHSLASH,
+ SLASHSTAR,
+ STAR,
+ };
+ ParseState st = INITIAL;
+ std::string body;
+ std::vector<Comments> result;
+ for (const auto& c : comments) {
+ switch (st) {
+ case INITIAL: // trim ws & newlines
+ if (c == '/') {
+ st = SLASH;
+ body += c;
+ } else if (std::isspace(c)) {
+ // skip whitespaces outside comments
+ } else {
+ return Error() << "expecing / or space, but got unknown: " << c;
+ }
+ break;
+ case SLASH:
+ if (c == '/') {
+ st = SLASHSLASH;
+ body += c;
+ } else if (c == '*') {
+ st = SLASHSTAR;
+ body += c;
+ } else {
+ return Error() << "expecting / or *, but got unknown: " << c;
+ }
+ break;
+ case SLASHSLASH:
+ if (c == '\n') {
+ st = INITIAL;
+ result.push_back({Comments::Type::LINE, std::move(body)});
+ body.clear();
+ } else {
+ body += c;
+ }
+ break;
+ case SLASHSTAR:
+ body += c;
+ if (c == '*') {
+ st = STAR;
+ }
+ break;
+ case STAR: // read "*", about to close
+ body += c;
+ if (c == '/') { // close!
+ st = INITIAL;
+ result.push_back({Comments::Type::BLOCK, std::move(body)});
+ body.clear();
+ } else if (c == '*') {
+ // about to close...
+ } else {
+ st = SLASHSTAR;
+ }
+ break;
+ default:
+ return Error() << "unexpected state: " << st;
+ }
+ }
+ return result;
+}
+
+} // namespace
+
+// Finds @deprecated tag and returns it with optional note which follows the tag.
+std::optional<Deprecated> FindDeprecated(const std::string& comments) {
+ auto result = ParseComments(comments);
+ AIDL_FATAL_IF(!result.ok(), AIDL_LOCATION_HERE) << result.error();
+
+ const std::string kTagDeprecated = "@deprecated";
+ for (const auto& c : *result) {
+ for (const auto& [name, description] : c.BlockTags()) {
+ // take the first @deprecated
+ if (kTagDeprecated == name) {
+ return Deprecated{description};
+ }
+ }
+ }
+ return std::nullopt;
+}
+
+} // namespace aidl
+} // namespace android \ No newline at end of file
diff --git a/comments.h b/comments.h
new file mode 100644
index 00000000..12edda16
--- /dev/null
+++ b/comments.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <optional>
+#include <string>
+
+namespace android {
+namespace aidl {
+
+struct Deprecated {
+ std::string note; // can be empty("")
+};
+
+std::optional<Deprecated> FindDeprecated(const std::string& comments);
+
+} // namespace aidl
+} // namespace android \ No newline at end of file
diff --git a/generate_rust.cpp b/generate_rust.cpp
index 4a6d1c57..7d0e4825 100644
--- a/generate_rust.cpp
+++ b/generate_rust.cpp
@@ -29,6 +29,7 @@
#include "aidl_to_cpp_common.h"
#include "aidl_to_rust.h"
#include "code_writer.h"
+#include "comments.h"
#include "logging.h"
using android::base::Join;
@@ -313,10 +314,13 @@ void GenerateServerItems(CodeWriter& out, const AidlInterface* iface,
out << "}\n";
}
-template <typename Type>
-void GenerateDeprecated(CodeWriter& out, const Type& type) {
- if (type.IsDeprecated()) {
- out << "#[deprecated]\n";
+void GenerateDeprecated(CodeWriter& out, const AidlCommentable& type) {
+ if (auto deprecated = FindDeprecated(type.GetComments()); deprecated.has_value()) {
+ if (deprecated->note.empty()) {
+ out << "#[deprecated]\n";
+ } else {
+ out << "#[deprecated = " << QuotedEscape(deprecated->note) << "]\n";
+ }
}
}