Limit comment tags placement

Documentation comments are recognized only when placed immediately
before entities.

--dumpapi now generates a single block comment when an entity is
hidden/deprecated to comply with the change.

  /* @hide
     @deprecated */
  AIDL entity;

Bug: 177276893
Bug: 177616426
Test: aidl_unittests
Change-Id: I2c00d8e234d1a1ec3fbbcf34f290e0b163d508c0
diff --git a/comments.cpp b/comments.cpp
index 36f37e8..4b724b2 100644
--- a/comments.cpp
+++ b/comments.cpp
@@ -45,7 +45,8 @@
 static const std::regex kTagHideRegex{"@hide\\b"};
 
 std::string ConsumePrefix(const std::string& s, std::string_view prefix) {
-  AIDL_FATAL_IF(!StartsWith(s, prefix), AIDL_LOCATION_HERE);
+  AIDL_FATAL_IF(!StartsWith(s, prefix), AIDL_LOCATION_HERE)
+      << "'" << s << "' has no prefix '" << prefix << "'";
   return s.substr(prefix.size());
 }
 
@@ -63,7 +64,9 @@
 // - keeps leading spaces, but trims trailing spaces
 // - keeps empty lines
 std::vector<std::string> TrimmedLines(const Comment& c) {
-  if (c.type == Comment::Type::LINE) return std::vector{ConsumePrefix(c.body, kLineCommentBegin)};
+  if (c.type == Comment::Type::LINE) {
+    return std::vector{ConsumePrefix(c.body, kLineCommentBegin)};
+  }
 
   std::string stripped = ConsumeSuffix(ConsumePrefix(c.body, kBlockCommentBegin), kBlockCommentEnd);
 
@@ -99,7 +102,10 @@
   return lines;
 }
 
+// Parses a block comment and returns block tags in the comment.
 std::vector<BlockTag> BlockTags(const Comment& c) {
+  AIDL_FATAL_IF(c.type != Comment::Type::BLOCK, AIDL_LOCATION_HERE);
+
   std::vector<BlockTag> tags;
 
   // current tag and paragraph
@@ -150,23 +156,55 @@
 
   return tags;
 }
-}
 
-bool HasHideInComments(const Comments& comments) {
-  for (const auto& c : comments) {
-    if (c.type == Comment::Type::LINE) continue;
-    if (std::regex_search(c.body, kTagHideRegex)) {
-      return true;
-    }
+}  // namespace
+
+Comment::Comment(const std::string& body) : body(body) {
+  if (StartsWith(body, kLineCommentBegin)) {
+    type = Type::LINE;
+  } else if (StartsWith(body, kBlockCommentBegin) && EndsWith(body, kBlockCommentEnd)) {
+    type = Type::BLOCK;
+  } else {
+    AIDL_FATAL(AIDL_LOCATION_HERE) << "invalid comments body:" << body;
   }
-  return false;
 }
 
-// Finds @deprecated tag and returns it with optional note which follows the tag.
+// Returns the immediate block comment from the list of comments.
+// Only the last/block comment can have the tag.
+//
+//   /* @hide */
+//   int x;
+//
+// But tags in line or distant comments don't count. In the following,
+// the variable 'x' is not hidden.
+//
+//    // @hide
+//    int x;
+//
+//    /* @hide */
+//    /* this is the immemediate comment to 'x' */
+//    int x;
+//
+static std::optional<Comment> GetValidComment(const Comments& comments) {
+  if (!comments.empty() && comments.back().type == Comment::Type::BLOCK) {
+    return comments.back();
+  }
+  return std::nullopt;
+}
+
+// Sees if comments have the @hide tag.
+// Example: /** @hide */
+bool HasHideInComments(const Comments& comments) {
+  const auto valid_comment = GetValidComment(comments);
+  return valid_comment && std::regex_search(valid_comment->body, kTagHideRegex);
+}
+
+// Finds the @deprecated tag in comments and returns it with optional note which
+// follows the tag.
+// Example: /** @deprecated reason */
 std::optional<Deprecated> FindDeprecated(const Comments& comments) {
-  for (const auto& c : comments) {
-    if (c.type == Comment::Type::LINE) continue;
-    for (const auto& [name, description] : BlockTags(c)) {
+  if (const auto valid_comment = GetValidComment(comments); valid_comment) {
+    for (const auto& [name, description] : BlockTags(comments.back())) {
       // take the first @deprecated
       if (kTagDeprecated == name) {
         return Deprecated{description};