diff options
author | Jooyung Han <jooyung@google.com> | 2021-01-11 16:21:53 +0900 |
---|---|---|
committer | Jooyung Han <jooyung@google.com> | 2021-01-16 00:43:20 +0000 |
commit | d4fe00eaba940095f1a31f6c37bffee7d226da4f (patch) | |
tree | 54b368d942a70d90b6ba3a33c6a46015386c4959 | |
parent | b2f4fe66d452f48bd6a608286682604373c53a93 (diff) | |
download | aidl-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.bp | 1 | ||||
-rw-r--r-- | aidl_language.cpp | 7 | ||||
-rw-r--r-- | aidl_to_cpp_common.cpp | 11 | ||||
-rw-r--r-- | aidl_to_cpp_common.h | 16 | ||||
-rw-r--r-- | aidl_unittest.cpp | 59 | ||||
-rw-r--r-- | code_writer.cpp | 19 | ||||
-rw-r--r-- | code_writer.h | 2 | ||||
-rw-r--r-- | comments.cpp | 249 | ||||
-rw-r--r-- | comments.h | 31 | ||||
-rw-r--r-- | generate_rust.cpp | 12 |
10 files changed, 373 insertions, 34 deletions
@@ -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"; + } } } |