[clang] 38b4516 - [libTooling] Add function to determine associated text of a declaration.
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 26 06:58:10 PST 2020
Author: Yitzhak Mandelbaum
Date: 2020-02-26T09:56:48-05:00
New Revision: 38b4516de8a4a791d17085d37f95e3cc15c359f9
URL: https://github.com/llvm/llvm-project/commit/38b4516de8a4a791d17085d37f95e3cc15c359f9
DIFF: https://github.com/llvm/llvm-project/commit/38b4516de8a4a791d17085d37f95e3cc15c359f9.diff
LOG: [libTooling] Add function to determine associated text of a declaration.
Summary:
Second attempt -- the first was reverted in commit 0e480b39c66143ad142f9a30d8d40e49d7d7b0ce, because of test breakages. This revision fixes the cause of the test breakages.
Original description follows:
This patch adds `getAssociatedRange` which, for a given decl, computes preceding
and trailing text that would conceptually be associated with the decl by the
reader. This includes comments, whitespace, and separators like ';'.
Reviewers: gribozavr
Subscribers: cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D72153
Added:
Modified:
clang/include/clang/Tooling/Transformer/SourceCode.h
clang/lib/Tooling/Transformer/SourceCode.cpp
clang/unittests/Tooling/SourceCodeTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Tooling/Transformer/SourceCode.h b/clang/include/clang/Tooling/Transformer/SourceCode.h
index 1b92a117f44c..2c7eb65371cf 100644
--- a/clang/include/clang/Tooling/Transformer/SourceCode.h
+++ b/clang/include/clang/Tooling/Transformer/SourceCode.h
@@ -20,9 +20,10 @@
namespace clang {
namespace tooling {
-/// Extends \p Range to include the token \p Next, if it immediately follows the
-/// end of the range. Otherwise, returns \p Range unchanged.
-CharSourceRange maybeExtendRange(CharSourceRange Range, tok::TokenKind Next,
+/// Extends \p Range to include the token \p Terminator, if it immediately
+/// follows the end of the range. Otherwise, returns \p Range unchanged.
+CharSourceRange maybeExtendRange(CharSourceRange Range,
+ tok::TokenKind Terminator,
ASTContext &Context);
/// Returns the source range spanning the node, extended to include \p Next, if
@@ -35,6 +36,13 @@ CharSourceRange getExtendedRange(const T &Node, tok::TokenKind Next,
Next, Context);
}
+/// Returns the logical source range of the node extended to include associated
+/// comments and whitespace before and after the node, and associated
+/// terminators. The returned range consists of file locations, if valid file
+/// locations can be found for the associated content; otherwise, an invalid
+/// range is returned.
+CharSourceRange getAssociatedRange(const Decl &D, ASTContext &Context);
+
/// Returns the source-code text in the specified range.
StringRef getText(CharSourceRange Range, const ASTContext &Context);
diff --git a/clang/lib/Tooling/Transformer/SourceCode.cpp b/clang/lib/Tooling/Transformer/SourceCode.cpp
index 5c1f8b46fe42..8d59487ea7ae 100644
--- a/clang/lib/Tooling/Transformer/SourceCode.cpp
+++ b/clang/lib/Tooling/Transformer/SourceCode.cpp
@@ -10,6 +10,13 @@
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Transformer/SourceCode.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
#include "clang/Lex/Lexer.h"
#include "llvm/Support/Errc.h"
@@ -84,3 +91,302 @@ clang::tooling::getRangeForEdit(const CharSourceRange &EditRange,
return Range;
}
+
+static bool startsWithNewline(const SourceManager &SM, const Token &Tok) {
+ return isVerticalWhitespace(SM.getCharacterData(Tok.getLocation())[0]);
+}
+
+static bool contains(const std::set<tok::TokenKind> &Terminators,
+ const Token &Tok) {
+ return Terminators.count(Tok.getKind()) > 0;
+}
+
+// Returns the exclusive, *file* end location of the entity whose last token is
+// at location 'EntityLast'. That is, it returns the location one past the last
+// relevant character.
+//
+// Associated tokens include comments, horizontal whitespace and 'Terminators'
+// -- optional tokens, which, if any are found, will be included; if
+// 'Terminators' is empty, we will not include any extra tokens beyond comments
+// and horizontal whitespace.
+static SourceLocation
+getEntityEndLoc(const SourceManager &SM, SourceLocation EntityLast,
+ const std::set<tok::TokenKind> &Terminators,
+ const LangOptions &LangOpts) {
+ assert(EntityLast.isValid() && "Invalid end location found.");
+
+ // We remember the last location of a non-horizontal-whitespace token we have
+ // lexed; this is the location up to which we will want to delete.
+ // FIXME: Support using the spelling loc here for cases where we want to
+ // analyze the macro text.
+
+ CharSourceRange ExpansionRange = SM.getExpansionRange(EntityLast);
+ // FIXME: Should check isTokenRange(), for the (rare) case that
+ // `ExpansionRange` is a character range.
+ std::unique_ptr<Lexer> Lexer = [&]() {
+ bool Invalid = false;
+ auto FileOffset = SM.getDecomposedLoc(ExpansionRange.getEnd());
+ llvm::StringRef File = SM.getBufferData(FileOffset.first, &Invalid);
+ assert(!Invalid && "Cannot get file/offset");
+ return std::make_unique<clang::Lexer>(
+ SM.getLocForStartOfFile(FileOffset.first), LangOpts, File.begin(),
+ File.data() + FileOffset.second, File.end());
+ }();
+
+ // Tell Lexer to return whitespace as pseudo-tokens (kind is tok::unknown).
+ Lexer->SetKeepWhitespaceMode(true);
+
+ // Generally, the code we want to include looks like this ([] are optional),
+ // If Terminators is empty:
+ // [ <comment> ] [ <newline> ]
+ // Otherwise:
+ // ... <terminator> [ <comment> ] [ <newline> ]
+
+ Token Tok;
+ bool Terminated = false;
+
+ // First, lex to the current token (which is the last token of the range that
+ // is definitely associated with the decl). Then, we process the first token
+ // separately from the rest based on conditions that hold specifically for
+ // that first token.
+ //
+ // We do not search for a terminator if none is required or we've already
+ // encountered it. Otherwise, if the original `EntityLast` location was in a
+ // macro expansion, we don't have visibility into the text, so we assume we've
+ // already terminated. However, we note this assumption with
+ // `TerminatedByMacro`, because we'll want to handle it somewhat
diff erently
+ // for the terminators semicolon and comma. These terminators can be safely
+ // associated with the entity when they appear after the macro -- extra
+ // semicolons have no effect on the program and a well-formed program won't
+ // have multiple commas in a row, so we're guaranteed that there is only one.
+ //
+ // FIXME: This handling of macros is more conservative than necessary. When
+ // the end of the expansion coincides with the end of the node, we can still
+ // safely analyze the code. But, it is more complicated, because we need to
+ // start by lexing the spelling loc for the first token and then switch to the
+ // expansion loc.
+ bool TerminatedByMacro = false;
+ Lexer->LexFromRawLexer(Tok);
+ if (Terminators.empty() || contains(Terminators, Tok))
+ Terminated = true;
+ else if (EntityLast.isMacroID()) {
+ Terminated = true;
+ TerminatedByMacro = true;
+ }
+
+ // We save the most recent candidate for the exclusive end location.
+ SourceLocation End = Tok.getEndLoc();
+
+ while (!Terminated) {
+ // Lex the next token we want to possibly expand the range with.
+ Lexer->LexFromRawLexer(Tok);
+
+ switch (Tok.getKind()) {
+ case tok::eof:
+ // Unexpected separators.
+ case tok::l_brace:
+ case tok::r_brace:
+ case tok::comma:
+ return End;
+ // Whitespace pseudo-tokens.
+ case tok::unknown:
+ if (startsWithNewline(SM, Tok))
+ // Include at least until the end of the line.
+ End = Tok.getEndLoc();
+ break;
+ default:
+ if (contains(Terminators, Tok))
+ Terminated = true;
+ End = Tok.getEndLoc();
+ break;
+ }
+ }
+
+ do {
+ // Lex the next token we want to possibly expand the range with.
+ Lexer->LexFromRawLexer(Tok);
+
+ switch (Tok.getKind()) {
+ case tok::unknown:
+ if (startsWithNewline(SM, Tok))
+ // We're done, but include this newline.
+ return Tok.getEndLoc();
+ break;
+ case tok::comment:
+ // Include any comments we find on the way.
+ End = Tok.getEndLoc();
+ break;
+ case tok::semi:
+ case tok::comma:
+ if (TerminatedByMacro && contains(Terminators, Tok)) {
+ End = Tok.getEndLoc();
+ // We've found a real terminator.
+ TerminatedByMacro = false;
+ break;
+ }
+ // Found an unrelated token; stop and don't include it.
+ return End;
+ default:
+ // Found an unrelated token; stop and don't include it.
+ return End;
+ }
+ } while (true);
+}
+
+// Returns the expected terminator tokens for the given declaration.
+//
+// If we do not know the correct terminator token, returns an empty set.
+//
+// There are cases where we have more than one possible terminator (for example,
+// we find either a comma or a semicolon after a VarDecl).
+static std::set<tok::TokenKind> getTerminators(const Decl &D) {
+ if (llvm::isa<RecordDecl>(D) || llvm::isa<UsingDecl>(D))
+ return {tok::semi};
+
+ if (llvm::isa<FunctionDecl>(D) || llvm::isa<LinkageSpecDecl>(D))
+ return {tok::r_brace, tok::semi};
+
+ if (llvm::isa<VarDecl>(D) || llvm::isa<FieldDecl>(D))
+ return {tok::comma, tok::semi};
+
+ return {};
+}
+
+// Starting from `Loc`, skips whitespace up to, and including, a single
+// newline. Returns the (exclusive) end of any skipped whitespace (that is, the
+// location immediately after the whitespace).
+static SourceLocation skipWhitespaceAndNewline(const SourceManager &SM,
+ SourceLocation Loc,
+ const LangOptions &LangOpts) {
+ const char *LocChars = SM.getCharacterData(Loc);
+ int i = 0;
+ while (isHorizontalWhitespace(LocChars[i]))
+ ++i;
+ if (isVerticalWhitespace(LocChars[i]))
+ ++i;
+ return Loc.getLocWithOffset(i);
+}
+
+// Is `Loc` separated from any following decl by something meaningful (e.g. an
+// empty line, a comment), ignoring horizontal whitespace? Since this is a
+// heuristic, we return false when in doubt. `Loc` cannot be the first location
+// in the file.
+static bool atOrBeforeSeparation(const SourceManager &SM, SourceLocation Loc,
+ const LangOptions &LangOpts) {
+ // If the preceding character is a newline, we'll check for an empty line as a
+ // separator. However, we can't identify an empty line using tokens, so we
+ // analyse the characters. If we try to use tokens, we'll just end up with a
+ // whitespace token, whose characters we'd have to analyse anyhow.
+ bool Invalid = false;
+ const char *LocChars =
+ SM.getCharacterData(Loc.getLocWithOffset(-1), &Invalid);
+ assert(!Invalid &&
+ "Loc must be a valid character and not the first of the source file.");
+ if (isVerticalWhitespace(LocChars[0])) {
+ for (int i = 1; isWhitespace(LocChars[i]); ++i)
+ if (isVerticalWhitespace(LocChars[i]))
+ return true;
+ }
+ // We didn't find an empty line, so lex the next token, skipping past any
+ // whitespace we just scanned.
+ Token Tok;
+ bool Failed = Lexer::getRawToken(Loc, Tok, SM, LangOpts,
+ /*IgnoreWhiteSpace=*/true);
+ if (Failed)
+ // Any text that confuses the lexer seems fair to consider a separation.
+ return true;
+
+ switch (Tok.getKind()) {
+ case tok::comment:
+ case tok::l_brace:
+ case tok::r_brace:
+ case tok::eof:
+ return true;
+ default:
+ return false;
+ }
+}
+
+CharSourceRange tooling::getAssociatedRange(const Decl &Decl,
+ ASTContext &Context) {
+ const SourceManager &SM = Context.getSourceManager();
+ const LangOptions &LangOpts = Context.getLangOpts();
+ CharSourceRange Range = CharSourceRange::getTokenRange(Decl.getSourceRange());
+
+ // First, expand to the start of the template<> declaration if necessary.
+ if (const auto *Record = llvm::dyn_cast<CXXRecordDecl>(&Decl)) {
+ if (const auto *T = Record->getDescribedClassTemplate())
+ if (SM.isBeforeInTranslationUnit(T->getBeginLoc(), Range.getBegin()))
+ Range.setBegin(T->getBeginLoc());
+ } else if (const auto *F = llvm::dyn_cast<FunctionDecl>(&Decl)) {
+ if (const auto *T = F->getDescribedFunctionTemplate())
+ if (SM.isBeforeInTranslationUnit(T->getBeginLoc(), Range.getBegin()))
+ Range.setBegin(T->getBeginLoc());
+ }
+
+ // Next, expand the end location past trailing comments to include a potential
+ // newline at the end of the decl's line.
+ Range.setEnd(
+ getEntityEndLoc(SM, Decl.getEndLoc(), getTerminators(Decl), LangOpts));
+ Range.setTokenRange(false);
+
+ // Expand to include preceeding associated comments. We ignore any comments
+ // that are not preceeding the decl, since we've already skipped trailing
+ // comments with getEntityEndLoc.
+ if (const RawComment *Comment =
+ Decl.getASTContext().getRawCommentForDeclNoCache(&Decl))
+ // Only include a preceding comment if:
+ // * it is *not* separate from the declaration (not including any newline
+ // that immediately follows the comment),
+ // * the decl *is* separate from any following entity (so, there are no
+ // other entities the comment could refer to), and
+ // * it is not a IfThisThenThat lint check.
+ if (SM.isBeforeInTranslationUnit(Comment->getBeginLoc(),
+ Range.getBegin()) &&
+ !atOrBeforeSeparation(
+ SM, skipWhitespaceAndNewline(SM, Comment->getEndLoc(), LangOpts),
+ LangOpts) &&
+ atOrBeforeSeparation(SM, Range.getEnd(), LangOpts)) {
+ const StringRef CommentText = Comment->getRawText(SM);
+ if (!CommentText.contains("LINT.IfChange") &&
+ !CommentText.contains("LINT.ThenChange"))
+ Range.setBegin(Comment->getBeginLoc());
+ }
+ // Add leading attributes.
+ for (auto *Attr : Decl.attrs()) {
+ if (Attr->getLocation().isInvalid() ||
+ !SM.isBeforeInTranslationUnit(Attr->getLocation(), Range.getBegin()))
+ continue;
+ Range.setBegin(Attr->getLocation());
+
+ // Extend to the left '[[' or '__attribute((' if we saw the attribute,
+ // unless it is not a valid location.
+ bool Invalid;
+ StringRef Source =
+ SM.getBufferData(SM.getFileID(Range.getBegin()), &Invalid);
+ if (Invalid)
+ continue;
+ llvm::StringRef BeforeAttr =
+ Source.substr(0, SM.getFileOffset(Range.getBegin()));
+ llvm::StringRef BeforeAttrStripped = BeforeAttr.rtrim();
+
+ for (llvm::StringRef Prefix : {"[[", "__attribute__(("}) {
+ // Handle whitespace between attribute prefix and attribute value.
+ if (BeforeAttrStripped.endswith(Prefix)) {
+ // Move start to start position of prefix, which is
+ // length(BeforeAttr) - length(BeforeAttrStripped) + length(Prefix)
+ // positions to the left.
+ Range.setBegin(Range.getBegin().getLocWithOffset(static_cast<int>(
+ -BeforeAttr.size() + BeforeAttrStripped.size() - Prefix.size())));
+ break;
+ // If we didn't see '[[' or '__attribute' it's probably coming from a
+ // macro expansion which is already handled by makeFileCharRange(),
+ // below.
+ }
+ }
+ }
+
+ // Range.getEnd() is already fully un-expanded by getEntityEndLoc. But,
+ // Range.getBegin() may be inside an expansion.
+ return Lexer::makeFileCharRange(Range, SM, LangOpts);
+}
diff --git a/clang/unittests/Tooling/SourceCodeTest.cpp b/clang/unittests/Tooling/SourceCodeTest.cpp
index 9e0d2e2c4274..aeb0ca36582b 100644
--- a/clang/unittests/Tooling/SourceCodeTest.cpp
+++ b/clang/unittests/Tooling/SourceCodeTest.cpp
@@ -20,6 +20,7 @@ using namespace clang;
using llvm::Failed;
using llvm::Succeeded;
using llvm::ValueIs;
+using tooling::getAssociatedRange;
using tooling::getExtendedText;
using tooling::getRangeForEdit;
using tooling::getText;
@@ -51,6 +52,28 @@ MATCHER_P(EqualsRange, R, "") {
arg.getBegin() == R.getBegin() && arg.getEnd() == R.getEnd();
}
+MATCHER_P2(EqualsAnnotatedRange, SM, R, "") {
+ if (arg.getBegin().isMacroID()) {
+ *result_listener << "which starts in a macro";
+ return false;
+ }
+ if (arg.getEnd().isMacroID()) {
+ *result_listener << "which ends in a macro";
+ return false;
+ }
+
+ unsigned Begin = SM->getFileOffset(arg.getBegin());
+ unsigned End = SM->getFileOffset(arg.getEnd());
+
+ *result_listener << "which is [" << Begin << ",";
+ if (arg.isTokenRange()) {
+ *result_listener << End << "]";
+ return Begin == R.Begin && End + 1 == R.End;
+ }
+ *result_listener << End << ")";
+ return Begin == R.Begin && End == R.End;
+}
+
static ::testing::Matcher<CharSourceRange> AsRange(const SourceManager &SM,
llvm::Annotations::Range R) {
return EqualsRange(CharSourceRange::getCharRange(
@@ -58,6 +81,40 @@ static ::testing::Matcher<CharSourceRange> AsRange(const SourceManager &SM,
SM.getLocForStartOfFile(SM.getMainFileID()).getLocWithOffset(R.End)));
}
+// Base class for visitors that expect a single match corresponding to a
+// specific annotated range.
+template <typename T> class AnnotatedCodeVisitor : public TestVisitor<T> {
+ llvm::Annotations Code;
+ int MatchCount = 0;
+
+public:
+ AnnotatedCodeVisitor() : Code("$r[[]]") {}
+ bool VisitDeclHelper(Decl *Decl) {
+ // Only consider explicit declarations.
+ if (Decl->isImplicit())
+ return true;
+
+ ++MatchCount;
+ EXPECT_THAT(getAssociatedRange(*Decl, *this->Context),
+ EqualsAnnotatedRange(&this->Context->getSourceManager(),
+ Code.range("r")))
+ << Code.code();
+ return true;
+ }
+
+ bool runOverAnnotated(llvm::StringRef AnnotatedCode,
+ std::vector<std::string> Args = {}) {
+ Code = llvm::Annotations(AnnotatedCode);
+ MatchCount = 0;
+ Args.push_back("-std=c++11");
+ Args.push_back("-fno-delayed-template-parsing");
+ bool result = tooling::runToolOnCodeWithArgs(this->CreateTestAction(),
+ Code.code(), Args);
+ EXPECT_EQ(MatchCount, 1) << AnnotatedCode;
+ return result;
+ }
+};
+
TEST(SourceCodeTest, getText) {
CallsVisitor Visitor;
@@ -126,6 +183,212 @@ TEST(SourceCodeTest, getExtendedText) {
Visitor.runOver("int foo() { return foo() + 3; }");
}
+TEST(SourceCodeTest, getAssociatedRange) {
+ struct VarDeclsVisitor : AnnotatedCodeVisitor<VarDeclsVisitor> {
+ bool VisitVarDecl(VarDecl *Decl) { return VisitDeclHelper(Decl); }
+ };
+ VarDeclsVisitor Visitor;
+
+ // Includes semicolon.
+ Visitor.runOverAnnotated("$r[[int x = 4;]]");
+
+ // Includes newline and semicolon.
+ Visitor.runOverAnnotated("$r[[int x = 4;\n]]");
+
+ // Includes trailing comments.
+ Visitor.runOverAnnotated("$r[[int x = 4; // Comment\n]]");
+ Visitor.runOverAnnotated("$r[[int x = 4; /* Comment */\n]]");
+
+ // Does *not* include trailing comments when another entity appears between
+ // the decl and the comment.
+ Visitor.runOverAnnotated("$r[[int x = 4;]] class C {}; // Comment\n");
+
+ // Includes attributes.
+ Visitor.runOverAnnotated(R"cpp(
+ #define ATTR __attribute__((deprecated("message")))
+ $r[[ATTR
+ int x;]])cpp");
+
+ // Includes attributes and comments together.
+ Visitor.runOverAnnotated(R"cpp(
+ #define ATTR __attribute__((deprecated("message")))
+ $r[[ATTR
+ // Commment.
+ int x;]])cpp");
+}
+
+TEST(SourceCodeTest, getAssociatedRangeClasses) {
+ struct RecordDeclsVisitor : AnnotatedCodeVisitor<RecordDeclsVisitor> {
+ bool VisitRecordDecl(RecordDecl *Decl) { return VisitDeclHelper(Decl); }
+ };
+ RecordDeclsVisitor Visitor;
+
+ Visitor.runOverAnnotated("$r[[class A;]]");
+ Visitor.runOverAnnotated("$r[[class A {};]]");
+
+ // Includes leading template annotation.
+ Visitor.runOverAnnotated("$r[[template <typename T> class A;]]");
+ Visitor.runOverAnnotated("$r[[template <typename T> class A {};]]");
+}
+
+TEST(SourceCodeTest, getAssociatedRangeClassTemplateSpecializations) {
+ struct CXXRecordDeclsVisitor : AnnotatedCodeVisitor<CXXRecordDeclsVisitor> {
+ bool VisitCXXRecordDecl(CXXRecordDecl *Decl) {
+ return Decl->getTemplateSpecializationKind() !=
+ TSK_ExplicitSpecialization ||
+ VisitDeclHelper(Decl);
+ }
+ };
+ CXXRecordDeclsVisitor Visitor;
+
+ Visitor.runOverAnnotated(R"cpp(
+ template <typename T> class A{};
+ $r[[template <> class A<int>;]])cpp");
+ Visitor.runOverAnnotated(R"cpp(
+ template <typename T> class A{};
+ $r[[template <> class A<int> {};]])cpp");
+}
+
+TEST(SourceCodeTest, getAssociatedRangeFunctions) {
+ struct FunctionDeclsVisitor : AnnotatedCodeVisitor<FunctionDeclsVisitor> {
+ bool VisitFunctionDecl(FunctionDecl *Decl) { return VisitDeclHelper(Decl); }
+ };
+ FunctionDeclsVisitor Visitor;
+
+ Visitor.runOverAnnotated("$r[[int f();]]");
+ Visitor.runOverAnnotated("$r[[int f() { return 0; }]]");
+ // Includes leading template annotation.
+ Visitor.runOverAnnotated("$r[[template <typename T> int f();]]");
+ Visitor.runOverAnnotated("$r[[template <typename T> int f() { return 0; }]]");
+}
+
+TEST(SourceCodeTest, getAssociatedRangeMemberTemplates) {
+ struct CXXMethodDeclsVisitor : AnnotatedCodeVisitor<CXXMethodDeclsVisitor> {
+ bool VisitCXXMethodDecl(CXXMethodDecl *Decl) {
+ // Only consider the definition of the template.
+ return !Decl->doesThisDeclarationHaveABody() || VisitDeclHelper(Decl);
+ }
+ };
+ CXXMethodDeclsVisitor Visitor;
+
+ Visitor.runOverAnnotated(R"cpp(
+ template <typename C>
+ struct A { template <typename T> int member(T v); };
+
+ $r[[template <typename C>
+ template <typename T>
+ int A<C>::member(T v) { return 0; }]])cpp");
+}
+
+TEST(SourceCodeTest, getAssociatedRangeWithComments) {
+ struct VarDeclsVisitor : AnnotatedCodeVisitor<VarDeclsVisitor> {
+ bool VisitVarDecl(VarDecl *Decl) { return VisitDeclHelper(Decl); }
+ };
+
+ VarDeclsVisitor Visitor;
+ auto Visit = [&](llvm::StringRef AnnotatedCode) {
+ Visitor.runOverAnnotated(AnnotatedCode, {"-fparse-all-comments"});
+ };
+
+ // Includes leading comments.
+ Visit("$r[[// Comment.\nint x = 4;]]");
+ Visit("$r[[// Comment.\nint x = 4;\n]]");
+ Visit("$r[[/* Comment.*/\nint x = 4;\n]]");
+ // ... even if separated by (extra) horizontal whitespace.
+ Visit("$r[[/* Comment.*/ \nint x = 4;\n]]");
+
+ // Includes comments even in the presence of trailing whitespace.
+ Visit("$r[[// Comment.\nint x = 4;]] ");
+
+ // Includes comments when the declaration is followed by the beginning or end
+ // of a compound statement.
+ Visit(R"cpp(
+ void foo() {
+ $r[[/* C */
+ int x = 4;
+ ]]};)cpp");
+ Visit(R"cpp(
+ void foo() {
+ $r[[/* C */
+ int x = 4;
+ ]]{ class Foo {}; }
+ })cpp");
+
+ // Includes comments inside macros (when decl is in the same macro).
+ Visit(R"cpp(
+ #define DECL /* Comment */ int x
+ $r[[DECL;]])cpp");
+
+ // Does not include comments when only the decl or the comment come from a
+ // macro.
+ // FIXME: Change code to allow this.
+ Visit(R"cpp(
+ #define DECL int x
+ // Comment
+ $r[[DECL;]])cpp");
+ Visit(R"cpp(
+ #define COMMENT /* Comment */
+ COMMENT
+ $r[[int x;]])cpp");
+
+ // Includes multi-line comments.
+ Visit(R"cpp(
+ $r[[/* multi
+ * line
+ * comment
+ */
+ int x;]])cpp");
+ Visit(R"cpp(
+ $r[[// multi
+ // line
+ // comment
+ int x;]])cpp");
+
+ // Does not include comments separated by multiple empty lines.
+ Visit("// Comment.\n\n\n$r[[int x = 4;\n]]");
+ Visit("/* Comment.*/\n\n\n$r[[int x = 4;\n]]");
+
+ // Does not include comments before a *series* of declarations.
+ Visit(R"cpp(
+ // Comment.
+ $r[[int x = 4;
+ ]]class foo {};)cpp");
+
+ // Does not include IfThisThenThat comments
+ Visit("// LINT.IfChange.\n$r[[int x = 4;]]");
+ Visit("// LINT.ThenChange.\n$r[[int x = 4;]]");
+
+ // Includes attributes.
+ Visit(R"cpp(
+ #define ATTR __attribute__((deprecated("message")))
+ $r[[ATTR
+ int x;]])cpp");
+
+ // Includes attributes and comments together.
+ Visit(R"cpp(
+ #define ATTR __attribute__((deprecated("message")))
+ $r[[ATTR
+ // Commment.
+ int x;]])cpp");
+}
+
+TEST(SourceCodeTest, getAssociatedRangeInvalidForPartialExpansions) {
+ struct FailingVarDeclsVisitor : TestVisitor<FailingVarDeclsVisitor> {
+ FailingVarDeclsVisitor() {}
+ bool VisitVarDecl(VarDecl *Decl) {
+ EXPECT_TRUE(getAssociatedRange(*Decl, *Context).isInvalid());
+ return true;
+ }
+ };
+
+ FailingVarDeclsVisitor Visitor;
+ // Should fail because it only includes a part of the expansion.
+ std::string Code = R"cpp(
+ #define DECL class foo { }; int x
+ DECL;)cpp";
+ Visitor.runOver(Code);
+}
+
TEST(SourceCodeTest, EditRangeWithMacroExpansionsShouldSucceed) {
// The call expression, whose range we are extracting, includes two macro
// expansions.
More information about the cfe-commits
mailing list