diff options
Diffstat (limited to 'unittests')
27 files changed, 7034 insertions, 75 deletions
diff --git a/unittests/AST/CMakeLists.txt b/unittests/AST/CMakeLists.txt new file mode 100644 index 0000000..63418a2 --- /dev/null +++ b/unittests/AST/CMakeLists.txt @@ -0,0 +1,8 @@ +add_clang_unittest(ASTTests + CommentLexer.cpp + CommentParser.cpp + ) + +target_link_libraries(ASTTests + clangAST + ) diff --git a/unittests/AST/CommentLexer.cpp b/unittests/AST/CommentLexer.cpp new file mode 100644 index 0000000..cab0fdd --- /dev/null +++ b/unittests/AST/CommentLexer.cpp @@ -0,0 +1,1636 @@ +//===- unittests/AST/CommentLexer.cpp ------ Comment lexer tests ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/AST/CommentLexer.h" +#include "clang/AST/CommentCommandTraits.h" +#include "llvm/ADT/STLExtras.h" +#include <vector> + +#include "gtest/gtest.h" + +using namespace llvm; +using namespace clang; + +namespace clang { +namespace comments { + +namespace { +class CommentLexerTest : public ::testing::Test { +protected: + CommentLexerTest() + : FileMgr(FileMgrOpts), + DiagID(new DiagnosticIDs()), + Diags(DiagID, new IgnoringDiagConsumer()), + SourceMgr(Diags, FileMgr) { + } + + FileSystemOptions FileMgrOpts; + FileManager FileMgr; + IntrusiveRefCntPtr<DiagnosticIDs> DiagID; + DiagnosticsEngine Diags; + SourceManager SourceMgr; + llvm::BumpPtrAllocator Allocator; + + void lexString(const char *Source, std::vector<Token> &Toks); +}; + +void CommentLexerTest::lexString(const char *Source, + std::vector<Token> &Toks) { + MemoryBuffer *Buf = MemoryBuffer::getMemBuffer(Source); + FileID File = SourceMgr.createFileIDForMemBuffer(Buf); + SourceLocation Begin = SourceMgr.getLocForStartOfFile(File); + + comments::CommandTraits Traits; + comments::Lexer L(Allocator, Traits, Begin, CommentOptions(), + Source, Source + strlen(Source)); + + while (1) { + Token Tok; + L.lex(Tok); + if (Tok.is(tok::eof)) + break; + Toks.push_back(Tok); + } +} + +} // unnamed namespace + +// Empty source range should be handled. +TEST_F(CommentLexerTest, Basic1) { + const char *Source = ""; + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(0U, Toks.size()); +} + +// Empty comments should be handled. +TEST_F(CommentLexerTest, Basic2) { + const char *Sources[] = { + "//", "///", "//!", "///<", "//!<" + }; + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(1U, Toks.size()); + + ASSERT_EQ(tok::newline, Toks[0].getKind()); + } +} + +// Empty comments should be handled. +TEST_F(CommentLexerTest, Basic3) { + const char *Sources[] = { + "/**/", "/***/", "/*!*/", "/**<*/", "/*!<*/" + }; + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(2U, Toks.size()); + + ASSERT_EQ(tok::newline, Toks[0].getKind()); + ASSERT_EQ(tok::newline, Toks[1].getKind()); + } +} + +// Single comment with plain text. +TEST_F(CommentLexerTest, Basic4) { + const char *Sources[] = { + "// Meow", "/// Meow", "//! Meow", + "// Meow\n", "// Meow\r\n", "//! Meow\r", + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(2U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Meow"), Toks[0].getText()); + + ASSERT_EQ(tok::newline, Toks[1].getKind()); + } +} + +// Single comment with plain text. +TEST_F(CommentLexerTest, Basic5) { + const char *Sources[] = { + "/* Meow*/", "/** Meow*/", "/*! Meow*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Meow"), Toks[0].getText()); + + ASSERT_EQ(tok::newline, Toks[1].getKind()); + ASSERT_EQ(tok::newline, Toks[2].getKind()); + } +} + +// Test newline escaping. +TEST_F(CommentLexerTest, Basic6) { + const char *Sources[] = { + "// Aaa\\\n" " Bbb\\ \n" " Ccc?" "?/\n", + "// Aaa\\\r\n" " Bbb\\ \r\n" " Ccc?" "?/\r\n", + "// Aaa\\\r" " Bbb\\ \r" " Ccc?" "?/\r" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(10U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Aaa"), Toks[0].getText()); + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("\\"), Toks[1].getText()); + ASSERT_EQ(tok::newline, Toks[2].getKind()); + + ASSERT_EQ(tok::text, Toks[3].getKind()); + ASSERT_EQ(StringRef(" Bbb"), Toks[3].getText()); + ASSERT_EQ(tok::text, Toks[4].getKind()); + ASSERT_EQ(StringRef("\\"), Toks[4].getText()); + ASSERT_EQ(tok::text, Toks[5].getKind()); + ASSERT_EQ(StringRef(" "), Toks[5].getText()); + ASSERT_EQ(tok::newline, Toks[6].getKind()); + + ASSERT_EQ(tok::text, Toks[7].getKind()); + ASSERT_EQ(StringRef(" Ccc?" "?/"), Toks[7].getText()); + ASSERT_EQ(tok::newline, Toks[8].getKind()); + + ASSERT_EQ(tok::newline, Toks[9].getKind()); + } +} + +// Check that we skip C-style aligned stars correctly. +TEST_F(CommentLexerTest, Basic7) { + const char *Source = + "/* Aaa\n" + " * Bbb\r\n" + "\t* Ccc\n" + " ! Ddd\n" + " * Eee\n" + " ** Fff\n" + " */"; + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(15U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Aaa"), Toks[0].getText()); + ASSERT_EQ(tok::newline, Toks[1].getKind()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef(" Bbb"), Toks[2].getText()); + ASSERT_EQ(tok::newline, Toks[3].getKind()); + + ASSERT_EQ(tok::text, Toks[4].getKind()); + ASSERT_EQ(StringRef(" Ccc"), Toks[4].getText()); + ASSERT_EQ(tok::newline, Toks[5].getKind()); + + ASSERT_EQ(tok::text, Toks[6].getKind()); + ASSERT_EQ(StringRef(" ! Ddd"), Toks[6].getText()); + ASSERT_EQ(tok::newline, Toks[7].getKind()); + + ASSERT_EQ(tok::text, Toks[8].getKind()); + ASSERT_EQ(StringRef(" Eee"), Toks[8].getText()); + ASSERT_EQ(tok::newline, Toks[9].getKind()); + + ASSERT_EQ(tok::text, Toks[10].getKind()); + ASSERT_EQ(StringRef("* Fff"), Toks[10].getText()); + ASSERT_EQ(tok::newline, Toks[11].getKind()); + + ASSERT_EQ(tok::text, Toks[12].getKind()); + ASSERT_EQ(StringRef(" "), Toks[12].getText()); + + ASSERT_EQ(tok::newline, Toks[13].getKind()); + ASSERT_EQ(tok::newline, Toks[14].getKind()); +} + +// A command marker followed by comment end. +TEST_F(CommentLexerTest, DoxygenCommand1) { + const char *Sources[] = { "//@", "///@", "//!@" }; + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(2U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef("@"), Toks[0].getText()); + + ASSERT_EQ(tok::newline, Toks[1].getKind()); + } +} + +// A command marker followed by comment end. +TEST_F(CommentLexerTest, DoxygenCommand2) { + const char *Sources[] = { "/*@*/", "/**@*/", "/*!@*/"}; + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef("@"), Toks[0].getText()); + + ASSERT_EQ(tok::newline, Toks[1].getKind()); + ASSERT_EQ(tok::newline, Toks[2].getKind()); + } +} + +// A command marker followed by comment end. +TEST_F(CommentLexerTest, DoxygenCommand3) { + const char *Sources[] = { "/*\\*/", "/**\\*/" }; + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef("\\"), Toks[0].getText()); + + ASSERT_EQ(tok::newline, Toks[1].getKind()); + ASSERT_EQ(tok::newline, Toks[2].getKind()); + } +} + +// Doxygen escape sequences. +TEST_F(CommentLexerTest, DoxygenCommand4) { + const char *Source = + "/// \\\\ \\@ \\& \\$ \\# \\< \\> \\% \\\" \\. \\::"; + const char *Text[] = { + " ", + "\\", " ", "@", " ", "&", " ", "$", " ", "#", " ", + "<", " ", ">", " ", "%", " ", "\"", " ", ".", " ", + "::", "" + }; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(array_lengthof(Text), Toks.size()); + + for (size_t i = 0, e = Toks.size(); i != e; i++) { + if(Toks[i].is(tok::text)) + ASSERT_EQ(StringRef(Text[i]), Toks[i].getText()) + << "index " << i; + } +} + +TEST_F(CommentLexerTest, DoxygenCommand5) { + const char *Source = "/// \\brief Aaa."; + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::command, Toks[1].getKind()); + ASSERT_EQ(StringRef("brief"), Toks[1].getCommandName()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef(" Aaa."), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, DoxygenCommand6) { + const char *Source = "/// \\aaa\\bbb \\ccc\t\\ddd\n"; + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(8U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::command, Toks[1].getKind()); + ASSERT_EQ(StringRef("aaa"), Toks[1].getCommandName()); + + ASSERT_EQ(tok::command, Toks[2].getKind()); + ASSERT_EQ(StringRef("bbb"), Toks[2].getCommandName()); + + ASSERT_EQ(tok::text, Toks[3].getKind()); + ASSERT_EQ(StringRef(" "), Toks[3].getText()); + + ASSERT_EQ(tok::command, Toks[4].getKind()); + ASSERT_EQ(StringRef("ccc"), Toks[4].getCommandName()); + + ASSERT_EQ(tok::text, Toks[5].getKind()); + ASSERT_EQ(StringRef("\t"), Toks[5].getText()); + + ASSERT_EQ(tok::command, Toks[6].getKind()); + ASSERT_EQ(StringRef("ddd"), Toks[6].getCommandName()); + + ASSERT_EQ(tok::newline, Toks[7].getKind()); +} + +TEST_F(CommentLexerTest, DoxygenCommand7) { + const char *Source = "// \\c\n"; + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::command, Toks[1].getKind()); + ASSERT_EQ(StringRef("c"), Toks[1].getCommandName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +// Empty verbatim block. +TEST_F(CommentLexerTest, VerbatimBlock1) { + const char *Sources[] = { + "/// \\verbatim\\endverbatim\n//", + "/** \\verbatim\\endverbatim*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(5U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[2].getKind()); + ASSERT_EQ(StringRef("endverbatim"), Toks[2].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); + ASSERT_EQ(tok::newline, Toks[4].getKind()); + } +} + +// Empty verbatim block without an end command. +TEST_F(CommentLexerTest, VerbatimBlock2) { + const char *Source = "/// \\verbatim"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +// Empty verbatim block without an end command. +TEST_F(CommentLexerTest, VerbatimBlock3) { + const char *Source = "/** \\verbatim*/"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +// Single-line verbatim block. +TEST_F(CommentLexerTest, VerbatimBlock4) { + const char *Sources[] = { + "/// Meow \\verbatim aaa \\endverbatim\n//", + "/** Meow \\verbatim aaa \\endverbatim*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(6U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Meow "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[2].getKind()); + ASSERT_EQ(StringRef(" aaa "), Toks[2].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[3].getKind()); + ASSERT_EQ(StringRef("endverbatim"), Toks[3].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[4].getKind()); + ASSERT_EQ(tok::newline, Toks[5].getKind()); + } +} + +// Single-line verbatim block without an end command. +TEST_F(CommentLexerTest, VerbatimBlock5) { + const char *Sources[] = { + "/// Meow \\verbatim aaa \n//", + "/** Meow \\verbatim aaa */" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(5U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Meow "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[2].getKind()); + ASSERT_EQ(StringRef(" aaa "), Toks[2].getVerbatimBlockText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); + ASSERT_EQ(tok::newline, Toks[4].getKind()); + } +} + +TEST_F(CommentLexerTest, VerbatimBlock6) { + const char *Source = + "// \\verbatim\n" + "// Aaa\n" + "//\n" + "// Bbb\n" + "// \\endverbatim\n"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(10U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[3].getKind()); + ASSERT_EQ(StringRef(" Aaa"), Toks[3].getVerbatimBlockText()); + + ASSERT_EQ(tok::newline, Toks[4].getKind()); + + ASSERT_EQ(tok::newline, Toks[5].getKind()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[6].getKind()); + ASSERT_EQ(StringRef(" Bbb"), Toks[6].getVerbatimBlockText()); + + ASSERT_EQ(tok::newline, Toks[7].getKind()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[8].getKind()); + ASSERT_EQ(StringRef("endverbatim"), Toks[8].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[9].getKind()); +} + +TEST_F(CommentLexerTest, VerbatimBlock7) { + const char *Source = + "/* \\verbatim\n" + " * Aaa\n" + " *\n" + " * Bbb\n" + " * \\endverbatim\n" + " */"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(10U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[2].getKind()); + ASSERT_EQ(StringRef(" Aaa"), Toks[2].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[3].getKind()); + ASSERT_EQ(StringRef(""), Toks[3].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[4].getKind()); + ASSERT_EQ(StringRef(" Bbb"), Toks[4].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[5].getKind()); + ASSERT_EQ(StringRef("endverbatim"), Toks[5].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[6].getKind()); + + ASSERT_EQ(tok::text, Toks[7].getKind()); + ASSERT_EQ(StringRef(" "), Toks[7].getText()); + + ASSERT_EQ(tok::newline, Toks[8].getKind()); + ASSERT_EQ(tok::newline, Toks[9].getKind()); +} + +// Complex test for verbatim blocks. +TEST_F(CommentLexerTest, VerbatimBlock8) { + const char *Source = + "/* Meow \\verbatim aaa\\$\\@\n" + "bbb \\endverbati\r" + "ccc\r\n" + "ddd \\endverbatim Blah \\verbatim eee\n" + "\\endverbatim BlahBlah*/"; + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(14U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Meow "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[2].getKind()); + ASSERT_EQ(StringRef(" aaa\\$\\@"), Toks[2].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[3].getKind()); + ASSERT_EQ(StringRef("bbb \\endverbati"), Toks[3].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[4].getKind()); + ASSERT_EQ(StringRef("ccc"), Toks[4].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[5].getKind()); + ASSERT_EQ(StringRef("ddd "), Toks[5].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[6].getKind()); + ASSERT_EQ(StringRef("endverbatim"), Toks[6].getVerbatimBlockName()); + + ASSERT_EQ(tok::text, Toks[7].getKind()); + ASSERT_EQ(StringRef(" Blah "), Toks[7].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[8].getKind()); + ASSERT_EQ(StringRef("verbatim"), Toks[8].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[9].getKind()); + ASSERT_EQ(StringRef(" eee"), Toks[9].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[10].getKind()); + ASSERT_EQ(StringRef("endverbatim"), Toks[10].getVerbatimBlockName()); + + ASSERT_EQ(tok::text, Toks[11].getKind()); + ASSERT_EQ(StringRef(" BlahBlah"), Toks[11].getText()); + + ASSERT_EQ(tok::newline, Toks[12].getKind()); + ASSERT_EQ(tok::newline, Toks[13].getKind()); +} + +// LaTeX verbatim blocks. +TEST_F(CommentLexerTest, VerbatimBlock9) { + const char *Source = + "/// \\f$ Aaa \\f$ \\f[ Bbb \\f] \\f{ Ccc \\f}"; + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(13U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[1].getKind()); + ASSERT_EQ(StringRef("f$"), Toks[1].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[2].getKind()); + ASSERT_EQ(StringRef(" Aaa "), Toks[2].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[3].getKind()); + ASSERT_EQ(StringRef("f$"), Toks[3].getVerbatimBlockName()); + + ASSERT_EQ(tok::text, Toks[4].getKind()); + ASSERT_EQ(StringRef(" "), Toks[4].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[5].getKind()); + ASSERT_EQ(StringRef("f["), Toks[5].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[6].getKind()); + ASSERT_EQ(StringRef(" Bbb "), Toks[6].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[7].getKind()); + ASSERT_EQ(StringRef("f]"), Toks[7].getVerbatimBlockName()); + + ASSERT_EQ(tok::text, Toks[8].getKind()); + ASSERT_EQ(StringRef(" "), Toks[8].getText()); + + ASSERT_EQ(tok::verbatim_block_begin, Toks[9].getKind()); + ASSERT_EQ(StringRef("f{"), Toks[9].getVerbatimBlockName()); + + ASSERT_EQ(tok::verbatim_block_line, Toks[10].getKind()); + ASSERT_EQ(StringRef(" Ccc "), Toks[10].getVerbatimBlockText()); + + ASSERT_EQ(tok::verbatim_block_end, Toks[11].getKind()); + ASSERT_EQ(StringRef("f}"), Toks[11].getVerbatimBlockName()); + + ASSERT_EQ(tok::newline, Toks[12].getKind()); +} + +// Empty verbatim line. +TEST_F(CommentLexerTest, VerbatimLine1) { + const char *Sources[] = { + "/// \\fn\n//", + "/** \\fn*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_line_name, Toks[1].getKind()); + ASSERT_EQ(StringRef("fn"), Toks[1].getVerbatimLineName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); + ASSERT_EQ(tok::newline, Toks[3].getKind()); + } +} + +// Verbatim line with Doxygen escape sequences, which should not be expanded. +TEST_F(CommentLexerTest, VerbatimLine2) { + const char *Sources[] = { + "/// \\fn void *foo(const char *zzz = \"\\$\");\n//", + "/** \\fn void *foo(const char *zzz = \"\\$\");*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(5U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_line_name, Toks[1].getKind()); + ASSERT_EQ(StringRef("fn"), Toks[1].getVerbatimLineName()); + + ASSERT_EQ(tok::verbatim_line_text, Toks[2].getKind()); + ASSERT_EQ(StringRef(" void *foo(const char *zzz = \"\\$\");"), + Toks[2].getVerbatimLineText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); + ASSERT_EQ(tok::newline, Toks[4].getKind()); + } +} + +// Verbatim line should not eat anything from next source line. +TEST_F(CommentLexerTest, VerbatimLine3) { + const char *Source = + "/** \\fn void *foo(const char *zzz = \"\\$\");\n" + " * Meow\n" + " */"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(9U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::verbatim_line_name, Toks[1].getKind()); + ASSERT_EQ(StringRef("fn"), Toks[1].getVerbatimLineName()); + + ASSERT_EQ(tok::verbatim_line_text, Toks[2].getKind()); + ASSERT_EQ(StringRef(" void *foo(const char *zzz = \"\\$\");"), + Toks[2].getVerbatimLineText()); + ASSERT_EQ(tok::newline, Toks[3].getKind()); + + ASSERT_EQ(tok::text, Toks[4].getKind()); + ASSERT_EQ(StringRef(" Meow"), Toks[4].getText()); + ASSERT_EQ(tok::newline, Toks[5].getKind()); + + ASSERT_EQ(tok::text, Toks[6].getKind()); + ASSERT_EQ(StringRef(" "), Toks[6].getText()); + + ASSERT_EQ(tok::newline, Toks[7].getKind()); + ASSERT_EQ(tok::newline, Toks[8].getKind()); +} + +TEST_F(CommentLexerTest, HTML1) { + const char *Source = + "// <"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("<"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTML2) { + const char *Source = + "// a<2"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" a"), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("<"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("2"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTML3) { + const char *Source = + "// < tag"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("<"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef(" tag"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTML4) { + const char *Sources[] = { + "// <tag", + "// <tag " + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML5) { + const char *Source = + "// <tag 42"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("42"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTML6) { + const char *Source = "// <tag> Meow"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(5U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_greater, Toks[2].getKind()); + + ASSERT_EQ(tok::text, Toks[3].getKind()); + ASSERT_EQ(StringRef(" Meow"), Toks[3].getText()); + + ASSERT_EQ(tok::newline, Toks[4].getKind()); +} + +TEST_F(CommentLexerTest, HTML7) { + const char *Source = "// <tag="; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("="), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTML8) { + const char *Source = "// <tag attr=> Meow"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(7U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_ident, Toks[2].getKind()); + ASSERT_EQ(StringRef("attr"), Toks[2].getHTMLIdent()); + + ASSERT_EQ(tok::html_equals, Toks[3].getKind()); + + ASSERT_EQ(tok::html_greater, Toks[4].getKind()); + + ASSERT_EQ(tok::text, Toks[5].getKind()); + ASSERT_EQ(StringRef(" Meow"), Toks[5].getText()); + + ASSERT_EQ(tok::newline, Toks[6].getKind()); +} + +TEST_F(CommentLexerTest, HTML9) { + const char *Sources[] = { + "// <tag attr", + "// <tag attr " + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_ident, Toks[2].getKind()); + ASSERT_EQ(StringRef("attr"), Toks[2].getHTMLIdent()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML10) { + const char *Sources[] = { + "// <tag attr=", + "// <tag attr =" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(5U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_ident, Toks[2].getKind()); + ASSERT_EQ(StringRef("attr"), Toks[2].getHTMLIdent()); + + ASSERT_EQ(tok::html_equals, Toks[3].getKind()); + + ASSERT_EQ(tok::newline, Toks[4].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML11) { + const char *Sources[] = { + "// <tag attr=\"", + "// <tag attr = \"", + "// <tag attr=\'", + "// <tag attr = \'" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(6U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_ident, Toks[2].getKind()); + ASSERT_EQ(StringRef("attr"), Toks[2].getHTMLIdent()); + + ASSERT_EQ(tok::html_equals, Toks[3].getKind()); + + ASSERT_EQ(tok::html_quoted_string, Toks[4].getKind()); + ASSERT_EQ(StringRef(""), Toks[4].getHTMLQuotedString()); + + ASSERT_EQ(tok::newline, Toks[5].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML12) { + const char *Source = "// <tag attr=@"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(6U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_ident, Toks[2].getKind()); + ASSERT_EQ(StringRef("attr"), Toks[2].getHTMLIdent()); + + ASSERT_EQ(tok::html_equals, Toks[3].getKind()); + + ASSERT_EQ(tok::text, Toks[4].getKind()); + ASSERT_EQ(StringRef("@"), Toks[4].getText()); + + ASSERT_EQ(tok::newline, Toks[5].getKind()); +} + +TEST_F(CommentLexerTest, HTML13) { + const char *Sources[] = { + "// <tag attr=\"val\\\"\\'val", + "// <tag attr=\"val\\\"\\'val\"", + "// <tag attr=\'val\\\"\\'val", + "// <tag attr=\'val\\\"\\'val\'" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(6U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_ident, Toks[2].getKind()); + ASSERT_EQ(StringRef("attr"), Toks[2].getHTMLIdent()); + + ASSERT_EQ(tok::html_equals, Toks[3].getKind()); + + ASSERT_EQ(tok::html_quoted_string, Toks[4].getKind()); + ASSERT_EQ(StringRef("val\\\"\\'val"), Toks[4].getHTMLQuotedString()); + + ASSERT_EQ(tok::newline, Toks[5].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML14) { + const char *Sources[] = { + "// <tag attr=\"val\\\"\\'val\">", + "// <tag attr=\'val\\\"\\'val\'>" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(7U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_ident, Toks[2].getKind()); + ASSERT_EQ(StringRef("attr"), Toks[2].getHTMLIdent()); + + ASSERT_EQ(tok::html_equals, Toks[3].getKind()); + + ASSERT_EQ(tok::html_quoted_string, Toks[4].getKind()); + ASSERT_EQ(StringRef("val\\\"\\'val"), Toks[4].getHTMLQuotedString()); + + ASSERT_EQ(tok::html_greater, Toks[5].getKind()); + + ASSERT_EQ(tok::newline, Toks[6].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML15) { + const char *Sources[] = { + "// <tag/>", + "// <tag />" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::html_slash_greater, Toks[2].getKind()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML16) { + const char *Sources[] = { + "// <tag/ Aaa", + "// <tag / Aaa" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(5U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_start_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagStartName()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("/"), Toks[2].getText()); + + ASSERT_EQ(tok::text, Toks[3].getKind()); + ASSERT_EQ(StringRef(" Aaa"), Toks[3].getText()); + + ASSERT_EQ(tok::newline, Toks[4].getKind()); + } +} + +TEST_F(CommentLexerTest, HTML17) { + const char *Source = "// </"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_end_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef(""), Toks[1].getHTMLTagEndName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTML18) { + const char *Source = "// </@"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_end_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef(""), Toks[1].getHTMLTagEndName()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("@"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTML19) { + const char *Source = "// </tag"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_end_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagEndName()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTML20) { + const char *Sources[] = { + "// </tag>", + "// </ tag>", + "// </ tag >" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::html_end_tag, Toks[1].getKind()); + ASSERT_EQ(StringRef("tag"), Toks[1].getHTMLTagEndName()); + + ASSERT_EQ(tok::html_greater, Toks[2].getKind()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); + } +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences1) { + const char *Source = "// &"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences2) { + const char *Source = "// &!"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("!"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences3) { + const char *Source = "// &"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences4) { + const char *Source = "// &!"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("!"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences5) { + const char *Source = "// &#"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&#"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences6) { + const char *Source = "// &#a"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&#"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("a"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences7) { + const char *Source = "// *"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("*"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences8) { + const char *Source = "// *a"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("*"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("a"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences9) { + const char *Source = "// &#x"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&#x"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences10) { + const char *Source = "// &#xz"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&#x"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("z"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences11) { + const char *Source = "// «"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("«"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences12) { + const char *Source = "// «z"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("«"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("z"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences13) { + const char *Source = "// &"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&"), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences14) { + const char *Source = "// &<"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef("<"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences15) { + const char *Source = "// & meow"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(4U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("&"), Toks[1].getText()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef(" meow"), Toks[2].getText()); + + ASSERT_EQ(tok::newline, Toks[3].getKind()); +} + +TEST_F(CommentLexerTest, HTMLCharacterReferences16) { + const char *Sources[] = { + "// =", + "// =", + "// =" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + std::vector<Token> Toks; + + lexString(Sources[i], Toks); + + ASSERT_EQ(3U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" "), Toks[0].getText()); + + ASSERT_EQ(tok::text, Toks[1].getKind()); + ASSERT_EQ(StringRef("="), Toks[1].getText()); + + ASSERT_EQ(tok::newline, Toks[2].getKind()); + } +} + +TEST_F(CommentLexerTest, MultipleComments) { + const char *Source = + "// Aaa\n" + "/// Bbb\n" + "/* Ccc\n" + " * Ddd*/\n" + "/** Eee*/"; + + std::vector<Token> Toks; + + lexString(Source, Toks); + + ASSERT_EQ(12U, Toks.size()); + + ASSERT_EQ(tok::text, Toks[0].getKind()); + ASSERT_EQ(StringRef(" Aaa"), Toks[0].getText()); + ASSERT_EQ(tok::newline, Toks[1].getKind()); + + ASSERT_EQ(tok::text, Toks[2].getKind()); + ASSERT_EQ(StringRef(" Bbb"), Toks[2].getText()); + ASSERT_EQ(tok::newline, Toks[3].getKind()); + + ASSERT_EQ(tok::text, Toks[4].getKind()); + ASSERT_EQ(StringRef(" Ccc"), Toks[4].getText()); + ASSERT_EQ(tok::newline, Toks[5].getKind()); + + ASSERT_EQ(tok::text, Toks[6].getKind()); + ASSERT_EQ(StringRef(" Ddd"), Toks[6].getText()); + ASSERT_EQ(tok::newline, Toks[7].getKind()); + ASSERT_EQ(tok::newline, Toks[8].getKind()); + + ASSERT_EQ(tok::text, Toks[9].getKind()); + ASSERT_EQ(StringRef(" Eee"), Toks[9].getText()); + + ASSERT_EQ(tok::newline, Toks[10].getKind()); + ASSERT_EQ(tok::newline, Toks[11].getKind()); +} + +} // end namespace comments +} // end namespace clang + diff --git a/unittests/AST/CommentParser.cpp b/unittests/AST/CommentParser.cpp new file mode 100644 index 0000000..7258a7e --- /dev/null +++ b/unittests/AST/CommentParser.cpp @@ -0,0 +1,1383 @@ +//===- unittests/AST/CommentParser.cpp ------ Comment parser tests --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/AST/Comment.h" +#include "clang/AST/CommentLexer.h" +#include "clang/AST/CommentParser.h" +#include "clang/AST/CommentSema.h" +#include "clang/AST/CommentCommandTraits.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Allocator.h" +#include <vector> + +#include "gtest/gtest.h" + +using namespace llvm; +using namespace clang; + +namespace clang { +namespace comments { + +namespace { + +const bool DEBUG = true; + +class CommentParserTest : public ::testing::Test { +protected: + CommentParserTest() + : FileMgr(FileMgrOpts), + DiagID(new DiagnosticIDs()), + Diags(DiagID, new IgnoringDiagConsumer()), + SourceMgr(Diags, FileMgr) { + } + + FileSystemOptions FileMgrOpts; + FileManager FileMgr; + IntrusiveRefCntPtr<DiagnosticIDs> DiagID; + DiagnosticsEngine Diags; + SourceManager SourceMgr; + llvm::BumpPtrAllocator Allocator; + + FullComment *parseString(const char *Source); +}; + +FullComment *CommentParserTest::parseString(const char *Source) { + MemoryBuffer *Buf = MemoryBuffer::getMemBuffer(Source); + FileID File = SourceMgr.createFileIDForMemBuffer(Buf); + SourceLocation Begin = SourceMgr.getLocForStartOfFile(File); + + comments::CommandTraits Traits; + comments::Lexer L(Allocator, Traits, Begin, CommentOptions(), + Source, Source + strlen(Source)); + + comments::Sema S(Allocator, SourceMgr, Diags, Traits); + comments::Parser P(L, S, Allocator, SourceMgr, Diags, Traits); + comments::FullComment *FC = P.parseFullComment(); + + if (DEBUG) { + llvm::errs() << "=== Source:\n" << Source << "\n=== AST:\n"; + FC->dump(SourceMgr); + } + + Token Tok; + L.lex(Tok); + if (Tok.is(tok::eof)) + return FC; + else + return NULL; +} + +::testing::AssertionResult HasChildCount(const Comment *C, size_t Count) { + if (!C) + return ::testing::AssertionFailure() << "Comment is NULL"; + + if (Count != C->child_count()) + return ::testing::AssertionFailure() + << "Count = " << Count + << ", child_count = " << C->child_count(); + + return ::testing::AssertionSuccess(); +} + +template <typename T> +::testing::AssertionResult GetChildAt(const Comment *C, + size_t Idx, + T *&Child) { + if (!C) + return ::testing::AssertionFailure() << "Comment is NULL"; + + if (Idx >= C->child_count()) + return ::testing::AssertionFailure() + << "Idx out of range. Idx = " << Idx + << ", child_count = " << C->child_count(); + + Comment::child_iterator I = C->child_begin() + Idx; + Comment *CommentChild = *I; + if (!CommentChild) + return ::testing::AssertionFailure() << "Child is NULL"; + + Child = dyn_cast<T>(CommentChild); + if (!Child) + return ::testing::AssertionFailure() + << "Child is not of requested type, but a " + << CommentChild->getCommentKindName(); + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasTextAt(const Comment *C, + size_t Idx, + StringRef Text) { + TextComment *TC; + ::testing::AssertionResult AR = GetChildAt(C, Idx, TC); + if (!AR) + return AR; + + StringRef ActualText = TC->getText(); + if (ActualText != Text) + return ::testing::AssertionFailure() + << "TextComment has text \"" << ActualText.str() << "\", " + "expected \"" << Text.str() << "\""; + + if (TC->hasTrailingNewline()) + return ::testing::AssertionFailure() + << "TextComment has a trailing newline"; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasTextWithNewlineAt(const Comment *C, + size_t Idx, + StringRef Text) { + TextComment *TC; + ::testing::AssertionResult AR = GetChildAt(C, Idx, TC); + if (!AR) + return AR; + + StringRef ActualText = TC->getText(); + if (ActualText != Text) + return ::testing::AssertionFailure() + << "TextComment has text \"" << ActualText.str() << "\", " + "expected \"" << Text.str() << "\""; + + if (!TC->hasTrailingNewline()) + return ::testing::AssertionFailure() + << "TextComment has no trailing newline"; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasBlockCommandAt(const Comment *C, + size_t Idx, + BlockCommandComment *&BCC, + StringRef Name, + ParagraphComment *&Paragraph) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, BCC); + if (!AR) + return AR; + + StringRef ActualName = BCC->getCommandName(); + if (ActualName != Name) + return ::testing::AssertionFailure() + << "BlockCommandComment has name \"" << ActualName.str() << "\", " + "expected \"" << Name.str() << "\""; + + Paragraph = BCC->getParagraph(); + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasParamCommandAt( + const Comment *C, + size_t Idx, + ParamCommandComment *&PCC, + StringRef CommandName, + ParamCommandComment::PassDirection Direction, + bool IsDirectionExplicit, + StringRef ParamName, + ParagraphComment *&Paragraph) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, PCC); + if (!AR) + return AR; + + StringRef ActualCommandName = PCC->getCommandName(); + if (ActualCommandName != CommandName) + return ::testing::AssertionFailure() + << "ParamCommandComment has name \"" << ActualCommandName.str() << "\", " + "expected \"" << CommandName.str() << "\""; + + if (PCC->getDirection() != Direction) + return ::testing::AssertionFailure() + << "ParamCommandComment has direction " << PCC->getDirection() << ", " + "expected " << Direction; + + if (PCC->isDirectionExplicit() != IsDirectionExplicit) + return ::testing::AssertionFailure() + << "ParamCommandComment has " + << (PCC->isDirectionExplicit() ? "explicit" : "implicit") + << " direction, " + "expected " << (IsDirectionExplicit ? "explicit" : "implicit"); + + if (!ParamName.empty() && !PCC->hasParamName()) + return ::testing::AssertionFailure() + << "ParamCommandComment has no parameter name"; + + StringRef ActualParamName = PCC->hasParamName() ? PCC->getParamName() : ""; + if (ActualParamName != ParamName) + return ::testing::AssertionFailure() + << "ParamCommandComment has parameter name \"" << ActualParamName.str() + << "\", " + "expected \"" << ParamName.str() << "\""; + + Paragraph = PCC->getParagraph(); + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasTParamCommandAt( + const Comment *C, + size_t Idx, + TParamCommandComment *&TPCC, + StringRef CommandName, + StringRef ParamName, + ParagraphComment *&Paragraph) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, TPCC); + if (!AR) + return AR; + + StringRef ActualCommandName = TPCC->getCommandName(); + if (ActualCommandName != CommandName) + return ::testing::AssertionFailure() + << "TParamCommandComment has name \"" << ActualCommandName.str() << "\", " + "expected \"" << CommandName.str() << "\""; + + if (!ParamName.empty() && !TPCC->hasParamName()) + return ::testing::AssertionFailure() + << "TParamCommandComment has no parameter name"; + + StringRef ActualParamName = TPCC->hasParamName() ? TPCC->getParamName() : ""; + if (ActualParamName != ParamName) + return ::testing::AssertionFailure() + << "TParamCommandComment has parameter name \"" << ActualParamName.str() + << "\", " + "expected \"" << ParamName.str() << "\""; + + Paragraph = TPCC->getParagraph(); + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasInlineCommandAt(const Comment *C, + size_t Idx, + InlineCommandComment *&ICC, + StringRef Name) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, ICC); + if (!AR) + return AR; + + StringRef ActualName = ICC->getCommandName(); + if (ActualName != Name) + return ::testing::AssertionFailure() + << "InlineCommandComment has name \"" << ActualName.str() << "\", " + "expected \"" << Name.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +struct NoArgs {}; + +::testing::AssertionResult HasInlineCommandAt(const Comment *C, + size_t Idx, + InlineCommandComment *&ICC, + StringRef Name, + NoArgs) { + ::testing::AssertionResult AR = HasInlineCommandAt(C, Idx, ICC, Name); + if (!AR) + return AR; + + if (ICC->getNumArgs() != 0) + return ::testing::AssertionFailure() + << "InlineCommandComment has " << ICC->getNumArgs() << " arg(s), " + "expected 0"; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasInlineCommandAt(const Comment *C, + size_t Idx, + InlineCommandComment *&ICC, + StringRef Name, + StringRef Arg) { + ::testing::AssertionResult AR = HasInlineCommandAt(C, Idx, ICC, Name); + if (!AR) + return AR; + + if (ICC->getNumArgs() != 1) + return ::testing::AssertionFailure() + << "InlineCommandComment has " << ICC->getNumArgs() << " arg(s), " + "expected 1"; + + StringRef ActualArg = ICC->getArgText(0); + if (ActualArg != Arg) + return ::testing::AssertionFailure() + << "InlineCommandComment has argument \"" << ActualArg.str() << "\", " + "expected \"" << Arg.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasHTMLStartTagAt(const Comment *C, + size_t Idx, + HTMLStartTagComment *&HST, + StringRef TagName) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, HST); + if (!AR) + return AR; + + StringRef ActualTagName = HST->getTagName(); + if (ActualTagName != TagName) + return ::testing::AssertionFailure() + << "HTMLStartTagComment has name \"" << ActualTagName.str() << "\", " + "expected \"" << TagName.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +struct SelfClosing {}; + +::testing::AssertionResult HasHTMLStartTagAt(const Comment *C, + size_t Idx, + HTMLStartTagComment *&HST, + StringRef TagName, + SelfClosing) { + ::testing::AssertionResult AR = HasHTMLStartTagAt(C, Idx, HST, TagName); + if (!AR) + return AR; + + if (!HST->isSelfClosing()) + return ::testing::AssertionFailure() + << "HTMLStartTagComment is not self-closing"; + + return ::testing::AssertionSuccess(); +} + + +struct NoAttrs {}; + +::testing::AssertionResult HasHTMLStartTagAt(const Comment *C, + size_t Idx, + HTMLStartTagComment *&HST, + StringRef TagName, + NoAttrs) { + ::testing::AssertionResult AR = HasHTMLStartTagAt(C, Idx, HST, TagName); + if (!AR) + return AR; + + if (HST->isSelfClosing()) + return ::testing::AssertionFailure() + << "HTMLStartTagComment is self-closing"; + + if (HST->getNumAttrs() != 0) + return ::testing::AssertionFailure() + << "HTMLStartTagComment has " << HST->getNumAttrs() << " attr(s), " + "expected 0"; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasHTMLStartTagAt(const Comment *C, + size_t Idx, + HTMLStartTagComment *&HST, + StringRef TagName, + StringRef AttrName, + StringRef AttrValue) { + ::testing::AssertionResult AR = HasHTMLStartTagAt(C, Idx, HST, TagName); + if (!AR) + return AR; + + if (HST->isSelfClosing()) + return ::testing::AssertionFailure() + << "HTMLStartTagComment is self-closing"; + + if (HST->getNumAttrs() != 1) + return ::testing::AssertionFailure() + << "HTMLStartTagComment has " << HST->getNumAttrs() << " attr(s), " + "expected 1"; + + StringRef ActualName = HST->getAttr(0).Name; + if (ActualName != AttrName) + return ::testing::AssertionFailure() + << "HTMLStartTagComment has attr \"" << ActualName.str() << "\", " + "expected \"" << AttrName.str() << "\""; + + StringRef ActualValue = HST->getAttr(0).Value; + if (ActualValue != AttrValue) + return ::testing::AssertionFailure() + << "HTMLStartTagComment has attr value \"" << ActualValue.str() << "\", " + "expected \"" << AttrValue.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasHTMLEndTagAt(const Comment *C, + size_t Idx, + HTMLEndTagComment *&HET, + StringRef TagName) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, HET); + if (!AR) + return AR; + + StringRef ActualTagName = HET->getTagName(); + if (ActualTagName != TagName) + return ::testing::AssertionFailure() + << "HTMLEndTagComment has name \"" << ActualTagName.str() << "\", " + "expected \"" << TagName.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasParagraphCommentAt(const Comment *C, + size_t Idx, + StringRef Text) { + ParagraphComment *PC; + + { + ::testing::AssertionResult AR = GetChildAt(C, Idx, PC); + if (!AR) + return AR; + } + + { + ::testing::AssertionResult AR = HasChildCount(PC, 1); + if (!AR) + return AR; + } + + { + ::testing::AssertionResult AR = HasTextAt(PC, 0, Text); + if (!AR) + return AR; + } + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasVerbatimBlockAt(const Comment *C, + size_t Idx, + VerbatimBlockComment *&VBC, + StringRef Name, + StringRef CloseName) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, VBC); + if (!AR) + return AR; + + StringRef ActualName = VBC->getCommandName(); + if (ActualName != Name) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has name \"" << ActualName.str() << "\", " + "expected \"" << Name.str() << "\""; + + StringRef ActualCloseName = VBC->getCloseName(); + if (ActualCloseName != CloseName) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has closing command name \"" + << ActualCloseName.str() << "\", " + "expected \"" << CloseName.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +struct NoLines {}; +struct Lines {}; + +::testing::AssertionResult HasVerbatimBlockAt(const Comment *C, + size_t Idx, + VerbatimBlockComment *&VBC, + StringRef Name, + StringRef CloseName, + NoLines) { + ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Idx, VBC, Name, + CloseName); + if (!AR) + return AR; + + if (VBC->getNumLines() != 0) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has " << VBC->getNumLines() << " lines(s), " + "expected 0"; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasVerbatimBlockAt(const Comment *C, + size_t Idx, + VerbatimBlockComment *&VBC, + StringRef Name, + StringRef CloseName, + Lines, + StringRef Line0) { + ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Idx, VBC, Name, + CloseName); + if (!AR) + return AR; + + if (VBC->getNumLines() != 1) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has " << VBC->getNumLines() << " lines(s), " + "expected 1"; + + StringRef ActualLine0 = VBC->getText(0); + if (ActualLine0 != Line0) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has lines[0] \"" << ActualLine0.str() << "\", " + "expected \"" << Line0.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasVerbatimBlockAt(const Comment *C, + size_t Idx, + VerbatimBlockComment *&VBC, + StringRef Name, + StringRef CloseName, + Lines, + StringRef Line0, + StringRef Line1) { + ::testing::AssertionResult AR = HasVerbatimBlockAt(C, Idx, VBC, Name, + CloseName); + if (!AR) + return AR; + + if (VBC->getNumLines() != 2) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has " << VBC->getNumLines() << " lines(s), " + "expected 2"; + + StringRef ActualLine0 = VBC->getText(0); + if (ActualLine0 != Line0) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has lines[0] \"" << ActualLine0.str() << "\", " + "expected \"" << Line0.str() << "\""; + + StringRef ActualLine1 = VBC->getText(1); + if (ActualLine1 != Line1) + return ::testing::AssertionFailure() + << "VerbatimBlockComment has lines[1] \"" << ActualLine1.str() << "\", " + "expected \"" << Line1.str() << "\""; + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult HasVerbatimLineAt(const Comment *C, + size_t Idx, + VerbatimLineComment *&VLC, + StringRef Name, + StringRef Text) { + ::testing::AssertionResult AR = GetChildAt(C, Idx, VLC); + if (!AR) + return AR; + + StringRef ActualName = VLC->getCommandName(); + if (ActualName != Name) + return ::testing::AssertionFailure() + << "VerbatimLineComment has name \"" << ActualName.str() << "\", " + "expected \"" << Name.str() << "\""; + + StringRef ActualText = VLC->getText(); + if (ActualText != Text) + return ::testing::AssertionFailure() + << "VerbatimLineComment has text \"" << ActualText.str() << "\", " + "expected \"" << Text.str() << "\""; + + return ::testing::AssertionSuccess(); +} + + +TEST_F(CommentParserTest, Basic1) { + const char *Source = "//"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 0)); +} + +TEST_F(CommentParserTest, Basic2) { + const char *Source = "// Meow"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " Meow")); +} + +TEST_F(CommentParserTest, Basic3) { + const char *Source = + "// Aaa\n" + "// Bbb"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextWithNewlineAt(PC, 0, " Aaa")); + ASSERT_TRUE(HasTextAt(PC, 1, " Bbb")); + } +} + +TEST_F(CommentParserTest, Paragraph1) { + const char *Sources[] = { + "// Aaa\n" + "//\n" + "// Bbb", + + "// Aaa\n" + "//\n" + "//\n" + "// Bbb", + }; + + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " Aaa")); + ASSERT_TRUE(HasParagraphCommentAt(FC, 1, " Bbb")); + } +} + +TEST_F(CommentParserTest, Paragraph2) { + const char *Source = + "// \\brief Aaa\n" + "//\n" + "// Bbb"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 3)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, 1, BCC, "brief", PC)); + + ASSERT_TRUE(HasParagraphCommentAt(BCC, 0, " Aaa")); + } + ASSERT_TRUE(HasParagraphCommentAt(FC, 2, " Bbb")); +} + +TEST_F(CommentParserTest, Paragraph3) { + const char *Source = "// \\brief \\author"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 3)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, 1, BCC, "brief", PC)); + + ASSERT_TRUE(HasParagraphCommentAt(BCC, 0, " ")); + } + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "author", PC)); + + ASSERT_TRUE(GetChildAt(BCC, 0, PC)); + ASSERT_TRUE(HasChildCount(PC, 0)); + } +} + +TEST_F(CommentParserTest, Paragraph4) { + const char *Source = + "// \\brief Aaa\n" + "// Bbb \\author\n" + "// Ccc"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 3)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, 1, BCC, "brief", PC)); + + ASSERT_TRUE(GetChildAt(BCC, 0, PC)); + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextWithNewlineAt(PC, 0, " Aaa")); + ASSERT_TRUE(HasTextAt(PC, 1, " Bbb ")); + } + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "author", PC)); + + ASSERT_TRUE(HasParagraphCommentAt(BCC, 0, " Ccc")); + } +} + +TEST_F(CommentParserTest, ParamCommand1) { + const char *Source = "// \\param aaa"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + ParamCommandComment *PCC; + ParagraphComment *PC; + ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param", + ParamCommandComment::In, + /* IsDirectionExplicit = */ false, + "aaa", PC)); + ASSERT_TRUE(HasChildCount(PCC, 1)); + ASSERT_TRUE(HasChildCount(PC, 0)); + } +} + +TEST_F(CommentParserTest, ParamCommand2) { + const char *Source = "// \\param\\brief"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 3)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + ParamCommandComment *PCC; + ParagraphComment *PC; + ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param", + ParamCommandComment::In, + /* IsDirectionExplicit = */ false, + "", PC)); + ASSERT_TRUE(HasChildCount(PCC, 1)); + ASSERT_TRUE(HasChildCount(PC, 0)); + } + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "brief", PC)); + ASSERT_TRUE(HasChildCount(PC, 0)); + } +} + +TEST_F(CommentParserTest, ParamCommand3) { + const char *Sources[] = { + "// \\param aaa Bbb\n", + "// \\param\n" + "// aaa Bbb\n", + "// \\param \n" + "// aaa Bbb\n", + "// \\param aaa\n" + "// Bbb\n" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + ParamCommandComment *PCC; + ParagraphComment *PC; + ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param", + ParamCommandComment::In, + /* IsDirectionExplicit = */ false, + "aaa", PC)); + ASSERT_TRUE(HasChildCount(PCC, 1)); + ASSERT_TRUE(HasParagraphCommentAt(PCC, 0, " Bbb")); + } + } +} + +TEST_F(CommentParserTest, ParamCommand4) { + const char *Sources[] = { + "// \\param [in] aaa Bbb\n", + "// \\param[in] aaa Bbb\n", + "// \\param\n" + "// [in] aaa Bbb\n", + "// \\param [in]\n" + "// aaa Bbb\n", + "// \\param [in] aaa\n" + "// Bbb\n", + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + ParamCommandComment *PCC; + ParagraphComment *PC; + ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param", + ParamCommandComment::In, + /* IsDirectionExplicit = */ true, + "aaa", PC)); + ASSERT_TRUE(HasChildCount(PCC, 1)); + ASSERT_TRUE(HasParagraphCommentAt(PCC, 0, " Bbb")); + } + } +} + +TEST_F(CommentParserTest, ParamCommand5) { + const char *Sources[] = { + "// \\param [out] aaa Bbb\n", + "// \\param[out] aaa Bbb\n", + "// \\param\n" + "// [out] aaa Bbb\n", + "// \\param [out]\n" + "// aaa Bbb\n", + "// \\param [out] aaa\n" + "// Bbb\n", + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + ParamCommandComment *PCC; + ParagraphComment *PC; + ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param", + ParamCommandComment::Out, + /* IsDirectionExplicit = */ true, + "aaa", PC)); + ASSERT_TRUE(HasChildCount(PCC, 1)); + ASSERT_TRUE(HasParagraphCommentAt(PCC, 0, " Bbb")); + } + } +} + +TEST_F(CommentParserTest, ParamCommand6) { + const char *Sources[] = { + "// \\param [in,out] aaa Bbb\n", + "// \\param[in,out] aaa Bbb\n", + "// \\param [in, out] aaa Bbb\n", + "// \\param [in,\n" + "// out] aaa Bbb\n", + "// \\param [in,out]\n" + "// aaa Bbb\n", + "// \\param [in,out] aaa\n" + "// Bbb\n" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + ParamCommandComment *PCC; + ParagraphComment *PC; + ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param", + ParamCommandComment::InOut, + /* IsDirectionExplicit = */ true, + "aaa", PC)); + ASSERT_TRUE(HasChildCount(PCC, 1)); + ASSERT_TRUE(HasParagraphCommentAt(PCC, 0, " Bbb")); + } + } +} + +TEST_F(CommentParserTest, ParamCommand7) { + const char *Source = + "// \\param aaa \\% Bbb \\$ ccc\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + ParamCommandComment *PCC; + ParagraphComment *PC; + ASSERT_TRUE(HasParamCommandAt(FC, 1, PCC, "param", + ParamCommandComment::In, + /* IsDirectionExplicit = */ false, + "aaa", PC)); + ASSERT_TRUE(HasChildCount(PCC, 1)); + + ASSERT_TRUE(HasChildCount(PC, 5)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasTextAt(PC, 1, "%")); + ASSERT_TRUE(HasTextAt(PC, 2, " Bbb ")); + ASSERT_TRUE(HasTextAt(PC, 3, "$")); + ASSERT_TRUE(HasTextAt(PC, 4, " ccc")); + } +} + +TEST_F(CommentParserTest, TParamCommand1) { + const char *Sources[] = { + "// \\tparam aaa Bbb\n", + "// \\tparam\n" + "// aaa Bbb\n", + "// \\tparam \n" + "// aaa Bbb\n", + "// \\tparam aaa\n" + "// Bbb\n" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + TParamCommandComment *TPCC; + ParagraphComment *PC; + ASSERT_TRUE(HasTParamCommandAt(FC, 1, TPCC, "tparam", + "aaa", PC)); + ASSERT_TRUE(HasChildCount(TPCC, 1)); + ASSERT_TRUE(HasParagraphCommentAt(TPCC, 0, " Bbb")); + } + } +} + +TEST_F(CommentParserTest, TParamCommand2) { + const char *Source = "// \\tparam\\brief"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 3)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + TParamCommandComment *TPCC; + ParagraphComment *PC; + ASSERT_TRUE(HasTParamCommandAt(FC, 1, TPCC, "tparam", "", PC)); + ASSERT_TRUE(HasChildCount(TPCC, 1)); + ASSERT_TRUE(HasChildCount(PC, 0)); + } + { + BlockCommandComment *BCC; + ParagraphComment *PC; + ASSERT_TRUE(HasBlockCommandAt(FC, 2, BCC, "brief", PC)); + ASSERT_TRUE(HasChildCount(PC, 0)); + } +} + + +TEST_F(CommentParserTest, InlineCommand1) { + const char *Source = "// \\c"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + InlineCommandComment *ICC; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", NoArgs())); + } +} + +TEST_F(CommentParserTest, InlineCommand2) { + const char *Source = "// \\c "; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + InlineCommandComment *ICC; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 3)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", NoArgs())); + ASSERT_TRUE(HasTextAt(PC, 2, " ")); + } +} + +TEST_F(CommentParserTest, InlineCommand3) { + const char *Source = "// \\c aaa\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + InlineCommandComment *ICC; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", "aaa")); + } +} + +TEST_F(CommentParserTest, InlineCommand4) { + const char *Source = "// \\c aaa bbb"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + InlineCommandComment *ICC; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 3)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "c", "aaa")); + ASSERT_TRUE(HasTextAt(PC, 2, " bbb")); + } +} + +TEST_F(CommentParserTest, InlineCommand5) { + const char *Source = "// \\unknown aaa\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + InlineCommandComment *ICC; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 3)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasInlineCommandAt(PC, 1, ICC, "unknown", NoArgs())); + ASSERT_TRUE(HasTextAt(PC, 2, " aaa")); + } +} + +TEST_F(CommentParserTest, HTML1) { + const char *Sources[] = { + "// <a", + "// <a>", + "// <a >" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + HTMLStartTagComment *HST; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasHTMLStartTagAt(PC, 1, HST, "a", NoAttrs())); + } + } +} + +TEST_F(CommentParserTest, HTML2) { + const char *Sources[] = { + "// <br/>", + "// <br />" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + HTMLStartTagComment *HST; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasHTMLStartTagAt(PC, 1, HST, "br", SelfClosing())); + } + } +} + +TEST_F(CommentParserTest, HTML3) { + const char *Sources[] = { + "// <a href", + "// <a href ", + "// <a href>", + "// <a href >", + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + HTMLStartTagComment *HST; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasHTMLStartTagAt(PC, 1, HST, "a", "href", "")); + } + } +} + +TEST_F(CommentParserTest, HTML4) { + const char *Sources[] = { + "// <a href=\"bbb\"", + "// <a href=\"bbb\">", + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + HTMLStartTagComment *HST; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasHTMLStartTagAt(PC, 1, HST, "a", "href", "bbb")); + } + } +} + +TEST_F(CommentParserTest, HTML5) { + const char *Sources[] = { + "// </a", + "// </a>", + "// </a >" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + HTMLEndTagComment *HET; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 2)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasHTMLEndTagAt(PC, 1, HET, "a")); + } + } +} + +TEST_F(CommentParserTest, HTML6) { + const char *Source = + "// <pre>\n" + "// Aaa\n" + "// Bbb\n" + "// </pre>\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + ParagraphComment *PC; + HTMLStartTagComment *HST; + HTMLEndTagComment *HET; + ASSERT_TRUE(GetChildAt(FC, 0, PC)); + + ASSERT_TRUE(HasChildCount(PC, 6)); + ASSERT_TRUE(HasTextAt(PC, 0, " ")); + ASSERT_TRUE(HasHTMLStartTagAt(PC, 1, HST, "pre", NoAttrs())); + ASSERT_TRUE(HasTextWithNewlineAt(PC, 2, " Aaa")); + ASSERT_TRUE(HasTextWithNewlineAt(PC, 3, " Bbb")); + ASSERT_TRUE(HasTextAt(PC, 4, " ")); + ASSERT_TRUE(HasHTMLEndTagAt(PC, 5, HET, "pre")); + } +} + +TEST_F(CommentParserTest, VerbatimBlock1) { + const char *Source = "// \\verbatim\\endverbatim\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimBlockComment *VCC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VCC, "verbatim", "endverbatim", + NoLines())); + } +} + +TEST_F(CommentParserTest, VerbatimBlock2) { + const char *Source = "// \\verbatim Aaa \\endverbatim\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimBlockComment *VBC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim", + Lines(), " Aaa ")); + } +} + +TEST_F(CommentParserTest, VerbatimBlock3) { + const char *Source = "// \\verbatim Aaa\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimBlockComment *VBC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "", + Lines(), " Aaa")); + } +} + +TEST_F(CommentParserTest, VerbatimBlock4) { + const char *Source = + "//\\verbatim\n" + "//\\endverbatim\n"; + + FullComment *FC = parseString(Source); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + VerbatimBlockComment *VBC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 0, VBC, "verbatim", "endverbatim", + NoLines())); + } +} + +TEST_F(CommentParserTest, VerbatimBlock5) { + const char *Sources[] = { + "//\\verbatim\n" + "// Aaa\n" + "//\\endverbatim\n", + + "/*\\verbatim\n" + " * Aaa\n" + " *\\endverbatim*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 1)); + + { + VerbatimBlockComment *VBC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 0, VBC, "verbatim", "endverbatim", + Lines(), " Aaa")); + } + } +} + +TEST_F(CommentParserTest, VerbatimBlock6) { + const char *Sources[] = { + "// \\verbatim\n" + "// Aaa\n" + "// \\endverbatim\n", + + "/* \\verbatim\n" + " * Aaa\n" + " * \\endverbatim*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimBlockComment *VBC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim", + Lines(), " Aaa")); + } + } +} + +TEST_F(CommentParserTest, VerbatimBlock7) { + const char *Sources[] = { + "// \\verbatim\n" + "// Aaa\n" + "// Bbb\n" + "// \\endverbatim\n", + + "/* \\verbatim\n" + " * Aaa\n" + " * Bbb\n" + " * \\endverbatim*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimBlockComment *VBC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim", + Lines(), " Aaa", " Bbb")); + } + } +} + +TEST_F(CommentParserTest, VerbatimBlock8) { + const char *Sources[] = { + "// \\verbatim\n" + "// Aaa\n" + "//\n" + "// Bbb\n" + "// \\endverbatim\n", + + "/* \\verbatim\n" + " * Aaa\n" + " *\n" + " * Bbb\n" + " * \\endverbatim*/" + }; + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimBlockComment *VBC; + ASSERT_TRUE(HasVerbatimBlockAt(FC, 1, VBC, "verbatim", "endverbatim")); + ASSERT_EQ(3U, VBC->getNumLines()); + ASSERT_EQ(" Aaa", VBC->getText(0)); + ASSERT_EQ("", VBC->getText(1)); + ASSERT_EQ(" Bbb", VBC->getText(2)); + } + } +} + +TEST_F(CommentParserTest, VerbatimLine1) { + const char *Sources[] = { + "// \\fn", + "// \\fn\n" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimLineComment *VLC; + ASSERT_TRUE(HasVerbatimLineAt(FC, 1, VLC, "fn", "")); + } + } +} + +TEST_F(CommentParserTest, VerbatimLine2) { + const char *Sources[] = { + "/// \\fn void *foo(const char *zzz = \"\\$\");\n//", + "/** \\fn void *foo(const char *zzz = \"\\$\");*/" + }; + + for (size_t i = 0, e = array_lengthof(Sources); i != e; i++) { + FullComment *FC = parseString(Sources[i]); + ASSERT_TRUE(HasChildCount(FC, 2)); + + ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " ")); + { + VerbatimLineComment *VLC; + ASSERT_TRUE(HasVerbatimLineAt(FC, 1, VLC, "fn", + " void *foo(const char *zzz = \"\\$\");")); + } + } +} + +} // unnamed namespace + +} // end namespace comments +} // end namespace clang + diff --git a/unittests/AST/Makefile b/unittests/AST/Makefile new file mode 100644 index 0000000..31cd5de --- /dev/null +++ b/unittests/AST/Makefile @@ -0,0 +1,15 @@ +##===- unittests/AST/Makefile ------------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL = ../.. +TESTNAME = AST +LINK_COMPONENTS := support mc +USEDLIBS = clangAST.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/unittests/Makefile diff --git a/unittests/ASTMatchers/ASTMatchersTest.cpp b/unittests/ASTMatchers/ASTMatchersTest.cpp new file mode 100644 index 0000000..cf37c7d --- /dev/null +++ b/unittests/ASTMatchers/ASTMatchersTest.cpp @@ -0,0 +1,2312 @@ +//===- unittest/Tooling/ASTMatchersTest.cpp - AST matcher unit tests ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ASTMatchersTest.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ast_matchers { + +#if GTEST_HAS_DEATH_TEST +TEST(HasNameDeathTest, DiesOnEmptyName) { + ASSERT_DEBUG_DEATH({ + DeclarationMatcher HasEmptyName = record(hasName("")); + EXPECT_TRUE(notMatches("class X {};", HasEmptyName)); + }, ""); +} + +TEST(HasNameDeathTest, DiesOnEmptyPattern) { + ASSERT_DEBUG_DEATH({ + DeclarationMatcher HasEmptyName = record(matchesName("")); + EXPECT_TRUE(notMatches("class X {};", HasEmptyName)); + }, ""); +} + +TEST(IsDerivedFromDeathTest, DiesOnEmptyBaseName) { + ASSERT_DEBUG_DEATH({ + DeclarationMatcher IsDerivedFromEmpty = record(isDerivedFrom("")); + EXPECT_TRUE(notMatches("class X {};", IsDerivedFromEmpty)); + }, ""); +} +#endif + +TEST(Decl, MatchesDeclarations) { + EXPECT_TRUE(notMatches("", decl(usingDecl()))); + EXPECT_TRUE(matches("namespace x { class X {}; } using x::X;", + decl(usingDecl()))); +} + +TEST(NameableDeclaration, MatchesVariousDecls) { + DeclarationMatcher NamedX = nameableDeclaration(hasName("X")); + EXPECT_TRUE(matches("typedef int X;", NamedX)); + EXPECT_TRUE(matches("int X;", NamedX)); + EXPECT_TRUE(matches("class foo { virtual void X(); };", NamedX)); + EXPECT_TRUE(matches("void foo() try { } catch(int X) { }", NamedX)); + EXPECT_TRUE(matches("void foo() { int X; }", NamedX)); + EXPECT_TRUE(matches("namespace X { }", NamedX)); + EXPECT_TRUE(matches("enum X { A, B, C };", NamedX)); + + EXPECT_TRUE(notMatches("#define X 1", NamedX)); +} + +TEST(NameableDeclaration, REMatchesVariousDecls) { + DeclarationMatcher NamedX = nameableDeclaration(matchesName("::X")); + EXPECT_TRUE(matches("typedef int Xa;", NamedX)); + EXPECT_TRUE(matches("int Xb;", NamedX)); + EXPECT_TRUE(matches("class foo { virtual void Xc(); };", NamedX)); + EXPECT_TRUE(matches("void foo() try { } catch(int Xdef) { }", NamedX)); + EXPECT_TRUE(matches("void foo() { int Xgh; }", NamedX)); + EXPECT_TRUE(matches("namespace Xij { }", NamedX)); + EXPECT_TRUE(matches("enum X { A, B, C };", NamedX)); + + EXPECT_TRUE(notMatches("#define Xkl 1", NamedX)); + + DeclarationMatcher StartsWithNo = nameableDeclaration(matchesName("::no")); + EXPECT_TRUE(matches("int no_foo;", StartsWithNo)); + EXPECT_TRUE(matches("class foo { virtual void nobody(); };", StartsWithNo)); + + DeclarationMatcher Abc = nameableDeclaration(matchesName("a.*b.*c")); + EXPECT_TRUE(matches("int abc;", Abc)); + EXPECT_TRUE(matches("int aFOObBARc;", Abc)); + EXPECT_TRUE(notMatches("int cab;", Abc)); + EXPECT_TRUE(matches("int cabc;", Abc)); +} + +TEST(DeclarationMatcher, MatchClass) { + DeclarationMatcher ClassMatcher(record()); +#if !defined(_MSC_VER) + EXPECT_FALSE(matches("", ClassMatcher)); +#else + // Matches class type_info. + EXPECT_TRUE(matches("", ClassMatcher)); +#endif + + DeclarationMatcher ClassX = record(record(hasName("X"))); + EXPECT_TRUE(matches("class X;", ClassX)); + EXPECT_TRUE(matches("class X {};", ClassX)); + EXPECT_TRUE(matches("template<class T> class X {};", ClassX)); + EXPECT_TRUE(notMatches("", ClassX)); +} + +TEST(DeclarationMatcher, ClassIsDerived) { + DeclarationMatcher IsDerivedFromX = record(isDerivedFrom("X")); + + EXPECT_TRUE(matches("class X {}; class Y : public X {};", IsDerivedFromX)); + EXPECT_TRUE(matches("class X {}; class Y : public X {};", IsDerivedFromX)); + EXPECT_TRUE(matches("class X {};", IsDerivedFromX)); + EXPECT_TRUE(matches("class X;", IsDerivedFromX)); + EXPECT_TRUE(notMatches("class Y;", IsDerivedFromX)); + EXPECT_TRUE(notMatches("", IsDerivedFromX)); + + DeclarationMatcher ZIsDerivedFromX = + record(hasName("Z"), isDerivedFrom("X")); + EXPECT_TRUE( + matches("class X {}; class Y : public X {}; class Z : public Y {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class X {};" + "template<class T> class Y : public X {};" + "class Z : public Y<int> {};", ZIsDerivedFromX)); + EXPECT_TRUE(matches("class X {}; template<class T> class Z : public X {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<class T> class X {}; " + "template<class T> class Z : public X<T> {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<class T, class U=T> class X {}; " + "template<class T> class Z : public X<T> {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("template<class X> class A { class Z : public X {}; };", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<class X> class A { public: class Z : public X {}; }; " + "class X{}; void y() { A<X>::Z z; }", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template <class T> class X {}; " + "template<class Y> class A { class Z : public X<Y> {}; };", + ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("template<template<class T> class X> class A { " + " class Z : public X<int> {}; };", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<template<class T> class X> class A { " + " public: class Z : public X<int> {}; }; " + "template<class T> class X {}; void y() { A<X>::Z z; }", + ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("template<class X> class A { class Z : public X::D {}; };", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<class X> class A { public: " + " class Z : public X::D {}; }; " + "class Y { public: class X {}; typedef X D; }; " + "void y() { A<Y>::Z z; }", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class X {}; typedef X Y; class Z : public Y {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<class T> class Y { typedef typename T::U X; " + " class Z : public X {}; };", ZIsDerivedFromX)); + EXPECT_TRUE(matches("class X {}; class Z : public ::X {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("template<class T> class X {}; " + "template<class T> class A { class Z : public X<T>::D {}; };", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<class T> class X { public: typedef X<T> D; }; " + "template<class T> class A { public: " + " class Z : public X<T>::D {}; }; void y() { A<int>::Z z; }", + ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("template<class X> class A { class Z : public X::D::E {}; };", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class X {}; typedef X V; typedef V W; class Z : public W {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class X {}; class Y : public X {}; " + "typedef Y V; typedef V W; class Z : public W {};", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("template<class T, class U> class X {}; " + "template<class T> class A { class Z : public X<T, int> {}; };", + ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("template<class X> class D { typedef X A; typedef A B; " + " typedef B C; class Z : public C {}; };", + ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class X {}; typedef X A; typedef A B; " + "class Z : public B {};", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class X {}; typedef X A; typedef A B; typedef B C; " + "class Z : public C {};", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class U {}; typedef U X; typedef X V; " + "class Z : public V {};", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class Base {}; typedef Base X; " + "class Z : public Base {};", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class Base {}; typedef Base Base2; typedef Base2 X; " + "class Z : public Base {};", ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("class Base {}; class Base2 {}; typedef Base2 X; " + "class Z : public Base {};", ZIsDerivedFromX)); + EXPECT_TRUE( + matches("class A {}; typedef A X; typedef A Y; " + "class Z : public Y {};", ZIsDerivedFromX)); + EXPECT_TRUE( + notMatches("template <typename T> class Z;" + "template <> class Z<void> {};" + "template <typename T> class Z : public Z<void> {};", + IsDerivedFromX)); + EXPECT_TRUE( + matches("template <typename T> class X;" + "template <> class X<void> {};" + "template <typename T> class X : public X<void> {};", + IsDerivedFromX)); + EXPECT_TRUE(matches( + "class X {};" + "template <typename T> class Z;" + "template <> class Z<void> {};" + "template <typename T> class Z : public Z<void>, public X {};", + ZIsDerivedFromX)); + + // FIXME: Once we have better matchers for template type matching, + // get rid of the Variable(...) matching and match the right template + // declarations directly. + const char *RecursiveTemplateOneParameter = + "class Base1 {}; class Base2 {};" + "template <typename T> class Z;" + "template <> class Z<void> : public Base1 {};" + "template <> class Z<int> : public Base2 {};" + "template <> class Z<float> : public Z<void> {};" + "template <> class Z<double> : public Z<int> {};" + "template <typename T> class Z : public Z<float>, public Z<double> {};" + "void f() { Z<float> z_float; Z<double> z_double; Z<char> z_char; }"; + EXPECT_TRUE(matches( + RecursiveTemplateOneParameter, + variable(hasName("z_float"), + hasInitializer(hasType(record(isDerivedFrom("Base1"))))))); + EXPECT_TRUE(notMatches( + RecursiveTemplateOneParameter, + variable( + hasName("z_float"), + hasInitializer(hasType(record(isDerivedFrom("Base2"))))))); + EXPECT_TRUE(matches( + RecursiveTemplateOneParameter, + variable( + hasName("z_char"), + hasInitializer(hasType(record(isDerivedFrom("Base1"), + isDerivedFrom("Base2"))))))); + + const char *RecursiveTemplateTwoParameters = + "class Base1 {}; class Base2 {};" + "template <typename T1, typename T2> class Z;" + "template <typename T> class Z<void, T> : public Base1 {};" + "template <typename T> class Z<int, T> : public Base2 {};" + "template <typename T> class Z<float, T> : public Z<void, T> {};" + "template <typename T> class Z<double, T> : public Z<int, T> {};" + "template <typename T1, typename T2> class Z : " + " public Z<float, T2>, public Z<double, T2> {};" + "void f() { Z<float, void> z_float; Z<double, void> z_double; " + " Z<char, void> z_char; }"; + EXPECT_TRUE(matches( + RecursiveTemplateTwoParameters, + variable( + hasName("z_float"), + hasInitializer(hasType(record(isDerivedFrom("Base1"))))))); + EXPECT_TRUE(notMatches( + RecursiveTemplateTwoParameters, + variable( + hasName("z_float"), + hasInitializer(hasType(record(isDerivedFrom("Base2"))))))); + EXPECT_TRUE(matches( + RecursiveTemplateTwoParameters, + variable( + hasName("z_char"), + hasInitializer(hasType(record(isDerivedFrom("Base1"), + isDerivedFrom("Base2"))))))); + EXPECT_TRUE(matches( + "namespace ns { class X {}; class Y : public X {}; }", + record(isDerivedFrom("::ns::X")))); + EXPECT_TRUE(notMatches( + "class X {}; class Y : public X {};", + record(isDerivedFrom("::ns::X")))); + + EXPECT_TRUE(matches( + "class X {}; class Y : public X {};", + record(isDerivedFrom(record(hasName("X")).bind("test"))))); +} + +TEST(AllOf, AllOverloadsWork) { + const char Program[] = + "struct T { }; int f(int, T*); void g(int x) { T t; f(x, &t); }"; + EXPECT_TRUE(matches(Program, + call(allOf(callee(function(hasName("f"))), + hasArgument(0, declarationReference(to(variable()))))))); + EXPECT_TRUE(matches(Program, + call(allOf(callee(function(hasName("f"))), + hasArgument(0, declarationReference(to(variable()))), + hasArgument(1, hasType(pointsTo(record(hasName("T"))))))))); +} + +TEST(DeclarationMatcher, MatchAnyOf) { + DeclarationMatcher YOrZDerivedFromX = + record(anyOf(hasName("Y"), allOf(isDerivedFrom("X"), hasName("Z")))); + EXPECT_TRUE( + matches("class X {}; class Z : public X {};", YOrZDerivedFromX)); + EXPECT_TRUE(matches("class Y {};", YOrZDerivedFromX)); + EXPECT_TRUE( + notMatches("class X {}; class W : public X {};", YOrZDerivedFromX)); + EXPECT_TRUE(notMatches("class Z {};", YOrZDerivedFromX)); + + DeclarationMatcher XOrYOrZOrU = + record(anyOf(hasName("X"), hasName("Y"), hasName("Z"), hasName("U"))); + EXPECT_TRUE(matches("class X {};", XOrYOrZOrU)); + EXPECT_TRUE(notMatches("class V {};", XOrYOrZOrU)); + + DeclarationMatcher XOrYOrZOrUOrV = + record(anyOf(hasName("X"), hasName("Y"), hasName("Z"), hasName("U"), + hasName("V"))); + EXPECT_TRUE(matches("class X {};", XOrYOrZOrUOrV)); + EXPECT_TRUE(matches("class Y {};", XOrYOrZOrUOrV)); + EXPECT_TRUE(matches("class Z {};", XOrYOrZOrUOrV)); + EXPECT_TRUE(matches("class U {};", XOrYOrZOrUOrV)); + EXPECT_TRUE(matches("class V {};", XOrYOrZOrUOrV)); + EXPECT_TRUE(notMatches("class A {};", XOrYOrZOrUOrV)); +} + +TEST(DeclarationMatcher, MatchHas) { + DeclarationMatcher HasClassX = record(has(record(hasName("X")))); + + EXPECT_TRUE(matches("class Y { class X {}; };", HasClassX)); + EXPECT_TRUE(matches("class X {};", HasClassX)); + + DeclarationMatcher YHasClassX = + record(hasName("Y"), has(record(hasName("X")))); + EXPECT_TRUE(matches("class Y { class X {}; };", YHasClassX)); + EXPECT_TRUE(notMatches("class X {};", YHasClassX)); + EXPECT_TRUE( + notMatches("class Y { class Z { class X {}; }; };", YHasClassX)); +} + +TEST(DeclarationMatcher, MatchHasRecursiveAllOf) { + DeclarationMatcher Recursive = + record( + has(record( + has(record(hasName("X"))), + has(record(hasName("Y"))), + hasName("Z"))), + has(record( + has(record(hasName("A"))), + has(record(hasName("B"))), + hasName("C"))), + hasName("F")); + + EXPECT_TRUE(matches( + "class F {" + " class Z {" + " class X {};" + " class Y {};" + " };" + " class C {" + " class A {};" + " class B {};" + " };" + "};", Recursive)); + + EXPECT_TRUE(matches( + "class F {" + " class Z {" + " class A {};" + " class X {};" + " class Y {};" + " };" + " class C {" + " class X {};" + " class A {};" + " class B {};" + " };" + "};", Recursive)); + + EXPECT_TRUE(matches( + "class O1 {" + " class O2 {" + " class F {" + " class Z {" + " class A {};" + " class X {};" + " class Y {};" + " };" + " class C {" + " class X {};" + " class A {};" + " class B {};" + " };" + " };" + " };" + "};", Recursive)); +} + +TEST(DeclarationMatcher, MatchHasRecursiveAnyOf) { + DeclarationMatcher Recursive = + record( + anyOf( + has(record( + anyOf( + has(record( + hasName("X"))), + has(record( + hasName("Y"))), + hasName("Z")))), + has(record( + anyOf( + hasName("C"), + has(record( + hasName("A"))), + has(record( + hasName("B")))))), + hasName("F"))); + + EXPECT_TRUE(matches("class F {};", Recursive)); + EXPECT_TRUE(matches("class Z {};", Recursive)); + EXPECT_TRUE(matches("class C {};", Recursive)); + EXPECT_TRUE(matches("class M { class N { class X {}; }; };", Recursive)); + EXPECT_TRUE(matches("class M { class N { class B {}; }; };", Recursive)); + EXPECT_TRUE( + matches("class O1 { class O2 {" + " class M { class N { class B {}; }; }; " + "}; };", Recursive)); +} + +TEST(DeclarationMatcher, MatchNot) { + DeclarationMatcher NotClassX = + record( + isDerivedFrom("Y"), + unless(hasName("Y")), + unless(hasName("X"))); + EXPECT_TRUE(notMatches("", NotClassX)); + EXPECT_TRUE(notMatches("class Y {};", NotClassX)); + EXPECT_TRUE(matches("class Y {}; class Z : public Y {};", NotClassX)); + EXPECT_TRUE(notMatches("class Y {}; class X : public Y {};", NotClassX)); + EXPECT_TRUE( + notMatches("class Y {}; class Z {}; class X : public Y {};", + NotClassX)); + + DeclarationMatcher ClassXHasNotClassY = + record( + hasName("X"), + has(record(hasName("Z"))), + unless( + has(record(hasName("Y"))))); + EXPECT_TRUE(matches("class X { class Z {}; };", ClassXHasNotClassY)); + EXPECT_TRUE(notMatches("class X { class Y {}; class Z {}; };", + ClassXHasNotClassY)); +} + +TEST(DeclarationMatcher, HasDescendant) { + DeclarationMatcher ZDescendantClassX = + record( + hasDescendant(record(hasName("X"))), + hasName("Z")); + EXPECT_TRUE(matches("class Z { class X {}; };", ZDescendantClassX)); + EXPECT_TRUE( + matches("class Z { class Y { class X {}; }; };", ZDescendantClassX)); + EXPECT_TRUE( + matches("class Z { class A { class Y { class X {}; }; }; };", + ZDescendantClassX)); + EXPECT_TRUE( + matches("class Z { class A { class B { class Y { class X {}; }; }; }; };", + ZDescendantClassX)); + EXPECT_TRUE(notMatches("class Z {};", ZDescendantClassX)); + + DeclarationMatcher ZDescendantClassXHasClassY = + record( + hasDescendant(record(has(record(hasName("Y"))), + hasName("X"))), + hasName("Z")); + EXPECT_TRUE(matches("class Z { class X { class Y {}; }; };", + ZDescendantClassXHasClassY)); + EXPECT_TRUE( + matches("class Z { class A { class B { class X { class Y {}; }; }; }; };", + ZDescendantClassXHasClassY)); + EXPECT_TRUE(notMatches( + "class Z {" + " class A {" + " class B {" + " class X {" + " class C {" + " class Y {};" + " };" + " };" + " }; " + " };" + "};", ZDescendantClassXHasClassY)); + + DeclarationMatcher ZDescendantClassXDescendantClassY = + record( + hasDescendant(record(hasDescendant(record(hasName("Y"))), + hasName("X"))), + hasName("Z")); + EXPECT_TRUE( + matches("class Z { class A { class X { class B { class Y {}; }; }; }; };", + ZDescendantClassXDescendantClassY)); + EXPECT_TRUE(matches( + "class Z {" + " class A {" + " class X {" + " class B {" + " class Y {};" + " };" + " class Y {};" + " };" + " };" + "};", ZDescendantClassXDescendantClassY)); +} + +TEST(Enum, DoesNotMatchClasses) { + EXPECT_TRUE(notMatches("class X {};", enumDecl(hasName("X")))); +} + +TEST(Enum, MatchesEnums) { + EXPECT_TRUE(matches("enum X {};", enumDecl(hasName("X")))); +} + +TEST(EnumConstant, Matches) { + DeclarationMatcher Matcher = enumConstant(hasName("A")); + EXPECT_TRUE(matches("enum X{ A };", Matcher)); + EXPECT_TRUE(notMatches("enum X{ B };", Matcher)); + EXPECT_TRUE(notMatches("enum X {};", Matcher)); +} + +TEST(StatementMatcher, Has) { + StatementMatcher HasVariableI = + expression( + hasType(pointsTo(record(hasName("X")))), + has(declarationReference(to(variable(hasName("i")))))); + + EXPECT_TRUE(matches( + "class X; X *x(int); void c() { int i; x(i); }", HasVariableI)); + EXPECT_TRUE(notMatches( + "class X; X *x(int); void c() { int i; x(42); }", HasVariableI)); +} + +TEST(StatementMatcher, HasDescendant) { + StatementMatcher HasDescendantVariableI = + expression( + hasType(pointsTo(record(hasName("X")))), + hasDescendant(declarationReference(to(variable(hasName("i")))))); + + EXPECT_TRUE(matches( + "class X; X *x(bool); bool b(int); void c() { int i; x(b(i)); }", + HasDescendantVariableI)); + EXPECT_TRUE(notMatches( + "class X; X *x(bool); bool b(int); void c() { int i; x(b(42)); }", + HasDescendantVariableI)); +} + +TEST(TypeMatcher, MatchesClassType) { + TypeMatcher TypeA = hasDeclaration(record(hasName("A"))); + + EXPECT_TRUE(matches("class A { public: A *a; };", TypeA)); + EXPECT_TRUE(notMatches("class A {};", TypeA)); + + TypeMatcher TypeDerivedFromA = hasDeclaration(record(isDerivedFrom("A"))); + + EXPECT_TRUE(matches("class A {}; class B : public A { public: B *b; };", + TypeDerivedFromA)); + EXPECT_TRUE(notMatches("class A {};", TypeA)); + + TypeMatcher TypeAHasClassB = hasDeclaration( + record(hasName("A"), has(record(hasName("B"))))); + + EXPECT_TRUE( + matches("class A { public: A *a; class B {}; };", TypeAHasClassB)); +} + +// Returns from Run whether 'bound_nodes' contain a Decl bound to 'Id', which +// can be dynamically casted to T. +// Optionally checks that the check succeeded a specific number of times. +template <typename T> +class VerifyIdIsBoundToDecl : public BoundNodesCallback { +public: + // Create an object that checks that a node of type 'T' was bound to 'Id'. + // Does not check for a certain number of matches. + explicit VerifyIdIsBoundToDecl(const std::string& Id) + : Id(Id), ExpectedCount(-1), Count(0) {} + + // Create an object that checks that a node of type 'T' was bound to 'Id'. + // Checks that there were exactly 'ExpectedCount' matches. + explicit VerifyIdIsBoundToDecl(const std::string& Id, int ExpectedCount) + : Id(Id), ExpectedCount(ExpectedCount), Count(0) {} + + ~VerifyIdIsBoundToDecl() { + if (ExpectedCount != -1) { + EXPECT_EQ(ExpectedCount, Count); + } + } + + virtual bool run(const BoundNodes *Nodes) { + if (Nodes->getDeclAs<T>(Id) != NULL) { + ++Count; + return true; + } + return false; + } + +private: + const std::string Id; + const int ExpectedCount; + int Count; +}; +template <typename T> +class VerifyIdIsBoundToStmt : public BoundNodesCallback { +public: + explicit VerifyIdIsBoundToStmt(const std::string &Id) : Id(Id) {} + virtual bool run(const BoundNodes *Nodes) { + const T *Node = Nodes->getStmtAs<T>(Id); + return Node != NULL; + } +private: + const std::string Id; +}; + +TEST(Matcher, BindMatchedNodes) { + DeclarationMatcher ClassX = has(record(hasName("::X")).bind("x")); + + EXPECT_TRUE(matchAndVerifyResultTrue("class X {};", + ClassX, new VerifyIdIsBoundToDecl<CXXRecordDecl>("x"))); + + EXPECT_TRUE(matchAndVerifyResultFalse("class X {};", + ClassX, new VerifyIdIsBoundToDecl<CXXRecordDecl>("other-id"))); + + TypeMatcher TypeAHasClassB = hasDeclaration( + record(hasName("A"), has(record(hasName("B")).bind("b")))); + + EXPECT_TRUE(matchAndVerifyResultTrue("class A { public: A *a; class B {}; };", + TypeAHasClassB, + new VerifyIdIsBoundToDecl<Decl>("b"))); + + StatementMatcher MethodX = call(callee(method(hasName("x")))).bind("x"); + + EXPECT_TRUE(matchAndVerifyResultTrue("class A { void x() { x(); } };", + MethodX, + new VerifyIdIsBoundToStmt<CXXMemberCallExpr>("x"))); +} + +TEST(Matcher, BindTheSameNameInAlternatives) { + StatementMatcher matcher = anyOf( + binaryOperator(hasOperatorName("+"), + hasLHS(expression().bind("x")), + hasRHS(integerLiteral(equals(0)))), + binaryOperator(hasOperatorName("+"), + hasLHS(integerLiteral(equals(0))), + hasRHS(expression().bind("x")))); + + EXPECT_TRUE(matchAndVerifyResultTrue( + // The first branch of the matcher binds x to 0 but then fails. + // The second branch binds x to f() and succeeds. + "int f() { return 0 + f(); }", + matcher, + new VerifyIdIsBoundToStmt<CallExpr>("x"))); +} + +TEST(HasType, TakesQualTypeMatcherAndMatchesExpr) { + TypeMatcher ClassX = hasDeclaration(record(hasName("X"))); + EXPECT_TRUE( + matches("class X {}; void y(X &x) { x; }", expression(hasType(ClassX)))); + EXPECT_TRUE( + notMatches("class X {}; void y(X *x) { x; }", + expression(hasType(ClassX)))); + EXPECT_TRUE( + matches("class X {}; void y(X *x) { x; }", + expression(hasType(pointsTo(ClassX))))); +} + +TEST(HasType, TakesQualTypeMatcherAndMatchesValueDecl) { + TypeMatcher ClassX = hasDeclaration(record(hasName("X"))); + EXPECT_TRUE( + matches("class X {}; void y() { X x; }", variable(hasType(ClassX)))); + EXPECT_TRUE( + notMatches("class X {}; void y() { X *x; }", variable(hasType(ClassX)))); + EXPECT_TRUE( + matches("class X {}; void y() { X *x; }", + variable(hasType(pointsTo(ClassX))))); +} + +TEST(HasType, TakesDeclMatcherAndMatchesExpr) { + DeclarationMatcher ClassX = record(hasName("X")); + EXPECT_TRUE( + matches("class X {}; void y(X &x) { x; }", expression(hasType(ClassX)))); + EXPECT_TRUE( + notMatches("class X {}; void y(X *x) { x; }", + expression(hasType(ClassX)))); +} + +TEST(HasType, TakesDeclMatcherAndMatchesValueDecl) { + DeclarationMatcher ClassX = record(hasName("X")); + EXPECT_TRUE( + matches("class X {}; void y() { X x; }", variable(hasType(ClassX)))); + EXPECT_TRUE( + notMatches("class X {}; void y() { X *x; }", variable(hasType(ClassX)))); +} + +TEST(Matcher, Call) { + // FIXME: Do we want to overload Call() to directly take + // Matcher<Decl>, too? + StatementMatcher MethodX = call(hasDeclaration(method(hasName("x")))); + + EXPECT_TRUE(matches("class Y { void x() { x(); } };", MethodX)); + EXPECT_TRUE(notMatches("class Y { void x() {} };", MethodX)); + + StatementMatcher MethodOnY = memberCall(on(hasType(record(hasName("Y"))))); + + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z() { Y y; y.x(); }", + MethodOnY)); + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z(Y &y) { y.x(); }", + MethodOnY)); + EXPECT_TRUE( + notMatches("class Y { public: void x(); }; void z(Y *&y) { y->x(); }", + MethodOnY)); + EXPECT_TRUE( + notMatches("class Y { public: void x(); }; void z(Y y[]) { y->x(); }", + MethodOnY)); + EXPECT_TRUE( + notMatches("class Y { public: void x(); }; void z() { Y *y; y->x(); }", + MethodOnY)); + + StatementMatcher MethodOnYPointer = + memberCall(on(hasType(pointsTo(record(hasName("Y")))))); + + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z() { Y *y; y->x(); }", + MethodOnYPointer)); + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z(Y *&y) { y->x(); }", + MethodOnYPointer)); + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z(Y y[]) { y->x(); }", + MethodOnYPointer)); + EXPECT_TRUE( + notMatches("class Y { public: void x(); }; void z() { Y y; y.x(); }", + MethodOnYPointer)); + EXPECT_TRUE( + notMatches("class Y { public: void x(); }; void z(Y &y) { y.x(); }", + MethodOnYPointer)); +} + +TEST(HasType, MatchesAsString) { + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z() {Y* y; y->x(); }", + memberCall(on(hasType(asString("class Y *")))))); + EXPECT_TRUE(matches("class X { void x(int x) {} };", + method(hasParameter(0, hasType(asString("int")))))); + EXPECT_TRUE(matches("namespace ns { struct A {}; } struct B { ns::A a; };", + field(hasType(asString("ns::A"))))); + EXPECT_TRUE(matches("namespace { struct A {}; } struct B { A a; };", + field(hasType(asString("struct <anonymous>::A"))))); +} + +TEST(Matcher, OverloadedOperatorCall) { + StatementMatcher OpCall = overloadedOperatorCall(); + // Unary operator + EXPECT_TRUE(matches("class Y { }; " + "bool operator!(Y x) { return false; }; " + "Y y; bool c = !y;", OpCall)); + // No match -- special operators like "new", "delete" + // FIXME: operator new takes size_t, for which we need stddef.h, for which + // we need to figure out include paths in the test. + // EXPECT_TRUE(NotMatches("#include <stddef.h>\n" + // "class Y { }; " + // "void *operator new(size_t size) { return 0; } " + // "Y *y = new Y;", OpCall)); + EXPECT_TRUE(notMatches("class Y { }; " + "void operator delete(void *p) { } " + "void a() {Y *y = new Y; delete y;}", OpCall)); + // Binary operator + EXPECT_TRUE(matches("class Y { }; " + "bool operator&&(Y x, Y y) { return true; }; " + "Y a; Y b; bool c = a && b;", + OpCall)); + // No match -- normal operator, not an overloaded one. + EXPECT_TRUE(notMatches("bool x = true, y = true; bool t = x && y;", OpCall)); + EXPECT_TRUE(notMatches("int t = 5 << 2;", OpCall)); +} + +TEST(Matcher, HasOperatorNameForOverloadedOperatorCall) { + StatementMatcher OpCallAndAnd = + overloadedOperatorCall(hasOverloadedOperatorName("&&")); + EXPECT_TRUE(matches("class Y { }; " + "bool operator&&(Y x, Y y) { return true; }; " + "Y a; Y b; bool c = a && b;", OpCallAndAnd)); + StatementMatcher OpCallLessLess = + overloadedOperatorCall(hasOverloadedOperatorName("<<")); + EXPECT_TRUE(notMatches("class Y { }; " + "bool operator&&(Y x, Y y) { return true; }; " + "Y a; Y b; bool c = a && b;", + OpCallLessLess)); +} + +TEST(Matcher, ThisPointerType) { + StatementMatcher MethodOnY = + memberCall(thisPointerType(record(hasName("Y")))); + + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z() { Y y; y.x(); }", + MethodOnY)); + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z(Y &y) { y.x(); }", + MethodOnY)); + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z(Y *&y) { y->x(); }", + MethodOnY)); + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z(Y y[]) { y->x(); }", + MethodOnY)); + EXPECT_TRUE( + matches("class Y { public: void x(); }; void z() { Y *y; y->x(); }", + MethodOnY)); + + EXPECT_TRUE(matches( + "class Y {" + " public: virtual void x();" + "};" + "class X : public Y {" + " public: virtual void x();" + "};" + "void z() { X *x; x->Y::x(); }", MethodOnY)); +} + +TEST(Matcher, VariableUsage) { + StatementMatcher Reference = + declarationReference(to( + variable(hasInitializer( + memberCall(thisPointerType(record(hasName("Y")))))))); + + EXPECT_TRUE(matches( + "class Y {" + " public:" + " bool x() const;" + "};" + "void z(const Y &y) {" + " bool b = y.x();" + " if (b) {}" + "}", Reference)); + + EXPECT_TRUE(notMatches( + "class Y {" + " public:" + " bool x() const;" + "};" + "void z(const Y &y) {" + " bool b = y.x();" + "}", Reference)); +} + +TEST(Matcher, FindsVarDeclInFuncitonParameter) { + EXPECT_TRUE(matches( + "void f(int i) {}", + variable(hasName("i")))); +} + +TEST(Matcher, CalledVariable) { + StatementMatcher CallOnVariableY = expression( + memberCall(on(declarationReference(to(variable(hasName("y"))))))); + + EXPECT_TRUE(matches( + "class Y { public: void x() { Y y; y.x(); } };", CallOnVariableY)); + EXPECT_TRUE(matches( + "class Y { public: void x() const { Y y; y.x(); } };", CallOnVariableY)); + EXPECT_TRUE(matches( + "class Y { public: void x(); };" + "class X : public Y { void z() { X y; y.x(); } };", CallOnVariableY)); + EXPECT_TRUE(matches( + "class Y { public: void x(); };" + "class X : public Y { void z() { X *y; y->x(); } };", CallOnVariableY)); + EXPECT_TRUE(notMatches( + "class Y { public: void x(); };" + "class X : public Y { void z() { unsigned long y; ((X*)y)->x(); } };", + CallOnVariableY)); +} + +TEST(UnaryExprOrTypeTraitExpr, MatchesSizeOfAndAlignOf) { + EXPECT_TRUE(matches("void x() { int a = sizeof(a); }", + unaryExprOrTypeTraitExpr())); + EXPECT_TRUE(notMatches("void x() { int a = sizeof(a); }", + alignOfExpr(anything()))); + // FIXME: Uncomment once alignof is enabled. + // EXPECT_TRUE(matches("void x() { int a = alignof(a); }", + // unaryExprOrTypeTraitExpr())); + // EXPECT_TRUE(notMatches("void x() { int a = alignof(a); }", + // sizeOfExpr())); +} + +TEST(UnaryExpressionOrTypeTraitExpression, MatchesCorrectType) { + EXPECT_TRUE(matches("void x() { int a = sizeof(a); }", sizeOfExpr( + hasArgumentOfType(asString("int"))))); + EXPECT_TRUE(notMatches("void x() { int a = sizeof(a); }", sizeOfExpr( + hasArgumentOfType(asString("float"))))); + EXPECT_TRUE(matches( + "struct A {}; void x() { A a; int b = sizeof(a); }", + sizeOfExpr(hasArgumentOfType(hasDeclaration(record(hasName("A"))))))); + EXPECT_TRUE(notMatches("void x() { int a = sizeof(a); }", sizeOfExpr( + hasArgumentOfType(hasDeclaration(record(hasName("string"))))))); +} + +TEST(MemberExpression, DoesNotMatchClasses) { + EXPECT_TRUE(notMatches("class Y { void x() {} };", memberExpression())); +} + +TEST(MemberExpression, MatchesMemberFunctionCall) { + EXPECT_TRUE(matches("class Y { void x() { x(); } };", memberExpression())); +} + +TEST(MemberExpression, MatchesVariable) { + EXPECT_TRUE( + matches("class Y { void x() { this->y; } int y; };", memberExpression())); + EXPECT_TRUE( + matches("class Y { void x() { y; } int y; };", memberExpression())); + EXPECT_TRUE( + matches("class Y { void x() { Y y; y.y; } int y; };", + memberExpression())); +} + +TEST(MemberExpression, MatchesStaticVariable) { + EXPECT_TRUE(matches("class Y { void x() { this->y; } static int y; };", + memberExpression())); + EXPECT_TRUE(notMatches("class Y { void x() { y; } static int y; };", + memberExpression())); + EXPECT_TRUE(notMatches("class Y { void x() { Y::y; } static int y; };", + memberExpression())); +} + +TEST(IsInteger, MatchesIntegers) { + EXPECT_TRUE(matches("int i = 0;", variable(hasType(isInteger())))); + EXPECT_TRUE(matches("long long i = 0; void f(long long) { }; void g() {f(i);}", + call(hasArgument(0, declarationReference( + to(variable(hasType(isInteger())))))))); +} + +TEST(IsInteger, ReportsNoFalsePositives) { + EXPECT_TRUE(notMatches("int *i;", variable(hasType(isInteger())))); + EXPECT_TRUE(notMatches("struct T {}; T t; void f(T *) { }; void g() {f(&t);}", + call(hasArgument(0, declarationReference( + to(variable(hasType(isInteger())))))))); +} + +TEST(IsArrow, MatchesMemberVariablesViaArrow) { + EXPECT_TRUE(matches("class Y { void x() { this->y; } int y; };", + memberExpression(isArrow()))); + EXPECT_TRUE(matches("class Y { void x() { y; } int y; };", + memberExpression(isArrow()))); + EXPECT_TRUE(notMatches("class Y { void x() { (*this).y; } int y; };", + memberExpression(isArrow()))); +} + +TEST(IsArrow, MatchesStaticMemberVariablesViaArrow) { + EXPECT_TRUE(matches("class Y { void x() { this->y; } static int y; };", + memberExpression(isArrow()))); + EXPECT_TRUE(notMatches("class Y { void x() { y; } static int y; };", + memberExpression(isArrow()))); + EXPECT_TRUE(notMatches("class Y { void x() { (*this).y; } static int y; };", + memberExpression(isArrow()))); +} + +TEST(IsArrow, MatchesMemberCallsViaArrow) { + EXPECT_TRUE(matches("class Y { void x() { this->x(); } };", + memberExpression(isArrow()))); + EXPECT_TRUE(matches("class Y { void x() { x(); } };", + memberExpression(isArrow()))); + EXPECT_TRUE(notMatches("class Y { void x() { Y y; y.x(); } };", + memberExpression(isArrow()))); +} + +TEST(Callee, MatchesDeclarations) { + StatementMatcher CallMethodX = call(callee(method(hasName("x")))); + + EXPECT_TRUE(matches("class Y { void x() { x(); } };", CallMethodX)); + EXPECT_TRUE(notMatches("class Y { void x() {} };", CallMethodX)); +} + +TEST(Callee, MatchesMemberExpressions) { + EXPECT_TRUE(matches("class Y { void x() { this->x(); } };", + call(callee(memberExpression())))); + EXPECT_TRUE( + notMatches("class Y { void x() { this->x(); } };", call(callee(call())))); +} + +TEST(Function, MatchesFunctionDeclarations) { + StatementMatcher CallFunctionF = call(callee(function(hasName("f")))); + + EXPECT_TRUE(matches("void f() { f(); }", CallFunctionF)); + EXPECT_TRUE(notMatches("void f() { }", CallFunctionF)); + +#if !defined(_MSC_VER) + // FIXME: Make this work for MSVC. + // Dependent contexts, but a non-dependent call. + EXPECT_TRUE(matches("void f(); template <int N> void g() { f(); }", + CallFunctionF)); + EXPECT_TRUE( + matches("void f(); template <int N> struct S { void g() { f(); } };", + CallFunctionF)); +#endif + + // Depedent calls don't match. + EXPECT_TRUE( + notMatches("void f(int); template <typename T> void g(T t) { f(t); }", + CallFunctionF)); + EXPECT_TRUE( + notMatches("void f(int);" + "template <typename T> struct S { void g(T t) { f(t); } };", + CallFunctionF)); +} + +TEST(Matcher, Argument) { + StatementMatcher CallArgumentY = expression(call( + hasArgument(0, declarationReference(to(variable(hasName("y"))))))); + + EXPECT_TRUE(matches("void x(int) { int y; x(y); }", CallArgumentY)); + EXPECT_TRUE( + matches("class X { void x(int) { int y; x(y); } };", CallArgumentY)); + EXPECT_TRUE(notMatches("void x(int) { int z; x(z); }", CallArgumentY)); + + StatementMatcher WrongIndex = expression(call( + hasArgument(42, declarationReference(to(variable(hasName("y"))))))); + EXPECT_TRUE(notMatches("void x(int) { int y; x(y); }", WrongIndex)); +} + +TEST(Matcher, AnyArgument) { + StatementMatcher CallArgumentY = expression(call( + hasAnyArgument(declarationReference(to(variable(hasName("y"))))))); + EXPECT_TRUE(matches("void x(int, int) { int y; x(1, y); }", CallArgumentY)); + EXPECT_TRUE(matches("void x(int, int) { int y; x(y, 42); }", CallArgumentY)); + EXPECT_TRUE(notMatches("void x(int, int) { x(1, 2); }", CallArgumentY)); +} + +TEST(Matcher, ArgumentCount) { + StatementMatcher Call1Arg = expression(call(argumentCountIs(1))); + + EXPECT_TRUE(matches("void x(int) { x(0); }", Call1Arg)); + EXPECT_TRUE(matches("class X { void x(int) { x(0); } };", Call1Arg)); + EXPECT_TRUE(notMatches("void x(int, int) { x(0, 0); }", Call1Arg)); +} + +TEST(Matcher, References) { + DeclarationMatcher ReferenceClassX = variable( + hasType(references(record(hasName("X"))))); + EXPECT_TRUE(matches("class X {}; void y(X y) { X &x = y; }", + ReferenceClassX)); + EXPECT_TRUE( + matches("class X {}; void y(X y) { const X &x = y; }", ReferenceClassX)); + EXPECT_TRUE( + notMatches("class X {}; void y(X y) { X x = y; }", ReferenceClassX)); + EXPECT_TRUE( + notMatches("class X {}; void y(X *y) { X *&x = y; }", ReferenceClassX)); +} + +TEST(HasParameter, CallsInnerMatcher) { + EXPECT_TRUE(matches("class X { void x(int) {} };", + method(hasParameter(0, variable())))); + EXPECT_TRUE(notMatches("class X { void x(int) {} };", + method(hasParameter(0, hasName("x"))))); +} + +TEST(HasParameter, DoesNotMatchIfIndexOutOfBounds) { + EXPECT_TRUE(notMatches("class X { void x(int) {} };", + method(hasParameter(42, variable())))); +} + +TEST(HasType, MatchesParameterVariableTypesStrictly) { + EXPECT_TRUE(matches("class X { void x(X x) {} };", + method(hasParameter(0, hasType(record(hasName("X"))))))); + EXPECT_TRUE(notMatches("class X { void x(const X &x) {} };", + method(hasParameter(0, hasType(record(hasName("X"))))))); + EXPECT_TRUE(matches("class X { void x(const X *x) {} };", + method(hasParameter(0, hasType(pointsTo(record(hasName("X")))))))); + EXPECT_TRUE(matches("class X { void x(const X &x) {} };", + method(hasParameter(0, hasType(references(record(hasName("X")))))))); +} + +TEST(HasAnyParameter, MatchesIndependentlyOfPosition) { + EXPECT_TRUE(matches("class Y {}; class X { void x(X x, Y y) {} };", + method(hasAnyParameter(hasType(record(hasName("X"))))))); + EXPECT_TRUE(matches("class Y {}; class X { void x(Y y, X x) {} };", + method(hasAnyParameter(hasType(record(hasName("X"))))))); +} + +TEST(Returns, MatchesReturnTypes) { + EXPECT_TRUE(matches("class Y { int f() { return 1; } };", + function(returns(asString("int"))))); + EXPECT_TRUE(notMatches("class Y { int f() { return 1; } };", + function(returns(asString("float"))))); + EXPECT_TRUE(matches("class Y { Y getMe() { return *this; } };", + function(returns(hasDeclaration(record(hasName("Y"))))))); +} + +TEST(HasAnyParameter, DoesntMatchIfInnerMatcherDoesntMatch) { + EXPECT_TRUE(notMatches("class Y {}; class X { void x(int) {} };", + method(hasAnyParameter(hasType(record(hasName("X"))))))); +} + +TEST(HasAnyParameter, DoesNotMatchThisPointer) { + EXPECT_TRUE(notMatches("class Y {}; class X { void x() {} };", + method(hasAnyParameter(hasType(pointsTo(record(hasName("X")))))))); +} + +TEST(HasName, MatchesParameterVariableDeclartions) { + EXPECT_TRUE(matches("class Y {}; class X { void x(int x) {} };", + method(hasAnyParameter(hasName("x"))))); + EXPECT_TRUE(notMatches("class Y {}; class X { void x(int) {} };", + method(hasAnyParameter(hasName("x"))))); +} + +TEST(Matcher, MatchesClassTemplateSpecialization) { + EXPECT_TRUE(matches("template<typename T> struct A {};" + "template<> struct A<int> {};", + classTemplateSpecialization())); + EXPECT_TRUE(matches("template<typename T> struct A {}; A<int> a;", + classTemplateSpecialization())); + EXPECT_TRUE(notMatches("template<typename T> struct A {};", + classTemplateSpecialization())); +} + +TEST(Matcher, MatchesTypeTemplateArgument) { + EXPECT_TRUE(matches( + "template<typename T> struct B {};" + "B<int> b;", + classTemplateSpecialization(hasAnyTemplateArgument(refersToType( + asString("int")))))); +} + +TEST(Matcher, MatchesDeclarationReferenceTemplateArgument) { + EXPECT_TRUE(matches( + "struct B { int next; };" + "template<int(B::*next_ptr)> struct A {};" + "A<&B::next> a;", + classTemplateSpecialization(hasAnyTemplateArgument( + refersToDeclaration(field(hasName("next"))))))); +} + +TEST(Matcher, MatchesSpecificArgument) { + EXPECT_TRUE(matches( + "template<typename T, typename U> class A {};" + "A<bool, int> a;", + classTemplateSpecialization(hasTemplateArgument( + 1, refersToType(asString("int")))))); + EXPECT_TRUE(notMatches( + "template<typename T, typename U> class A {};" + "A<int, bool> a;", + classTemplateSpecialization(hasTemplateArgument( + 1, refersToType(asString("int")))))); +} + +TEST(Matcher, ConstructorCall) { + StatementMatcher Constructor = expression(constructorCall()); + + EXPECT_TRUE( + matches("class X { public: X(); }; void x() { X x; }", Constructor)); + EXPECT_TRUE( + matches("class X { public: X(); }; void x() { X x = X(); }", + Constructor)); + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { X x = 0; }", + Constructor)); + EXPECT_TRUE(matches("class X {}; void x(int) { X x; }", Constructor)); +} + +TEST(Matcher, ConstructorArgument) { + StatementMatcher Constructor = expression(constructorCall( + hasArgument(0, declarationReference(to(variable(hasName("y"))))))); + + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { int y; X x(y); }", + Constructor)); + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { int y; X x = X(y); }", + Constructor)); + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { int y; X x = y; }", + Constructor)); + EXPECT_TRUE( + notMatches("class X { public: X(int); }; void x() { int z; X x(z); }", + Constructor)); + + StatementMatcher WrongIndex = expression(constructorCall( + hasArgument(42, declarationReference(to(variable(hasName("y"))))))); + EXPECT_TRUE( + notMatches("class X { public: X(int); }; void x() { int y; X x(y); }", + WrongIndex)); +} + +TEST(Matcher, ConstructorArgumentCount) { + StatementMatcher Constructor1Arg = + expression(constructorCall(argumentCountIs(1))); + + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { X x(0); }", + Constructor1Arg)); + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { X x = X(0); }", + Constructor1Arg)); + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { X x = 0; }", + Constructor1Arg)); + EXPECT_TRUE( + notMatches("class X { public: X(int, int); }; void x() { X x(0, 0); }", + Constructor1Arg)); +} + +TEST(Matcher, BindTemporaryExpression) { + StatementMatcher TempExpression = expression(bindTemporaryExpression()); + + std::string ClassString = "class string { public: string(); ~string(); }; "; + + EXPECT_TRUE( + matches(ClassString + + "string GetStringByValue();" + "void FunctionTakesString(string s);" + "void run() { FunctionTakesString(GetStringByValue()); }", + TempExpression)); + + EXPECT_TRUE( + notMatches(ClassString + + "string* GetStringPointer(); " + "void FunctionTakesStringPtr(string* s);" + "void run() {" + " string* s = GetStringPointer();" + " FunctionTakesStringPtr(GetStringPointer());" + " FunctionTakesStringPtr(s);" + "}", + TempExpression)); + + EXPECT_TRUE( + notMatches("class no_dtor {};" + "no_dtor GetObjByValue();" + "void ConsumeObj(no_dtor param);" + "void run() { ConsumeObj(GetObjByValue()); }", + TempExpression)); +} + +TEST(ConstructorDeclaration, SimpleCase) { + EXPECT_TRUE(matches("class Foo { Foo(int i); };", + constructor(ofClass(hasName("Foo"))))); + EXPECT_TRUE(notMatches("class Foo { Foo(int i); };", + constructor(ofClass(hasName("Bar"))))); +} + +TEST(ConstructorDeclaration, IsImplicit) { + // This one doesn't match because the constructor is not added by the + // compiler (it is not needed). + EXPECT_TRUE(notMatches("class Foo { };", + constructor(isImplicit()))); + // The compiler added the implicit default constructor. + EXPECT_TRUE(matches("class Foo { }; Foo* f = new Foo();", + constructor(isImplicit()))); + EXPECT_TRUE(matches("class Foo { Foo(){} };", + constructor(unless(isImplicit())))); +} + +TEST(DestructorDeclaration, MatchesVirtualDestructor) { + EXPECT_TRUE(matches("class Foo { virtual ~Foo(); };", + destructor(ofClass(hasName("Foo"))))); +} + +TEST(DestructorDeclaration, DoesNotMatchImplicitDestructor) { + EXPECT_TRUE(notMatches("class Foo {};", destructor(ofClass(hasName("Foo"))))); +} + +TEST(HasAnyConstructorInitializer, SimpleCase) { + EXPECT_TRUE(notMatches( + "class Foo { Foo() { } };", + constructor(hasAnyConstructorInitializer(anything())))); + EXPECT_TRUE(matches( + "class Foo {" + " Foo() : foo_() { }" + " int foo_;" + "};", + constructor(hasAnyConstructorInitializer(anything())))); +} + +TEST(HasAnyConstructorInitializer, ForField) { + static const char Code[] = + "class Baz { };" + "class Foo {" + " Foo() : foo_() { }" + " Baz foo_;" + " Baz bar_;" + "};"; + EXPECT_TRUE(matches(Code, constructor(hasAnyConstructorInitializer( + forField(hasType(record(hasName("Baz")))))))); + EXPECT_TRUE(matches(Code, constructor(hasAnyConstructorInitializer( + forField(hasName("foo_")))))); + EXPECT_TRUE(notMatches(Code, constructor(hasAnyConstructorInitializer( + forField(hasType(record(hasName("Bar")))))))); +} + +TEST(HasAnyConstructorInitializer, WithInitializer) { + static const char Code[] = + "class Foo {" + " Foo() : foo_(0) { }" + " int foo_;" + "};"; + EXPECT_TRUE(matches(Code, constructor(hasAnyConstructorInitializer( + withInitializer(integerLiteral(equals(0))))))); + EXPECT_TRUE(notMatches(Code, constructor(hasAnyConstructorInitializer( + withInitializer(integerLiteral(equals(1))))))); +} + +TEST(HasAnyConstructorInitializer, IsWritten) { + static const char Code[] = + "struct Bar { Bar(){} };" + "class Foo {" + " Foo() : foo_() { }" + " Bar foo_;" + " Bar bar_;" + "};"; + EXPECT_TRUE(matches(Code, constructor(hasAnyConstructorInitializer( + allOf(forField(hasName("foo_")), isWritten()))))); + EXPECT_TRUE(notMatches(Code, constructor(hasAnyConstructorInitializer( + allOf(forField(hasName("bar_")), isWritten()))))); + EXPECT_TRUE(matches(Code, constructor(hasAnyConstructorInitializer( + allOf(forField(hasName("bar_")), unless(isWritten())))))); +} + +TEST(Matcher, NewExpression) { + StatementMatcher New = expression(newExpression()); + + EXPECT_TRUE(matches("class X { public: X(); }; void x() { new X; }", New)); + EXPECT_TRUE( + matches("class X { public: X(); }; void x() { new X(); }", New)); + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { new X(0); }", New)); + EXPECT_TRUE(matches("class X {}; void x(int) { new X; }", New)); +} + +TEST(Matcher, NewExpressionArgument) { + StatementMatcher New = expression(constructorCall( + hasArgument( + 0, declarationReference(to(variable(hasName("y"))))))); + + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { int y; new X(y); }", + New)); + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { int y; new X(y); }", + New)); + EXPECT_TRUE( + notMatches("class X { public: X(int); }; void x() { int z; new X(z); }", + New)); + + StatementMatcher WrongIndex = expression(constructorCall( + hasArgument( + 42, declarationReference(to(variable(hasName("y"))))))); + EXPECT_TRUE( + notMatches("class X { public: X(int); }; void x() { int y; new X(y); }", + WrongIndex)); +} + +TEST(Matcher, NewExpressionArgumentCount) { + StatementMatcher New = constructorCall(argumentCountIs(1)); + + EXPECT_TRUE( + matches("class X { public: X(int); }; void x() { new X(0); }", New)); + EXPECT_TRUE( + notMatches("class X { public: X(int, int); }; void x() { new X(0, 0); }", + New)); +} + +TEST(Matcher, DeleteExpression) { + EXPECT_TRUE(matches("struct A {}; void f(A* a) { delete a; }", + deleteExpression())); +} + +TEST(Matcher, DefaultArgument) { + StatementMatcher Arg = defaultArgument(); + + EXPECT_TRUE(matches("void x(int, int = 0) { int y; x(y); }", Arg)); + EXPECT_TRUE( + matches("class X { void x(int, int = 0) { int y; x(y); } };", Arg)); + EXPECT_TRUE(notMatches("void x(int, int = 0) { int y; x(y, 0); }", Arg)); +} + +TEST(Matcher, StringLiterals) { + StatementMatcher Literal = expression(stringLiteral()); + EXPECT_TRUE(matches("const char *s = \"string\";", Literal)); + // wide string + EXPECT_TRUE(matches("const wchar_t *s = L\"string\";", Literal)); + // with escaped characters + EXPECT_TRUE(matches("const char *s = \"\x05five\";", Literal)); + // no matching -- though the data type is the same, there is no string literal + EXPECT_TRUE(notMatches("const char s[1] = {'a'};", Literal)); +} + +TEST(Matcher, CharacterLiterals) { + StatementMatcher CharLiteral = expression(characterLiteral()); + EXPECT_TRUE(matches("const char c = 'c';", CharLiteral)); + // wide character + EXPECT_TRUE(matches("const char c = L'c';", CharLiteral)); + // wide character, Hex encoded, NOT MATCHED! + EXPECT_TRUE(notMatches("const wchar_t c = 0x2126;", CharLiteral)); + EXPECT_TRUE(notMatches("const char c = 0x1;", CharLiteral)); +} + +TEST(Matcher, IntegerLiterals) { + StatementMatcher HasIntLiteral = expression(integerLiteral()); + EXPECT_TRUE(matches("int i = 10;", HasIntLiteral)); + EXPECT_TRUE(matches("int i = 0x1AB;", HasIntLiteral)); + EXPECT_TRUE(matches("int i = 10L;", HasIntLiteral)); + EXPECT_TRUE(matches("int i = 10U;", HasIntLiteral)); + + // Non-matching cases (character literals, float and double) + EXPECT_TRUE(notMatches("int i = L'a';", + HasIntLiteral)); // this is actually a character + // literal cast to int + EXPECT_TRUE(notMatches("int i = 'a';", HasIntLiteral)); + EXPECT_TRUE(notMatches("int i = 1e10;", HasIntLiteral)); + EXPECT_TRUE(notMatches("int i = 10.0;", HasIntLiteral)); +} + +TEST(Matcher, Conditions) { + StatementMatcher Condition = ifStmt(hasCondition(boolLiteral(equals(true)))); + + EXPECT_TRUE(matches("void x() { if (true) {} }", Condition)); + EXPECT_TRUE(notMatches("void x() { if (false) {} }", Condition)); + EXPECT_TRUE(notMatches("void x() { bool a = true; if (a) {} }", Condition)); + EXPECT_TRUE(notMatches("void x() { if (true || false) {} }", Condition)); + EXPECT_TRUE(notMatches("void x() { if (1) {} }", Condition)); +} + +TEST(MatchBinaryOperator, HasOperatorName) { + StatementMatcher OperatorOr = binaryOperator(hasOperatorName("||")); + + EXPECT_TRUE(matches("void x() { true || false; }", OperatorOr)); + EXPECT_TRUE(notMatches("void x() { true && false; }", OperatorOr)); +} + +TEST(MatchBinaryOperator, HasLHSAndHasRHS) { + StatementMatcher OperatorTrueFalse = + binaryOperator(hasLHS(boolLiteral(equals(true))), + hasRHS(boolLiteral(equals(false)))); + + EXPECT_TRUE(matches("void x() { true || false; }", OperatorTrueFalse)); + EXPECT_TRUE(matches("void x() { true && false; }", OperatorTrueFalse)); + EXPECT_TRUE(notMatches("void x() { false || true; }", OperatorTrueFalse)); +} + +TEST(MatchBinaryOperator, HasEitherOperand) { + StatementMatcher HasOperand = + binaryOperator(hasEitherOperand(boolLiteral(equals(false)))); + + EXPECT_TRUE(matches("void x() { true || false; }", HasOperand)); + EXPECT_TRUE(matches("void x() { false && true; }", HasOperand)); + EXPECT_TRUE(notMatches("void x() { true || true; }", HasOperand)); +} + +TEST(Matcher, BinaryOperatorTypes) { + // Integration test that verifies the AST provides all binary operators in + // a way we expect. + // FIXME: Operator ',' + EXPECT_TRUE( + matches("void x() { 3, 4; }", binaryOperator(hasOperatorName(",")))); + EXPECT_TRUE( + matches("bool b; bool c = (b = true);", + binaryOperator(hasOperatorName("=")))); + EXPECT_TRUE( + matches("bool b = 1 != 2;", binaryOperator(hasOperatorName("!=")))); + EXPECT_TRUE( + matches("bool b = 1 == 2;", binaryOperator(hasOperatorName("==")))); + EXPECT_TRUE(matches("bool b = 1 < 2;", binaryOperator(hasOperatorName("<")))); + EXPECT_TRUE( + matches("bool b = 1 <= 2;", binaryOperator(hasOperatorName("<=")))); + EXPECT_TRUE( + matches("int i = 1 << 2;", binaryOperator(hasOperatorName("<<")))); + EXPECT_TRUE( + matches("int i = 1; int j = (i <<= 2);", + binaryOperator(hasOperatorName("<<=")))); + EXPECT_TRUE(matches("bool b = 1 > 2;", binaryOperator(hasOperatorName(">")))); + EXPECT_TRUE( + matches("bool b = 1 >= 2;", binaryOperator(hasOperatorName(">=")))); + EXPECT_TRUE( + matches("int i = 1 >> 2;", binaryOperator(hasOperatorName(">>")))); + EXPECT_TRUE( + matches("int i = 1; int j = (i >>= 2);", + binaryOperator(hasOperatorName(">>=")))); + EXPECT_TRUE( + matches("int i = 42 ^ 23;", binaryOperator(hasOperatorName("^")))); + EXPECT_TRUE( + matches("int i = 42; int j = (i ^= 42);", + binaryOperator(hasOperatorName("^=")))); + EXPECT_TRUE( + matches("int i = 42 % 23;", binaryOperator(hasOperatorName("%")))); + EXPECT_TRUE( + matches("int i = 42; int j = (i %= 42);", + binaryOperator(hasOperatorName("%=")))); + EXPECT_TRUE( + matches("bool b = 42 &23;", binaryOperator(hasOperatorName("&")))); + EXPECT_TRUE( + matches("bool b = true && false;", + binaryOperator(hasOperatorName("&&")))); + EXPECT_TRUE( + matches("bool b = true; bool c = (b &= false);", + binaryOperator(hasOperatorName("&=")))); + EXPECT_TRUE( + matches("bool b = 42 | 23;", binaryOperator(hasOperatorName("|")))); + EXPECT_TRUE( + matches("bool b = true || false;", + binaryOperator(hasOperatorName("||")))); + EXPECT_TRUE( + matches("bool b = true; bool c = (b |= false);", + binaryOperator(hasOperatorName("|=")))); + EXPECT_TRUE( + matches("int i = 42 *23;", binaryOperator(hasOperatorName("*")))); + EXPECT_TRUE( + matches("int i = 42; int j = (i *= 23);", + binaryOperator(hasOperatorName("*=")))); + EXPECT_TRUE( + matches("int i = 42 / 23;", binaryOperator(hasOperatorName("/")))); + EXPECT_TRUE( + matches("int i = 42; int j = (i /= 23);", + binaryOperator(hasOperatorName("/=")))); + EXPECT_TRUE( + matches("int i = 42 + 23;", binaryOperator(hasOperatorName("+")))); + EXPECT_TRUE( + matches("int i = 42; int j = (i += 23);", + binaryOperator(hasOperatorName("+=")))); + EXPECT_TRUE( + matches("int i = 42 - 23;", binaryOperator(hasOperatorName("-")))); + EXPECT_TRUE( + matches("int i = 42; int j = (i -= 23);", + binaryOperator(hasOperatorName("-=")))); + EXPECT_TRUE( + matches("struct A { void x() { void (A::*a)(); (this->*a)(); } };", + binaryOperator(hasOperatorName("->*")))); + EXPECT_TRUE( + matches("struct A { void x() { void (A::*a)(); ((*this).*a)(); } };", + binaryOperator(hasOperatorName(".*")))); + + // Member expressions as operators are not supported in matches. + EXPECT_TRUE( + notMatches("struct A { void x(A *a) { a->x(this); } };", + binaryOperator(hasOperatorName("->")))); + + // Initializer assignments are not represented as operator equals. + EXPECT_TRUE( + notMatches("bool b = true;", binaryOperator(hasOperatorName("=")))); + + // Array indexing is not represented as operator. + EXPECT_TRUE(notMatches("int a[42]; void x() { a[23]; }", unaryOperator())); + + // Overloaded operators do not match at all. + EXPECT_TRUE(notMatches( + "struct A { bool operator&&(const A &a) const { return false; } };" + "void x() { A a, b; a && b; }", + binaryOperator())); +} + +TEST(MatchUnaryOperator, HasOperatorName) { + StatementMatcher OperatorNot = unaryOperator(hasOperatorName("!")); + + EXPECT_TRUE(matches("void x() { !true; } ", OperatorNot)); + EXPECT_TRUE(notMatches("void x() { true; } ", OperatorNot)); +} + +TEST(MatchUnaryOperator, HasUnaryOperand) { + StatementMatcher OperatorOnFalse = + unaryOperator(hasUnaryOperand(boolLiteral(equals(false)))); + + EXPECT_TRUE(matches("void x() { !false; }", OperatorOnFalse)); + EXPECT_TRUE(notMatches("void x() { !true; }", OperatorOnFalse)); +} + +TEST(Matcher, UnaryOperatorTypes) { + // Integration test that verifies the AST provides all unary operators in + // a way we expect. + EXPECT_TRUE(matches("bool b = !true;", unaryOperator(hasOperatorName("!")))); + EXPECT_TRUE( + matches("bool b; bool *p = &b;", unaryOperator(hasOperatorName("&")))); + EXPECT_TRUE(matches("int i = ~ 1;", unaryOperator(hasOperatorName("~")))); + EXPECT_TRUE( + matches("bool *p; bool b = *p;", unaryOperator(hasOperatorName("*")))); + EXPECT_TRUE( + matches("int i; int j = +i;", unaryOperator(hasOperatorName("+")))); + EXPECT_TRUE( + matches("int i; int j = -i;", unaryOperator(hasOperatorName("-")))); + EXPECT_TRUE( + matches("int i; int j = ++i;", unaryOperator(hasOperatorName("++")))); + EXPECT_TRUE( + matches("int i; int j = i++;", unaryOperator(hasOperatorName("++")))); + EXPECT_TRUE( + matches("int i; int j = --i;", unaryOperator(hasOperatorName("--")))); + EXPECT_TRUE( + matches("int i; int j = i--;", unaryOperator(hasOperatorName("--")))); + + // We don't match conversion operators. + EXPECT_TRUE(notMatches("int i; double d = (double)i;", unaryOperator())); + + // Function calls are not represented as operator. + EXPECT_TRUE(notMatches("void f(); void x() { f(); }", unaryOperator())); + + // Overloaded operators do not match at all. + // FIXME: We probably want to add that. + EXPECT_TRUE(notMatches( + "struct A { bool operator!() const { return false; } };" + "void x() { A a; !a; }", unaryOperator(hasOperatorName("!")))); +} + +TEST(Matcher, ConditionalOperator) { + StatementMatcher Conditional = conditionalOperator( + hasCondition(boolLiteral(equals(true))), + hasTrueExpression(boolLiteral(equals(false)))); + + EXPECT_TRUE(matches("void x() { true ? false : true; }", Conditional)); + EXPECT_TRUE(notMatches("void x() { false ? false : true; }", Conditional)); + EXPECT_TRUE(notMatches("void x() { true ? true : false; }", Conditional)); + + StatementMatcher ConditionalFalse = conditionalOperator( + hasFalseExpression(boolLiteral(equals(false)))); + + EXPECT_TRUE(matches("void x() { true ? true : false; }", ConditionalFalse)); + EXPECT_TRUE( + notMatches("void x() { true ? false : true; }", ConditionalFalse)); +} + +TEST(ArraySubscriptMatchers, ArraySubscripts) { + EXPECT_TRUE(matches("int i[2]; void f() { i[1] = 1; }", + arraySubscriptExpr())); + EXPECT_TRUE(notMatches("int i; void f() { i = 1; }", + arraySubscriptExpr())); +} + +TEST(ArraySubscriptMatchers, ArrayIndex) { + EXPECT_TRUE(matches( + "int i[2]; void f() { i[1] = 1; }", + arraySubscriptExpr(hasIndex(integerLiteral(equals(1)))))); + EXPECT_TRUE(matches( + "int i[2]; void f() { 1[i] = 1; }", + arraySubscriptExpr(hasIndex(integerLiteral(equals(1)))))); + EXPECT_TRUE(notMatches( + "int i[2]; void f() { i[1] = 1; }", + arraySubscriptExpr(hasIndex(integerLiteral(equals(0)))))); +} + +TEST(ArraySubscriptMatchers, MatchesArrayBase) { + EXPECT_TRUE(matches( + "int i[2]; void f() { i[1] = 2; }", + arraySubscriptExpr(hasBase(implicitCast( + hasSourceExpression(declarationReference())))))); +} + +TEST(Matcher, HasNameSupportsNamespaces) { + EXPECT_TRUE(matches("namespace a { namespace b { class C; } }", + record(hasName("a::b::C")))); + EXPECT_TRUE(matches("namespace a { namespace b { class C; } }", + record(hasName("::a::b::C")))); + EXPECT_TRUE(matches("namespace a { namespace b { class C; } }", + record(hasName("b::C")))); + EXPECT_TRUE(matches("namespace a { namespace b { class C; } }", + record(hasName("C")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class C; } }", + record(hasName("c::b::C")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class C; } }", + record(hasName("a::c::C")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class C; } }", + record(hasName("a::b::A")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class C; } }", + record(hasName("::C")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class C; } }", + record(hasName("::b::C")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class C; } }", + record(hasName("z::a::b::C")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class C; } }", + record(hasName("a+b::C")))); + EXPECT_TRUE(notMatches("namespace a { namespace b { class AC; } }", + record(hasName("C")))); +} + +TEST(Matcher, HasNameSupportsOuterClasses) { + EXPECT_TRUE( + matches("class A { class B { class C; }; };", record(hasName("A::B::C")))); + EXPECT_TRUE( + matches("class A { class B { class C; }; };", + record(hasName("::A::B::C")))); + EXPECT_TRUE( + matches("class A { class B { class C; }; };", record(hasName("B::C")))); + EXPECT_TRUE( + matches("class A { class B { class C; }; };", record(hasName("C")))); + EXPECT_TRUE( + notMatches("class A { class B { class C; }; };", + record(hasName("c::B::C")))); + EXPECT_TRUE( + notMatches("class A { class B { class C; }; };", + record(hasName("A::c::C")))); + EXPECT_TRUE( + notMatches("class A { class B { class C; }; };", + record(hasName("A::B::A")))); + EXPECT_TRUE( + notMatches("class A { class B { class C; }; };", record(hasName("::C")))); + EXPECT_TRUE( + notMatches("class A { class B { class C; }; };", + record(hasName("::B::C")))); + EXPECT_TRUE(notMatches("class A { class B { class C; }; };", + record(hasName("z::A::B::C")))); + EXPECT_TRUE( + notMatches("class A { class B { class C; }; };", + record(hasName("A+B::C")))); +} + +TEST(Matcher, IsDefinition) { + DeclarationMatcher DefinitionOfClassA = + record(hasName("A"), isDefinition()); + EXPECT_TRUE(matches("class A {};", DefinitionOfClassA)); + EXPECT_TRUE(notMatches("class A;", DefinitionOfClassA)); + + DeclarationMatcher DefinitionOfVariableA = + variable(hasName("a"), isDefinition()); + EXPECT_TRUE(matches("int a;", DefinitionOfVariableA)); + EXPECT_TRUE(notMatches("extern int a;", DefinitionOfVariableA)); + + DeclarationMatcher DefinitionOfMethodA = + method(hasName("a"), isDefinition()); + EXPECT_TRUE(matches("class A { void a() {} };", DefinitionOfMethodA)); + EXPECT_TRUE(notMatches("class A { void a(); };", DefinitionOfMethodA)); +} + +TEST(Matcher, OfClass) { + StatementMatcher Constructor = constructorCall(hasDeclaration(method( + ofClass(hasName("X"))))); + + EXPECT_TRUE( + matches("class X { public: X(); }; void x(int) { X x; }", Constructor)); + EXPECT_TRUE( + matches("class X { public: X(); }; void x(int) { X x = X(); }", + Constructor)); + EXPECT_TRUE( + notMatches("class Y { public: Y(); }; void x(int) { Y y; }", + Constructor)); +} + +TEST(Matcher, VisitsTemplateInstantiations) { + EXPECT_TRUE(matches( + "class A { public: void x(); };" + "template <typename T> class B { public: void y() { T t; t.x(); } };" + "void f() { B<A> b; b.y(); }", call(callee(method(hasName("x")))))); + + EXPECT_TRUE(matches( + "class A { public: void x(); };" + "class C {" + " public:" + " template <typename T> class B { public: void y() { T t; t.x(); } };" + "};" + "void f() {" + " C::B<A> b; b.y();" + "}", record(hasName("C"), + hasDescendant(call(callee(method(hasName("x")))))))); +} + +TEST(Matcher, HandlesNullQualTypes) { + // FIXME: Add a Type matcher so we can replace uses of this + // variable with Type(True()) + const TypeMatcher AnyType = anything(); + + // We don't really care whether this matcher succeeds; we're testing that + // it completes without crashing. + EXPECT_TRUE(matches( + "struct A { };" + "template <typename T>" + "void f(T t) {" + " T local_t(t /* this becomes a null QualType in the AST */);" + "}" + "void g() {" + " f(0);" + "}", + expression(hasType(TypeMatcher( + anyOf( + TypeMatcher(hasDeclaration(anything())), + pointsTo(AnyType), + references(AnyType) + // Other QualType matchers should go here. + )))))); +} + +// For testing AST_MATCHER_P(). +AST_MATCHER_P(Decl, just, internal::Matcher<Decl>, AMatcher) { + // Make sure all special variables are used: node, match_finder, + // bound_nodes_builder, and the parameter named 'AMatcher'. + return AMatcher.matches(Node, Finder, Builder); +} + +TEST(AstMatcherPMacro, Works) { + DeclarationMatcher HasClassB = just(has(record(hasName("B")).bind("b"))); + + EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };", + HasClassB, new VerifyIdIsBoundToDecl<Decl>("b"))); + + EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };", + HasClassB, new VerifyIdIsBoundToDecl<Decl>("a"))); + + EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };", + HasClassB, new VerifyIdIsBoundToDecl<Decl>("b"))); +} + +AST_POLYMORPHIC_MATCHER_P( + polymorphicHas, internal::Matcher<Decl>, AMatcher) { + TOOLING_COMPILE_ASSERT((llvm::is_same<NodeType, Decl>::value) || + (llvm::is_same<NodeType, Stmt>::value), + assert_node_type_is_accessible); + internal::TypedBaseMatcher<Decl> ChildMatcher(AMatcher); + return Finder->matchesChildOf( + Node, ChildMatcher, Builder, + ASTMatchFinder::TK_IgnoreImplicitCastsAndParentheses, + ASTMatchFinder::BK_First); +} + +TEST(AstPolymorphicMatcherPMacro, Works) { + DeclarationMatcher HasClassB = polymorphicHas(record(hasName("B")).bind("b")); + + EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };", + HasClassB, new VerifyIdIsBoundToDecl<Decl>("b"))); + + EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };", + HasClassB, new VerifyIdIsBoundToDecl<Decl>("a"))); + + EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };", + HasClassB, new VerifyIdIsBoundToDecl<Decl>("b"))); + + StatementMatcher StatementHasClassB = + polymorphicHas(record(hasName("B"))); + + EXPECT_TRUE(matches("void x() { class B {}; }", StatementHasClassB)); +} + +TEST(For, FindsForLoops) { + EXPECT_TRUE(matches("void f() { for(;;); }", forStmt())); + EXPECT_TRUE(matches("void f() { if(true) for(;;); }", forStmt())); +} + +TEST(For, ForLoopInternals) { + EXPECT_TRUE(matches("void f(){ int i; for (; i < 3 ; ); }", + forStmt(hasCondition(anything())))); + EXPECT_TRUE(matches("void f() { for (int i = 0; ;); }", + forStmt(hasLoopInit(anything())))); +} + +TEST(For, NegativeForLoopInternals) { + EXPECT_TRUE(notMatches("void f(){ for (int i = 0; ; ++i); }", + forStmt(hasCondition(expression())))); + EXPECT_TRUE(notMatches("void f() {int i; for (; i < 4; ++i) {} }", + forStmt(hasLoopInit(anything())))); +} + +TEST(For, ReportsNoFalsePositives) { + EXPECT_TRUE(notMatches("void f() { ; }", forStmt())); + EXPECT_TRUE(notMatches("void f() { if(true); }", forStmt())); +} + +TEST(CompoundStatement, HandlesSimpleCases) { + EXPECT_TRUE(notMatches("void f();", compoundStatement())); + EXPECT_TRUE(matches("void f() {}", compoundStatement())); + EXPECT_TRUE(matches("void f() {{}}", compoundStatement())); +} + +TEST(CompoundStatement, DoesNotMatchEmptyStruct) { + // It's not a compound statement just because there's "{}" in the source + // text. This is an AST search, not grep. + EXPECT_TRUE(notMatches("namespace n { struct S {}; }", + compoundStatement())); + EXPECT_TRUE(matches("namespace n { struct S { void f() {{}} }; }", + compoundStatement())); +} + +TEST(HasBody, FindsBodyOfForWhileDoLoops) { + EXPECT_TRUE(matches("void f() { for(;;) {} }", + forStmt(hasBody(compoundStatement())))); + EXPECT_TRUE(notMatches("void f() { for(;;); }", + forStmt(hasBody(compoundStatement())))); + EXPECT_TRUE(matches("void f() { while(true) {} }", + whileStmt(hasBody(compoundStatement())))); + EXPECT_TRUE(matches("void f() { do {} while(true); }", + doStmt(hasBody(compoundStatement())))); +} + +TEST(HasAnySubstatement, MatchesForTopLevelCompoundStatement) { + // The simplest case: every compound statement is in a function + // definition, and the function body itself must be a compound + // statement. + EXPECT_TRUE(matches("void f() { for (;;); }", + compoundStatement(hasAnySubstatement(forStmt())))); +} + +TEST(HasAnySubstatement, IsNotRecursive) { + // It's really "has any immediate substatement". + EXPECT_TRUE(notMatches("void f() { if (true) for (;;); }", + compoundStatement(hasAnySubstatement(forStmt())))); +} + +TEST(HasAnySubstatement, MatchesInNestedCompoundStatements) { + EXPECT_TRUE(matches("void f() { if (true) { for (;;); } }", + compoundStatement(hasAnySubstatement(forStmt())))); +} + +TEST(HasAnySubstatement, FindsSubstatementBetweenOthers) { + EXPECT_TRUE(matches("void f() { 1; 2; 3; for (;;); 4; 5; 6; }", + compoundStatement(hasAnySubstatement(forStmt())))); +} + +TEST(StatementCountIs, FindsNoStatementsInAnEmptyCompoundStatement) { + EXPECT_TRUE(matches("void f() { }", + compoundStatement(statementCountIs(0)))); + EXPECT_TRUE(notMatches("void f() {}", + compoundStatement(statementCountIs(1)))); +} + +TEST(StatementCountIs, AppearsToMatchOnlyOneCount) { + EXPECT_TRUE(matches("void f() { 1; }", + compoundStatement(statementCountIs(1)))); + EXPECT_TRUE(notMatches("void f() { 1; }", + compoundStatement(statementCountIs(0)))); + EXPECT_TRUE(notMatches("void f() { 1; }", + compoundStatement(statementCountIs(2)))); +} + +TEST(StatementCountIs, WorksWithMultipleStatements) { + EXPECT_TRUE(matches("void f() { 1; 2; 3; }", + compoundStatement(statementCountIs(3)))); +} + +TEST(StatementCountIs, WorksWithNestedCompoundStatements) { + EXPECT_TRUE(matches("void f() { { 1; } { 1; 2; 3; 4; } }", + compoundStatement(statementCountIs(1)))); + EXPECT_TRUE(matches("void f() { { 1; } { 1; 2; 3; 4; } }", + compoundStatement(statementCountIs(2)))); + EXPECT_TRUE(notMatches("void f() { { 1; } { 1; 2; 3; 4; } }", + compoundStatement(statementCountIs(3)))); + EXPECT_TRUE(matches("void f() { { 1; } { 1; 2; 3; 4; } }", + compoundStatement(statementCountIs(4)))); +} + +TEST(Member, WorksInSimplestCase) { + EXPECT_TRUE(matches("struct { int first; } s; int i(s.first);", + memberExpression(member(hasName("first"))))); +} + +TEST(Member, DoesNotMatchTheBaseExpression) { + // Don't pick out the wrong part of the member expression, this should + // be checking the member (name) only. + EXPECT_TRUE(notMatches("struct { int i; } first; int i(first.i);", + memberExpression(member(hasName("first"))))); +} + +TEST(Member, MatchesInMemberFunctionCall) { + EXPECT_TRUE(matches("void f() {" + " struct { void first() {}; } s;" + " s.first();" + "};", + memberExpression(member(hasName("first"))))); +} + +TEST(HasObjectExpression, DoesNotMatchMember) { + EXPECT_TRUE(notMatches( + "class X {}; struct Z { X m; }; void f(Z z) { z.m; }", + memberExpression(hasObjectExpression(hasType(record(hasName("X"))))))); +} + +TEST(HasObjectExpression, MatchesBaseOfVariable) { + EXPECT_TRUE(matches( + "struct X { int m; }; void f(X x) { x.m; }", + memberExpression(hasObjectExpression(hasType(record(hasName("X"))))))); + EXPECT_TRUE(matches( + "struct X { int m; }; void f(X* x) { x->m; }", + memberExpression(hasObjectExpression( + hasType(pointsTo(record(hasName("X")))))))); +} + +TEST(HasObjectExpression, + MatchesObjectExpressionOfImplicitlyFormedMemberExpression) { + EXPECT_TRUE(matches( + "class X {}; struct S { X m; void f() { this->m; } };", + memberExpression(hasObjectExpression( + hasType(pointsTo(record(hasName("S")))))))); + EXPECT_TRUE(matches( + "class X {}; struct S { X m; void f() { m; } };", + memberExpression(hasObjectExpression( + hasType(pointsTo(record(hasName("S")))))))); +} + +TEST(Field, DoesNotMatchNonFieldMembers) { + EXPECT_TRUE(notMatches("class X { void m(); };", field(hasName("m")))); + EXPECT_TRUE(notMatches("class X { class m {}; };", field(hasName("m")))); + EXPECT_TRUE(notMatches("class X { enum { m }; };", field(hasName("m")))); + EXPECT_TRUE(notMatches("class X { enum m {}; };", field(hasName("m")))); +} + +TEST(Field, MatchesField) { + EXPECT_TRUE(matches("class X { int m; };", field(hasName("m")))); +} + +TEST(IsConstQualified, MatchesConstInt) { + EXPECT_TRUE(matches("const int i = 42;", + variable(hasType(isConstQualified())))); +} + +TEST(IsConstQualified, MatchesConstPointer) { + EXPECT_TRUE(matches("int i = 42; int* const p(&i);", + variable(hasType(isConstQualified())))); +} + +TEST(IsConstQualified, MatchesThroughTypedef) { + EXPECT_TRUE(matches("typedef const int const_int; const_int i = 42;", + variable(hasType(isConstQualified())))); + EXPECT_TRUE(matches("typedef int* int_ptr; const int_ptr p(0);", + variable(hasType(isConstQualified())))); +} + +TEST(IsConstQualified, DoesNotMatchInappropriately) { + EXPECT_TRUE(notMatches("typedef int nonconst_int; nonconst_int i = 42;", + variable(hasType(isConstQualified())))); + EXPECT_TRUE(notMatches("int const* p;", + variable(hasType(isConstQualified())))); +} + +TEST(ReinterpretCast, MatchesSimpleCase) { + EXPECT_TRUE(matches("char* p = reinterpret_cast<char*>(&p);", + expression(reinterpretCast()))); +} + +TEST(ReinterpretCast, DoesNotMatchOtherCasts) { + EXPECT_TRUE(notMatches("char* p = (char*)(&p);", + expression(reinterpretCast()))); + EXPECT_TRUE(notMatches("char q, *p = const_cast<char*>(&q);", + expression(reinterpretCast()))); + EXPECT_TRUE(notMatches("void* p = static_cast<void*>(&p);", + expression(reinterpretCast()))); + EXPECT_TRUE(notMatches("struct B { virtual ~B() {} }; struct D : B {};" + "B b;" + "D* p = dynamic_cast<D*>(&b);", + expression(reinterpretCast()))); +} + +TEST(FunctionalCast, MatchesSimpleCase) { + std::string foo_class = "class Foo { public: Foo(char*); };"; + EXPECT_TRUE(matches(foo_class + "void r() { Foo f = Foo(\"hello world\"); }", + expression(functionalCast()))); +} + +TEST(FunctionalCast, DoesNotMatchOtherCasts) { + std::string FooClass = "class Foo { public: Foo(char*); };"; + EXPECT_TRUE( + notMatches(FooClass + "void r() { Foo f = (Foo) \"hello world\"; }", + expression(functionalCast()))); + EXPECT_TRUE( + notMatches(FooClass + "void r() { Foo f = \"hello world\"; }", + expression(functionalCast()))); +} + +TEST(DynamicCast, MatchesSimpleCase) { + EXPECT_TRUE(matches("struct B { virtual ~B() {} }; struct D : B {};" + "B b;" + "D* p = dynamic_cast<D*>(&b);", + expression(dynamicCast()))); +} + +TEST(StaticCast, MatchesSimpleCase) { + EXPECT_TRUE(matches("void* p(static_cast<void*>(&p));", + expression(staticCast()))); +} + +TEST(StaticCast, DoesNotMatchOtherCasts) { + EXPECT_TRUE(notMatches("char* p = (char*)(&p);", + expression(staticCast()))); + EXPECT_TRUE(notMatches("char q, *p = const_cast<char*>(&q);", + expression(staticCast()))); + EXPECT_TRUE(notMatches("void* p = reinterpret_cast<char*>(&p);", + expression(staticCast()))); + EXPECT_TRUE(notMatches("struct B { virtual ~B() {} }; struct D : B {};" + "B b;" + "D* p = dynamic_cast<D*>(&b);", + expression(staticCast()))); +} + +TEST(HasDestinationType, MatchesSimpleCase) { + EXPECT_TRUE(matches("char* p = static_cast<char*>(0);", + expression( + staticCast(hasDestinationType( + pointsTo(TypeMatcher(anything()))))))); +} + +TEST(HasSourceExpression, MatchesImplicitCasts) { + EXPECT_TRUE(matches("class string {}; class URL { public: URL(string s); };" + "void r() {string a_string; URL url = a_string; }", + expression(implicitCast( + hasSourceExpression(constructorCall()))))); +} + +TEST(HasSourceExpression, MatchesExplicitCasts) { + EXPECT_TRUE(matches("float x = static_cast<float>(42);", + expression(explicitCast( + hasSourceExpression(hasDescendant( + expression(integerLiteral()))))))); +} + +TEST(Statement, DoesNotMatchDeclarations) { + EXPECT_TRUE(notMatches("class X {};", statement())); +} + +TEST(Statement, MatchesCompoundStatments) { + EXPECT_TRUE(matches("void x() {}", statement())); +} + +TEST(DeclarationStatement, DoesNotMatchCompoundStatements) { + EXPECT_TRUE(notMatches("void x() {}", declarationStatement())); +} + +TEST(DeclarationStatement, MatchesVariableDeclarationStatements) { + EXPECT_TRUE(matches("void x() { int a; }", declarationStatement())); +} + +TEST(InitListExpression, MatchesInitListExpression) { + EXPECT_TRUE(matches("int a[] = { 1, 2 };", + initListExpr(hasType(asString("int [2]"))))); + EXPECT_TRUE(matches("struct B { int x, y; }; B b = { 5, 6 };", + initListExpr(hasType(record(hasName("B")))))); +} + +TEST(UsingDeclaration, MatchesUsingDeclarations) { + EXPECT_TRUE(matches("namespace X { int x; } using X::x;", + usingDecl())); +} + +TEST(UsingDeclaration, MatchesShadowUsingDelcarations) { + EXPECT_TRUE(matches("namespace f { int a; } using f::a;", + usingDecl(hasAnyUsingShadowDecl(hasName("a"))))); +} + +TEST(UsingDeclaration, MatchesSpecificTarget) { + EXPECT_TRUE(matches("namespace f { int a; void b(); } using f::b;", + usingDecl(hasAnyUsingShadowDecl( + hasTargetDecl(function()))))); + EXPECT_TRUE(notMatches("namespace f { int a; void b(); } using f::a;", + usingDecl(hasAnyUsingShadowDecl( + hasTargetDecl(function()))))); +} + +TEST(UsingDeclaration, ThroughUsingDeclaration) { + EXPECT_TRUE(matches( + "namespace a { void f(); } using a::f; void g() { f(); }", + declarationReference(throughUsingDecl(anything())))); + EXPECT_TRUE(notMatches( + "namespace a { void f(); } using a::f; void g() { a::f(); }", + declarationReference(throughUsingDecl(anything())))); +} + +TEST(While, MatchesWhileLoops) { + EXPECT_TRUE(notMatches("void x() {}", whileStmt())); + EXPECT_TRUE(matches("void x() { while(true); }", whileStmt())); + EXPECT_TRUE(notMatches("void x() { do {} while(true); }", whileStmt())); +} + +TEST(Do, MatchesDoLoops) { + EXPECT_TRUE(matches("void x() { do {} while(true); }", doStmt())); + EXPECT_TRUE(matches("void x() { do ; while(false); }", doStmt())); +} + +TEST(Do, DoesNotMatchWhileLoops) { + EXPECT_TRUE(notMatches("void x() { while(true) {} }", doStmt())); +} + +TEST(SwitchCase, MatchesCase) { + EXPECT_TRUE(matches("void x() { switch(42) { case 42:; } }", switchCase())); + EXPECT_TRUE(matches("void x() { switch(42) { default:; } }", switchCase())); + EXPECT_TRUE(matches("void x() { switch(42) default:; }", switchCase())); + EXPECT_TRUE(notMatches("void x() { switch(42) {} }", switchCase())); +} + +TEST(HasConditionVariableStatement, DoesNotMatchCondition) { + EXPECT_TRUE(notMatches( + "void x() { if(true) {} }", + ifStmt(hasConditionVariableStatement(declarationStatement())))); + EXPECT_TRUE(notMatches( + "void x() { int x; if((x = 42)) {} }", + ifStmt(hasConditionVariableStatement(declarationStatement())))); +} + +TEST(HasConditionVariableStatement, MatchesConditionVariables) { + EXPECT_TRUE(matches( + "void x() { if(int* a = 0) {} }", + ifStmt(hasConditionVariableStatement(declarationStatement())))); +} + +TEST(ForEach, BindsOneNode) { + EXPECT_TRUE(matchAndVerifyResultTrue("class C { int x; };", + record(hasName("C"), forEach(field(hasName("x")).bind("x"))), + new VerifyIdIsBoundToDecl<FieldDecl>("x", 1))); +} + +TEST(ForEach, BindsMultipleNodes) { + EXPECT_TRUE(matchAndVerifyResultTrue("class C { int x; int y; int z; };", + record(hasName("C"), forEach(field().bind("f"))), + new VerifyIdIsBoundToDecl<FieldDecl>("f", 3))); +} + +TEST(ForEach, BindsRecursiveCombinations) { + EXPECT_TRUE(matchAndVerifyResultTrue( + "class C { class D { int x; int y; }; class E { int y; int z; }; };", + record(hasName("C"), forEach(record(forEach(field().bind("f"))))), + new VerifyIdIsBoundToDecl<FieldDecl>("f", 4))); +} + +TEST(ForEachDescendant, BindsOneNode) { + EXPECT_TRUE(matchAndVerifyResultTrue("class C { class D { int x; }; };", + record(hasName("C"), forEachDescendant(field(hasName("x")).bind("x"))), + new VerifyIdIsBoundToDecl<FieldDecl>("x", 1))); +} + +TEST(ForEachDescendant, BindsMultipleNodes) { + EXPECT_TRUE(matchAndVerifyResultTrue( + "class C { class D { int x; int y; }; " + " class E { class F { int y; int z; }; }; };", + record(hasName("C"), forEachDescendant(field().bind("f"))), + new VerifyIdIsBoundToDecl<FieldDecl>("f", 4))); +} + +TEST(ForEachDescendant, BindsRecursiveCombinations) { + EXPECT_TRUE(matchAndVerifyResultTrue( + "class C { class D { " + " class E { class F { class G { int y; int z; }; }; }; }; };", + record(hasName("C"), forEachDescendant(record( + forEachDescendant(field().bind("f"))))), + new VerifyIdIsBoundToDecl<FieldDecl>("f", 8))); +} + + +TEST(IsTemplateInstantiation, MatchesImplicitClassTemplateInstantiation) { + // Make sure that we can both match the class by name (::X) and by the type + // the template was instantiated with (via a field). + + EXPECT_TRUE(matches( + "template <typename T> class X {}; class A {}; X<A> x;", + record(hasName("::X"), isTemplateInstantiation()))); + + EXPECT_TRUE(matches( + "template <typename T> class X { T t; }; class A {}; X<A> x;", + record(isTemplateInstantiation(), hasDescendant( + field(hasType(record(hasName("A")))))))); +} + +TEST(IsTemplateInstantiation, MatchesImplicitFunctionTemplateInstantiation) { + EXPECT_TRUE(matches( + "template <typename T> void f(T t) {} class A {}; void g() { f(A()); }", + function(hasParameter(0, hasType(record(hasName("A")))), + isTemplateInstantiation()))); +} + +TEST(IsTemplateInstantiation, MatchesExplicitClassTemplateInstantiation) { + EXPECT_TRUE(matches( + "template <typename T> class X { T t; }; class A {};" + "template class X<A>;", + record(isTemplateInstantiation(), hasDescendant( + field(hasType(record(hasName("A")))))))); +} + +TEST(IsTemplateInstantiation, + MatchesInstantiationOfPartiallySpecializedClassTemplate) { + EXPECT_TRUE(matches( + "template <typename T> class X {};" + "template <typename T> class X<T*> {}; class A {}; X<A*> x;", + record(hasName("::X"), isTemplateInstantiation()))); +} + +TEST(IsTemplateInstantiation, + MatchesInstantiationOfClassTemplateNestedInNonTemplate) { + EXPECT_TRUE(matches( + "class A {};" + "class X {" + " template <typename U> class Y { U u; };" + " Y<A> y;" + "};", + record(hasName("::X::Y"), isTemplateInstantiation()))); +} + +TEST(IsTemplateInstantiation, DoesNotMatchInstantiationsInsideOfInstantiation) { + // FIXME: Figure out whether this makes sense. It doesn't affect the + // normal use case as long as the uppermost instantiation always is marked + // as template instantiation, but it might be confusing as a predicate. + EXPECT_TRUE(matches( + "class A {};" + "template <typename T> class X {" + " template <typename U> class Y { U u; };" + " Y<T> y;" + "}; X<A> x;", + record(hasName("::X<A>::Y"), unless(isTemplateInstantiation())))); +} + +TEST(IsTemplateInstantiation, DoesNotMatchExplicitClassTemplateSpecialization) { + EXPECT_TRUE(notMatches( + "template <typename T> class X {}; class A {};" + "template <> class X<A> {}; X<A> x;", + record(hasName("::X"), isTemplateInstantiation()))); +} + +TEST(IsTemplateInstantiation, DoesNotMatchNonTemplate) { + EXPECT_TRUE(notMatches( + "class A {}; class Y { A a; };", + record(isTemplateInstantiation()))); +} + +} // end namespace ast_matchers +} // end namespace clang diff --git a/unittests/ASTMatchers/ASTMatchersTest.h b/unittests/ASTMatchers/ASTMatchersTest.h new file mode 100644 index 0000000..64816f5 --- /dev/null +++ b/unittests/ASTMatchers/ASTMatchersTest.h @@ -0,0 +1,128 @@ +//===- unittest/Tooling/ASTMatchersTest.h - Matcher tests helpers ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNITTESTS_AST_MATCHERS_AST_MATCHERS_TEST_H +#define LLVM_CLANG_UNITTESTS_AST_MATCHERS_AST_MATCHERS_TEST_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ast_matchers { + +using clang::tooling::newFrontendActionFactory; +using clang::tooling::runToolOnCode; +using clang::tooling::FrontendActionFactory; + +class BoundNodesCallback { +public: + virtual ~BoundNodesCallback() {} + virtual bool run(const BoundNodes *BoundNodes) = 0; +}; + +// If 'FindResultVerifier' is not NULL, sets *Verified to the result of +// running 'FindResultVerifier' with the bound nodes as argument. +// If 'FindResultVerifier' is NULL, sets *Verified to true when Run is called. +class VerifyMatch : public MatchFinder::MatchCallback { +public: + VerifyMatch(BoundNodesCallback *FindResultVerifier, bool *Verified) + : Verified(Verified), FindResultReviewer(FindResultVerifier) {} + + virtual void run(const MatchFinder::MatchResult &Result) { + if (FindResultReviewer != NULL) { + *Verified = FindResultReviewer->run(&Result.Nodes); + } else { + *Verified = true; + } + } + +private: + bool *const Verified; + BoundNodesCallback *const FindResultReviewer; +}; + +template <typename T> +testing::AssertionResult matchesConditionally(const std::string &Code, + const T &AMatcher, + bool ExpectMatch) { + bool Found = false; + MatchFinder Finder; + Finder.addMatcher(AMatcher, new VerifyMatch(0, &Found)); + OwningPtr<FrontendActionFactory> Factory(newFrontendActionFactory(&Finder)); + if (!runToolOnCode(Factory->create(), Code)) { + return testing::AssertionFailure() << "Parsing error in \"" << Code << "\""; + } + if (!Found && ExpectMatch) { + return testing::AssertionFailure() + << "Could not find match in \"" << Code << "\""; + } else if (Found && !ExpectMatch) { + return testing::AssertionFailure() + << "Found unexpected match in \"" << Code << "\""; + } + return testing::AssertionSuccess(); +} + +template <typename T> +testing::AssertionResult matches(const std::string &Code, const T &AMatcher) { + return matchesConditionally(Code, AMatcher, true); +} + +template <typename T> +testing::AssertionResult notMatches(const std::string &Code, + const T &AMatcher) { + return matchesConditionally(Code, AMatcher, false); +} + +template <typename T> +testing::AssertionResult +matchAndVerifyResultConditionally(const std::string &Code, const T &AMatcher, + BoundNodesCallback *FindResultVerifier, + bool ExpectResult) { + llvm::OwningPtr<BoundNodesCallback> ScopedVerifier(FindResultVerifier); + bool VerifiedResult = false; + MatchFinder Finder; + Finder.addMatcher( + AMatcher, new VerifyMatch(FindResultVerifier, &VerifiedResult)); + OwningPtr<FrontendActionFactory> Factory(newFrontendActionFactory(&Finder)); + if (!runToolOnCode(Factory->create(), Code)) { + return testing::AssertionFailure() << "Parsing error in \"" << Code << "\""; + } + if (!VerifiedResult && ExpectResult) { + return testing::AssertionFailure() + << "Could not verify result in \"" << Code << "\""; + } else if (VerifiedResult && !ExpectResult) { + return testing::AssertionFailure() + << "Verified unexpected result in \"" << Code << "\""; + } + return testing::AssertionSuccess(); +} + +// FIXME: Find better names for these functions (or document what they +// do more precisely). +template <typename T> +testing::AssertionResult +matchAndVerifyResultTrue(const std::string &Code, const T &AMatcher, + BoundNodesCallback *FindResultVerifier) { + return matchAndVerifyResultConditionally( + Code, AMatcher, FindResultVerifier, true); +} + +template <typename T> +testing::AssertionResult +matchAndVerifyResultFalse(const std::string &Code, const T &AMatcher, + BoundNodesCallback *FindResultVerifier) { + return matchAndVerifyResultConditionally( + Code, AMatcher, FindResultVerifier, false); +} + +} // end namespace ast_matchers +} // end namespace clang + +#endif // LLVM_CLANG_UNITTESTS_AST_MATCHERS_AST_MATCHERS_TEST_H diff --git a/unittests/ASTMatchers/CMakeLists.txt b/unittests/ASTMatchers/CMakeLists.txt new file mode 100644 index 0000000..b56d756 --- /dev/null +++ b/unittests/ASTMatchers/CMakeLists.txt @@ -0,0 +1,12 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + asmparser + support + mc + ) + +add_clang_unittest(ASTMatchersTests + ASTMatchersTest.cpp) + +target_link_libraries(ASTMatchersTests + gtest gtest_main clangASTMatchers clangTooling) diff --git a/unittests/ASTMatchers/Makefile b/unittests/ASTMatchers/Makefile new file mode 100644 index 0000000..d3e4aa37 --- /dev/null +++ b/unittests/ASTMatchers/Makefile @@ -0,0 +1,19 @@ +##===- unittests/ASTMatchers/Makefile ----------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL = ../.. + +TESTNAME = ASTMatchers +include $(CLANG_LEVEL)/../../Makefile.config +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser support mc +USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ + clangRewrite.a clangParse.a clangSema.a clangAnalysis.a \ + clangAST.a clangASTMatchers.a clangLex.a clangBasic.a clangEdit.a + +include $(CLANG_LEVEL)/unittests/Makefile diff --git a/unittests/Basic/CMakeLists.txt b/unittests/Basic/CMakeLists.txt new file mode 100644 index 0000000..300dcd5 --- /dev/null +++ b/unittests/Basic/CMakeLists.txt @@ -0,0 +1,9 @@ +add_clang_unittest(BasicTests + FileManagerTest.cpp + SourceManagerTest.cpp + ) + +target_link_libraries(BasicTests + clangBasic + clangLex + ) diff --git a/unittests/Basic/SourceManagerTest.cpp b/unittests/Basic/SourceManagerTest.cpp index 429b58d..de3b723 100644 --- a/unittests/Basic/SourceManagerTest.cpp +++ b/unittests/Basic/SourceManagerTest.cpp @@ -107,6 +107,54 @@ TEST_F(SourceManagerTest, isBeforeInTranslationUnit) { EXPECT_TRUE(SourceMgr.isBeforeInTranslationUnit(idLoc, macroExpEndLoc)); } +TEST_F(SourceManagerTest, getColumnNumber) { + const char *Source = + "int x;\n" + "int y;"; + + MemoryBuffer *Buf = MemoryBuffer::getMemBuffer(Source); + FileID MainFileID = SourceMgr.createMainFileIDForMemBuffer(Buf); + + bool Invalid; + + Invalid = false; + EXPECT_EQ(1U, SourceMgr.getColumnNumber(MainFileID, 0, &Invalid)); + EXPECT_TRUE(!Invalid); + + Invalid = false; + EXPECT_EQ(5U, SourceMgr.getColumnNumber(MainFileID, 4, &Invalid)); + EXPECT_TRUE(!Invalid); + + Invalid = false; + EXPECT_EQ(1U, SourceMgr.getColumnNumber(MainFileID, 7, &Invalid)); + EXPECT_TRUE(!Invalid); + + Invalid = false; + EXPECT_EQ(5U, SourceMgr.getColumnNumber(MainFileID, 11, &Invalid)); + EXPECT_TRUE(!Invalid); + + Invalid = false; + EXPECT_EQ(7U, SourceMgr.getColumnNumber(MainFileID, strlen(Source), + &Invalid)); + EXPECT_TRUE(!Invalid); + + Invalid = false; + SourceMgr.getColumnNumber(MainFileID, strlen(Source)+1, &Invalid); + EXPECT_TRUE(Invalid); + + // Test invalid files + Invalid = false; + SourceMgr.getColumnNumber(FileID(), 0, &Invalid); + EXPECT_TRUE(Invalid); + + Invalid = false; + SourceMgr.getColumnNumber(FileID(), 1, &Invalid); + EXPECT_TRUE(Invalid); + + // Test with no invalid flag. + EXPECT_EQ(1U, SourceMgr.getColumnNumber(MainFileID, 0, NULL)); +} + #if defined(LLVM_ON_UNIX) TEST_F(SourceManagerTest, getMacroArgExpandedLocation) { diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 0b3eac9..989025a 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -1,73 +1,17 @@ -include(LLVMParseArguments) +add_custom_target(ClangUnitTests) +set_target_properties(ClangUnitTests PROPERTIES FOLDER "Clang tests") -# add_clang_unittest(test_dirname file1.cpp file2.cpp ... -# [USED_LIBS lib1 lib2] -# [LINK_COMPONENTS component1 component2]) +# add_clang_unittest(test_dirname file1.cpp file2.cpp) # # Will compile the list of files together and link against the clang -# libraries in the USED_LIBS list and the llvm-config components in -# the LINK_COMPONENTS list. Produces a binary named -# 'basename(test_dirname)Tests'. -function(add_clang_unittest) - PARSE_ARGUMENTS(CLANG_UNITTEST "USED_LIBS;LINK_COMPONENTS" "" ${ARGN}) - set(LLVM_LINK_COMPONENTS ${CLANG_UNITTEST_LINK_COMPONENTS}) - set(LLVM_USED_LIBS ${CLANG_UNITTEST_USED_LIBS}) - list(GET CLANG_UNITTEST_DEFAULT_ARGS 0 test_dirname) - list(REMOVE_AT CLANG_UNITTEST_DEFAULT_ARGS 0) - - string(REGEX MATCH "([^/]+)$" test_name ${test_dirname}) - if (CMAKE_BUILD_TYPE) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY - ${CLANG_BINARY_DIR}/unittests/${test_dirname}/${CMAKE_BUILD_TYPE}) - else() - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY - ${CLANG_BINARY_DIR}/unittests/${test_dirname}) - endif() - if( NOT LLVM_BUILD_TESTS ) - set(EXCLUDE_FROM_ALL ON) - endif() - add_clang_executable(${test_name}Tests ${CLANG_UNITTEST_DEFAULT_ARGS}) - add_dependencies(ClangUnitTests ${test_name}Tests) - set_target_properties(${test_name}Tests PROPERTIES FOLDER "Clang tests") +# Produces a binary named 'basename(test_dirname)'. +function(add_clang_unittest test_dirname) + add_unittest(ClangUnitTests ${test_dirname} ${ARGN}) endfunction() -add_custom_target(ClangUnitTests) -set_target_properties(ClangUnitTests PROPERTIES FOLDER "Clang tests") - -include_directories(${LLVM_MAIN_SRC_DIR}/utils/unittest/googletest/include) -add_definitions(-DGTEST_HAS_RTTI=0) -if( LLVM_COMPILER_IS_GCC_COMPATIBLE ) - llvm_replace_compiler_option(CMAKE_CXX_FLAGS "-frtti" "-fno-rtti") -elseif( MSVC ) - llvm_replace_compiler_option(CMAKE_CXX_FLAGS "/GR" "/GR-") -endif() - -if (NOT LLVM_ENABLE_THREADS) - add_definitions(-DGTEST_HAS_PTHREAD=0) -endif() - -if(SUPPORTS_NO_VARIADIC_MACROS_FLAG) - add_definitions("-Wno-variadic-macros") -endif() - -add_clang_unittest(Basic - Basic/FileManagerTest.cpp - Basic/SourceManagerTest.cpp - USED_LIBS gtest gtest_main clangLex - ) - -add_clang_unittest(Lex - Lex/LexerTest.cpp - USED_LIBS gtest gtest_main clangLex - ) - -add_clang_unittest(Frontend - Frontend/FrontendActionTest.cpp - USED_LIBS gtest gtest_main clangFrontend - ) - -add_clang_unittest(Tooling - Tooling/CompilationDatabaseTest.cpp - Tooling/ToolingTest.cpp - USED_LIBS gtest gtest_main clangTooling - ) +add_subdirectory(ASTMatchers) +add_subdirectory(AST) +add_subdirectory(Basic) +add_subdirectory(Lex) +add_subdirectory(Frontend) +add_subdirectory(Tooling) diff --git a/unittests/Frontend/CMakeLists.txt b/unittests/Frontend/CMakeLists.txt new file mode 100644 index 0000000..139cf42 --- /dev/null +++ b/unittests/Frontend/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + asmparser + support + mc + ) + +add_clang_unittest(FrontendTests + FrontendActionTest.cpp + ) +target_link_libraries(FrontendTests + clangFrontend + ) diff --git a/unittests/Frontend/FrontendActionTest.cpp b/unittests/Frontend/FrontendActionTest.cpp index 2d4befc..84a6545 100644 --- a/unittests/Frontend/FrontendActionTest.cpp +++ b/unittests/Frontend/FrontendActionTest.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/ASTContext.h" #include "clang/AST/ASTConsumer.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" diff --git a/unittests/Frontend/Makefile b/unittests/Frontend/Makefile index f3e6396..bfc3494 100644 --- a/unittests/Frontend/Makefile +++ b/unittests/Frontend/Makefile @@ -9,7 +9,8 @@ CLANG_LEVEL = ../.. TESTNAME = Frontend -LINK_COMPONENTS := support mc +include $(CLANG_LEVEL)/../../Makefile.config +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser support mc USEDLIBS = clangFrontendTool.a clangFrontend.a clangDriver.a \ clangSerialization.a clangCodeGen.a clangParse.a clangSema.a \ clangStaticAnalyzerCheckers.a clangStaticAnalyzerCore.a \ diff --git a/unittests/Lex/CMakeLists.txt b/unittests/Lex/CMakeLists.txt new file mode 100644 index 0000000..10c9361 --- /dev/null +++ b/unittests/Lex/CMakeLists.txt @@ -0,0 +1,8 @@ +add_clang_unittest(LexTests + LexerTest.cpp + PreprocessingRecordTest.cpp + ) + +target_link_libraries(LexTests + clangLex + ) diff --git a/unittests/Makefile b/unittests/Makefile index 05449d8..f74820b 100644 --- a/unittests/Makefile +++ b/unittests/Makefile @@ -14,7 +14,7 @@ ifndef CLANG_LEVEL IS_UNITTEST_LEVEL := 1 CLANG_LEVEL := .. -PARALLEL_DIRS = Basic Frontend Lex Tooling +PARALLEL_DIRS = ASTMatchers Basic AST Frontend Lex Tooling endif # CLANG_LEVEL diff --git a/unittests/Tooling/CMakeLists.txt b/unittests/Tooling/CMakeLists.txt new file mode 100644 index 0000000..4eaf339 --- /dev/null +++ b/unittests/Tooling/CMakeLists.txt @@ -0,0 +1,22 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + asmparser + support + mc + ) + +add_clang_unittest(ToolingTests + CommentHandlerTest.cpp + CompilationDatabaseTest.cpp + ToolingTest.cpp + RecursiveASTVisitorTest.cpp + RefactoringTest.cpp + RewriterTest.cpp + RefactoringCallbacksTest.cpp + ) + +target_link_libraries(ToolingTests + clangAST + clangTooling + clangRewrite + ) diff --git a/unittests/Tooling/CommentHandlerTest.cpp b/unittests/Tooling/CommentHandlerTest.cpp new file mode 100644 index 0000000..f0f7797 --- /dev/null +++ b/unittests/Tooling/CommentHandlerTest.cpp @@ -0,0 +1,221 @@ +//===- unittest/Tooling/CommentHandlerTest.cpp -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TestVisitor.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { + +struct Comment { + Comment(const std::string &Message, unsigned Line, unsigned Col) + : Message(Message), Line(Line), Col(Col) { } + + std::string Message; + unsigned Line, Col; +}; + +class CommentVerifier; +typedef std::vector<Comment> CommentList; + +class CommentHandlerVisitor : public TestVisitor<CommentHandlerVisitor>, + public CommentHandler { + typedef TestVisitor<CommentHandlerVisitor> base; + +public: + CommentHandlerVisitor() : base(), PP(0), Verified(false) { } + + ~CommentHandlerVisitor() { + EXPECT_TRUE(Verified) << "CommentVerifier not accessed"; + } + + virtual bool HandleComment(Preprocessor &PP, SourceRange Loc) { + assert(&PP == this->PP && "Preprocessor changed!"); + + SourceLocation Start = Loc.getBegin(); + SourceManager &SM = PP.getSourceManager(); + std::string C(SM.getCharacterData(Start), + SM.getCharacterData(Loc.getEnd())); + + bool Invalid; + unsigned CLine = SM.getSpellingLineNumber(Start, &Invalid); + EXPECT_TRUE(!Invalid) << "Invalid line number on comment " << C; + + unsigned CCol = SM.getSpellingColumnNumber(Start, &Invalid); + EXPECT_TRUE(!Invalid) << "Invalid column number on comment " << C; + + Comments.push_back(Comment(C, CLine, CCol)); + return false; + } + + CommentVerifier GetVerifier(); + +protected: + virtual ASTFrontendAction* CreateTestAction() { + return new CommentHandlerAction(this); + } + +private: + Preprocessor *PP; + CommentList Comments; + bool Verified; + + class CommentHandlerAction : public base::TestAction { + public: + CommentHandlerAction(CommentHandlerVisitor *Visitor) + : TestAction(Visitor) { } + + virtual bool BeginSourceFileAction(CompilerInstance &CI, + StringRef FileName) { + CommentHandlerVisitor *V = + static_cast<CommentHandlerVisitor*>(this->Visitor); + V->PP = &CI.getPreprocessor(); + V->PP->addCommentHandler(V); + return true; + } + + virtual void EndSourceFileAction() { + CommentHandlerVisitor *V = + static_cast<CommentHandlerVisitor*>(this->Visitor); + V->PP->removeCommentHandler(V); + } + }; +}; + +class CommentVerifier { + CommentList::const_iterator Current; + CommentList::const_iterator End; + Preprocessor *PP; + +public: + CommentVerifier(const CommentList &Comments, Preprocessor *PP) + : Current(Comments.begin()), End(Comments.end()), PP(PP) + { } + + ~CommentVerifier() { + if (Current != End) { + EXPECT_TRUE(Current == End) << "Unexpected comment \"" + << Current->Message << "\" at line " << Current->Line << ", column " + << Current->Col; + } + } + + void Match(const char *Message, unsigned Line, unsigned Col) { + EXPECT_TRUE(Current != End) << "Comment " << Message << " not found"; + if (Current == End) return; + + const Comment &C = *Current; + EXPECT_TRUE(C.Message == Message && C.Line == Line && C.Col == Col) + << "Expected comment \"" << Message + << "\" at line " << Line << ", column " << Col + << "\nActual comment \"" << C.Message + << "\" at line " << C.Line << ", column " << C.Col; + + ++Current; + } +}; + +CommentVerifier CommentHandlerVisitor::GetVerifier() { + Verified = true; + return CommentVerifier(Comments, PP); +} + + +TEST(CommentHandlerTest, BasicTest1) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver("class X {}; int main() { return 0; }")); + CommentVerifier Verifier = Visitor.GetVerifier(); +} + +TEST(CommentHandlerTest, BasicTest2) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "class X {}; int main() { /* comment */ return 0; }")); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("/* comment */", 1, 26); +} + +TEST(CommentHandlerTest, BasicTest3) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "class X {}; // comment 1\n" + "int main() {\n" + " // comment 2\n" + " return 0;\n" + "}")); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// comment 1", 1, 13); + Verifier.Match("// comment 2", 3, 3); +} + +TEST(CommentHandlerTest, IfBlock1) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "#if 0\n" + "// ignored comment\n" + "#endif\n" + "// visible comment\n")); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// visible comment", 4, 1); +} + +TEST(CommentHandlerTest, IfBlock2) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "#define TEST // visible_1\n" + "#ifndef TEST // visible_2\n" + " // ignored_3\n" + "# ifdef UNDEFINED // ignored_4\n" + "# endif // ignored_5\n" + "#elif defined(TEST) // visible_6\n" + "# if 1 // visible_7\n" + " // visible_8\n" + "# else // visible_9\n" + " // ignored_10\n" + "# ifndef TEST // ignored_11\n" + "# endif // ignored_12\n" + "# endif // visible_13\n" + "#endif // visible_14\n")); + + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// visible_1", 1, 21); + Verifier.Match("// visible_2", 2, 21); + Verifier.Match("// visible_6", 6, 21); + Verifier.Match("// visible_7", 7, 21); + Verifier.Match("// visible_8", 8, 21); + Verifier.Match("// visible_9", 9, 21); + Verifier.Match("// visible_13", 13, 21); + Verifier.Match("// visible_14", 14, 21); +} + +TEST(CommentHandlerTest, IfBlock3) { + const char *Source = + "/* commented out ...\n" + "#if 0\n" + "// enclosed\n" + "#endif */"; + + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver(Source)); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match(Source, 1, 1); +} + +TEST(CommentHandlerTest, PPDirectives) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "#warning Y // ignored_1\n" // #warning takes whole line as message + "#undef MACRO // visible_2\n" + "#line 1 // visible_3\n")); + + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// visible_2", 2, 14); + Verifier.Match("// visible_3", 3, 14); +} + +} // end namespace clang diff --git a/unittests/Tooling/CompilationDatabaseTest.cpp b/unittests/Tooling/CompilationDatabaseTest.cpp index 68d2896..591d48d 100644 --- a/unittests/Tooling/CompilationDatabaseTest.cpp +++ b/unittests/Tooling/CompilationDatabaseTest.cpp @@ -18,6 +18,55 @@ namespace clang { namespace tooling { +static void expectFailure(StringRef JSONDatabase, StringRef Explanation) { + std::string ErrorMessage; + EXPECT_EQ(NULL, JSONCompilationDatabase::loadFromBuffer(JSONDatabase, + ErrorMessage)) + << "Expected an error because of: " << Explanation; +} + +TEST(JSONCompilationDatabase, ErrsOnInvalidFormat) { + expectFailure("", "Empty database"); + expectFailure("{", "Invalid JSON"); + expectFailure("[[]]", "Array instead of object"); + expectFailure("[{\"a\":[]}]", "Array instead of value"); + expectFailure("[{\"a\":\"b\"}]", "Unknown key"); + expectFailure("[{[]:\"\"}]", "Incorrectly typed entry"); + expectFailure("[{}]", "Empty entry"); + expectFailure("[{\"directory\":\"\",\"command\":\"\"}]", "Missing file"); + expectFailure("[{\"directory\":\"\",\"file\":\"\"}]", "Missing command"); + expectFailure("[{\"command\":\"\",\"file\":\"\"}]", "Missing directory"); +} + +static std::vector<std::string> getAllFiles(StringRef JSONDatabase, + std::string &ErrorMessage) { + llvm::OwningPtr<CompilationDatabase> Database( + JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage)); + if (!Database) { + ADD_FAILURE() << ErrorMessage; + return std::vector<std::string>(); + } + return Database->getAllFiles(); +} + +TEST(JSONCompilationDatabase, GetAllFiles) { + std::string ErrorMessage; + EXPECT_EQ(std::vector<std::string>(), + getAllFiles("[]", ErrorMessage)) << ErrorMessage; + + std::vector<std::string> expected_files; + expected_files.push_back("file1"); + expected_files.push_back("file2"); + EXPECT_EQ(expected_files, getAllFiles( + "[{\"directory\":\"dir\"," + "\"command\":\"command\"," + "\"file\":\"file1\"}," + " {\"directory\":\"dir\"," + "\"command\":\"command\"," + "\"file\":\"file2\"}]", + ErrorMessage)) << ErrorMessage; +} + static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName, StringRef JSONDatabase, std::string &ErrorMessage) { @@ -235,6 +284,15 @@ TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) { EXPECT_EQ(ExpectedCommandLine, Result[0].CommandLine); } +TEST(FixedCompilationDatabase, GetAllFiles) { + std::vector<std::string> CommandLine; + CommandLine.push_back("one"); + CommandLine.push_back("two"); + FixedCompilationDatabase Database(".", CommandLine); + + EXPECT_EQ(0ul, Database.getAllFiles().size()); +} + TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) { int Argc = 0; llvm::OwningPtr<FixedCompilationDatabase> Database( diff --git a/unittests/Tooling/Makefile b/unittests/Tooling/Makefile index 0829da5..5d2224d 100644 --- a/unittests/Tooling/Makefile +++ b/unittests/Tooling/Makefile @@ -9,9 +9,10 @@ CLANG_LEVEL = ../.. TESTNAME = Tooling -LINK_COMPONENTS := support mc +include $(CLANG_LEVEL)/../../Makefile.config +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser support mc USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ - clangParse.a clangSema.a clangAnalysis.a clangEdit.a clangAST.a \ - clangLex.a clangBasic.a + clangParse.a clangRewrite.a clangSema.a clangAnalysis.a clangEdit.a \ + clangAST.a clangASTMatchers.a clangLex.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/unittests/Tooling/RecursiveASTVisitorTest.cpp b/unittests/Tooling/RecursiveASTVisitorTest.cpp new file mode 100644 index 0000000..f3ba646 --- /dev/null +++ b/unittests/Tooling/RecursiveASTVisitorTest.cpp @@ -0,0 +1,388 @@ +//===- unittest/Tooling/RecursiveASTVisitorTest.cpp -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TestVisitor.h" + +namespace clang { + +class TypeLocVisitor : public ExpectedLocationVisitor<TypeLocVisitor> { +public: + bool VisitTypeLoc(TypeLoc TypeLocation) { + Match(TypeLocation.getType().getAsString(), TypeLocation.getBeginLoc()); + return true; + } +}; + +class DeclRefExprVisitor : public ExpectedLocationVisitor<DeclRefExprVisitor> { +public: + bool VisitDeclRefExpr(DeclRefExpr *Reference) { + Match(Reference->getNameInfo().getAsString(), Reference->getLocation()); + return true; + } +}; + +class VarDeclVisitor : public ExpectedLocationVisitor<VarDeclVisitor> { +public: + bool VisitVarDecl(VarDecl *Variable) { + Match(Variable->getNameAsString(), Variable->getLocStart()); + return true; + } +}; + +class CXXMemberCallVisitor + : public ExpectedLocationVisitor<CXXMemberCallVisitor> { +public: + bool VisitCXXMemberCallExpr(CXXMemberCallExpr *Call) { + Match(Call->getMethodDecl()->getQualifiedNameAsString(), + Call->getLocStart()); + return true; + } +}; + +class NamedDeclVisitor + : public ExpectedLocationVisitor<NamedDeclVisitor> { +public: + bool VisitNamedDecl(NamedDecl *Decl) { + std::string NameWithTemplateArgs; + Decl->getNameForDiagnostic(NameWithTemplateArgs, + Decl->getASTContext().getPrintingPolicy(), + true); + Match(NameWithTemplateArgs, Decl->getLocation()); + return true; + } +}; + +class CXXOperatorCallExprTraverser + : public ExpectedLocationVisitor<CXXOperatorCallExprTraverser> { +public: + // Use Traverse, not Visit, to check that data recursion optimization isn't + // bypassing the call of this function. + bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr *CE) { + Match(getOperatorSpelling(CE->getOperator()), CE->getExprLoc()); + return ExpectedLocationVisitor<CXXOperatorCallExprTraverser>:: + TraverseCXXOperatorCallExpr(CE); + } +}; + +class ParenExprVisitor : public ExpectedLocationVisitor<ParenExprVisitor> { +public: + bool VisitParenExpr(ParenExpr *Parens) { + Match("", Parens->getExprLoc()); + return true; + } +}; + +class TemplateArgumentLocTraverser + : public ExpectedLocationVisitor<TemplateArgumentLocTraverser> { +public: + bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &ArgLoc) { + std::string ArgStr; + llvm::raw_string_ostream Stream(ArgStr); + const TemplateArgument &Arg = ArgLoc.getArgument(); + + Arg.print(Context->getPrintingPolicy(), Stream); + Match(Stream.str(), ArgLoc.getLocation()); + return ExpectedLocationVisitor<TemplateArgumentLocTraverser>:: + TraverseTemplateArgumentLoc(ArgLoc); + } +}; + +class CXXBoolLiteralExprVisitor + : public ExpectedLocationVisitor<CXXBoolLiteralExprVisitor> { +public: + bool VisitCXXBoolLiteralExpr(CXXBoolLiteralExpr *BE) { + if (BE->getValue()) + Match("true", BE->getLocation()); + else + Match("false", BE->getLocation()); + return true; + } +}; + +TEST(RecursiveASTVisitor, VisitsBaseClassDeclarations) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 1, 30); + EXPECT_TRUE(Visitor.runOver("class X {}; class Y : public X {};")); +} + +TEST(RecursiveASTVisitor, VisitsCXXBaseSpecifiersOfForwardDeclaredClass) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 3, 18); + EXPECT_TRUE(Visitor.runOver( + "class Y;\n" + "class X {};\n" + "class Y : public X {};")); +} + +TEST(RecursiveASTVisitor, VisitsCXXBaseSpecifiersWithIncompleteInnerClass) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 2, 18); + EXPECT_TRUE(Visitor.runOver( + "class X {};\n" + "class Y : public X { class Z; };")); +} + +TEST(RecursiveASTVisitor, VisitsCXXBaseSpecifiersOfSelfReferentialType) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("X<class Y>", 2, 18); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> class X {};\n" + "class Y : public X<Y> {};")); +} + +TEST(RecursiveASTVisitor, VisitsBaseClassTemplateArguments) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 2, 3); + EXPECT_TRUE(Visitor.runOver( + "void x(); template <void (*T)()> class X {};\nX<x> y;")); +} + +TEST(RecursiveASTVisitor, VisitsCXXForRangeStmtRange) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 2, 25); + Visitor.ExpectMatch("x", 2, 30); + EXPECT_TRUE(Visitor.runOver( + "int x[5];\n" + "void f() { for (int i : x) { x[0] = 1; } }")); +} + +TEST(RecursiveASTVisitor, VisitsCXXForRangeStmtLoopVariable) { + VarDeclVisitor Visitor; + Visitor.ExpectMatch("i", 2, 17); + EXPECT_TRUE(Visitor.runOver( + "int x[5];\n" + "void f() { for (int i : x) {} }")); +} + +TEST(RecursiveASTVisitor, VisitsCallExpr) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 1, 22); + EXPECT_TRUE(Visitor.runOver( + "void x(); void y() { x(); }")); +} + +TEST(RecursiveASTVisitor, VisitsCallInTemplateInstantiation) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("Y::x", 3, 3); + EXPECT_TRUE(Visitor.runOver( + "struct Y { void x(); };\n" + "template<typename T> void y(T t) {\n" + " t.x();\n" + "}\n" + "void foo() { y<Y>(Y()); }")); +} + +TEST(RecursiveASTVisitor, VisitsCallInNestedFunctionTemplateInstantiation) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("Y::x", 4, 5); + EXPECT_TRUE(Visitor.runOver( + "struct Y { void x(); };\n" + "template<typename T> struct Z {\n" + " template<typename U> static void f() {\n" + " T().x();\n" + " }\n" + "};\n" + "void foo() { Z<Y>::f<int>(); }")); +} + +TEST(RecursiveASTVisitor, VisitsCallInNestedClassTemplateInstantiation) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("A::x", 5, 7); + EXPECT_TRUE(Visitor.runOver( + "template <typename T1> struct X {\n" + " template <typename T2> struct Y {\n" + " void f() {\n" + " T2 y;\n" + " y.x();\n" + " }\n" + " };\n" + "};\n" + "struct A { void x(); };\n" + "int main() {\n" + " (new X<A>::Y<A>())->f();\n" + "}")); +} + +/* FIXME: According to Richard Smith this is a bug in the AST. +TEST(RecursiveASTVisitor, VisitsBaseClassTemplateArgumentsInInstantiation) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 3, 43); + EXPECT_TRUE(Visitor.runOver( + "template <typename T> void x();\n" + "template <void (*T)()> class X {};\n" + "template <typename T> class Y : public X< x<T> > {};\n" + "Y<int> y;")); +} +*/ + +TEST(RecursiveASTVisitor, VisitsCallInPartialTemplateSpecialization) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("A::x", 6, 20); + EXPECT_TRUE(Visitor.runOver( + "template <typename T1> struct X {\n" + " template <typename T2, bool B> struct Y { void g(); };\n" + "};\n" + "template <typename T1> template <typename T2>\n" + "struct X<T1>::Y<T2, true> {\n" + " void f() { T2 y; y.x(); }\n" + "};\n" + "struct A { void x(); };\n" + "int main() {\n" + " (new X<A>::Y<A, true>())->f();\n" + "}\n")); +} + +TEST(RecursiveASTVisitor, VisitsExplicitTemplateSpecialization) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("A::f", 4, 5); + EXPECT_TRUE(Visitor.runOver( + "struct A {\n" + " void f() const {}\n" + " template<class T> void g(const T& t) const {\n" + " t.f();\n" + " }\n" + "};\n" + "template void A::g(const A& a) const;\n")); +} + +TEST(RecursiveASTVisitor, VisitsPartialTemplateSpecialization) { + // From cfe-commits/Week-of-Mon-20100830/033998.html + // Contrary to the approach suggested in that email, we visit all + // specializations when we visit the primary template. Visiting them when we + // visit the associated specialization is problematic for specializations of + // template members of class templates. + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<bool>", 1, 26); + Visitor.ExpectMatch("A<char *>", 2, 26); + EXPECT_TRUE(Visitor.runOver( + "template <class T> class A {};\n" + "template <class T> class A<T*> {};\n" + "A<bool> ab;\n" + "A<char*> acp;\n")); +} + +TEST(RecursiveASTVisitor, VisitsUndefinedClassTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>", 1, 29); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> struct A;\n" + "A<int> *p;\n")); +} + +TEST(RecursiveASTVisitor, VisitsNestedUndefinedClassTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>::B<char>", 2, 31); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> struct A {\n" + " template<typename U> struct B;\n" + "};\n" + "A<int>::B<char> *p;\n")); +} + +TEST(RecursiveASTVisitor, VisitsUndefinedFunctionTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>", 1, 26); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> int A();\n" + "int k = A<int>();\n")); +} + +TEST(RecursiveASTVisitor, VisitsNestedUndefinedFunctionTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>::B<char>", 2, 35); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> struct A {\n" + " template<typename U> static int B();\n" + "};\n" + "int k = A<int>::B<char>();\n")); +} + +TEST(RecursiveASTVisitor, NoRecursionInSelfFriend) { + // From cfe-commits/Week-of-Mon-20100830/033977.html + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("vector_iterator<int>", 2, 7); + EXPECT_TRUE(Visitor.runOver( + "template<typename Container>\n" + "class vector_iterator {\n" + " template <typename C> friend class vector_iterator;\n" + "};\n" + "vector_iterator<int> it_int;\n")); +} + +TEST(RecursiveASTVisitor, TraversesOverloadedOperator) { + CXXOperatorCallExprTraverser Visitor; + Visitor.ExpectMatch("()", 4, 9); + EXPECT_TRUE(Visitor.runOver( + "struct A {\n" + " int operator()();\n" + "} a;\n" + "int k = a();\n")); +} + +TEST(RecursiveASTVisitor, VisitsParensDuringDataRecursion) { + ParenExprVisitor Visitor; + Visitor.ExpectMatch("", 1, 9); + EXPECT_TRUE(Visitor.runOver("int k = (4) + 9;\n")); +} + +TEST(RecursiveASTVisitor, VisitsClassTemplateNonTypeParmDefaultArgument) { + CXXBoolLiteralExprVisitor Visitor; + Visitor.ExpectMatch("true", 2, 19); + EXPECT_TRUE(Visitor.runOver( + "template<bool B> class X;\n" + "template<bool B = true> class Y;\n" + "template<bool B> class Y {};\n")); +} + +TEST(RecursiveASTVisitor, VisitsClassTemplateTypeParmDefaultArgument) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 2, 23); + EXPECT_TRUE(Visitor.runOver( + "class X;\n" + "template<typename T = X> class Y;\n" + "template<typename T> class Y {};\n")); +} + +TEST(RecursiveASTVisitor, VisitsClassTemplateTemplateParmDefaultArgument) { + TemplateArgumentLocTraverser Visitor; + Visitor.ExpectMatch("X", 2, 40); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> class X;\n" + "template<template <typename> class T = X> class Y;\n" + "template<template <typename> class T> class Y {};\n")); +} + +// A visitor that visits implicit declarations and matches constructors. +class ImplicitCtorVisitor + : public ExpectedLocationVisitor<ImplicitCtorVisitor> { +public: + bool shouldVisitImplicitCode() const { return true; } + + bool VisitCXXConstructorDecl(CXXConstructorDecl* Ctor) { + if (Ctor->isImplicit()) { // Was not written in source code + if (const CXXRecordDecl* Class = Ctor->getParent()) { + Match(Class->getName(), Ctor->getLocation()); + } + } + return true; + } +}; + +TEST(RecursiveASTVisitor, VisitsImplicitCopyConstructors) { + ImplicitCtorVisitor Visitor; + Visitor.ExpectMatch("Simple", 2, 8); + // Note: Clang lazily instantiates implicit declarations, so we need + // to use them in order to force them to appear in the AST. + EXPECT_TRUE(Visitor.runOver( + "struct WithCtor { WithCtor(); }; \n" + "struct Simple { Simple(); WithCtor w; }; \n" + "int main() { Simple s; Simple t(s); }\n")); +} + +} // end namespace clang diff --git a/unittests/Tooling/RefactoringCallbacksTest.cpp b/unittests/Tooling/RefactoringCallbacksTest.cpp new file mode 100644 index 0000000..00eb193 --- /dev/null +++ b/unittests/Tooling/RefactoringCallbacksTest.cpp @@ -0,0 +1,100 @@ +//===- unittest/ASTMatchers/RefactoringCallbacksTest.cpp ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/RefactoringCallbacks.h" +#include "gtest/gtest.h" +#include "RewriterTestContext.h" + +namespace clang { +namespace tooling { + +using namespace ast_matchers; + +template <typename T> +void expectRewritten(const std::string &Code, + const std::string &Expected, + const T &AMatcher, + RefactoringCallback &Callback) { + MatchFinder Finder; + Finder.addMatcher(AMatcher, &Callback); + OwningPtr<tooling::FrontendActionFactory> Factory( + tooling::newFrontendActionFactory(&Finder)); + ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), Code)) + << "Parsing error in \"" << Code << "\""; + RewriterTestContext Context; + FileID ID = Context.createInMemoryFile("input.cc", Code); + EXPECT_TRUE(tooling::applyAllReplacements(Callback.getReplacements(), + Context.Rewrite)); + EXPECT_EQ(Expected, Context.getRewrittenText(ID)); +} + +TEST(RefactoringCallbacksTest, ReplacesStmtsWithString) { + std::string Code = "void f() { int i = 1; }"; + std::string Expected = "void f() { ; }"; + ReplaceStmtWithText Callback("id", ";"); + expectRewritten(Code, Expected, id("id", declarationStatement()), Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesStmtsInCalledMacros) { + std::string Code = "#define A void f() { int i = 1; }\nA"; + std::string Expected = "#define A void f() { ; }\nA"; + ReplaceStmtWithText Callback("id", ";"); + expectRewritten(Code, Expected, id("id", declarationStatement()), Callback); +} + +TEST(RefactoringCallbacksTest, IgnoresStmtsInUncalledMacros) { + std::string Code = "#define A void f() { int i = 1; }"; + std::string Expected = "#define A void f() { int i = 1; }"; + ReplaceStmtWithText Callback("id", ";"); + expectRewritten(Code, Expected, id("id", declarationStatement()), Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesInteger) { + std::string Code = "void f() { int i = 1; }"; + std::string Expected = "void f() { int i = 2; }"; + ReplaceStmtWithText Callback("id", "2"); + expectRewritten(Code, Expected, id("id", expression(integerLiteral())), + Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesStmtWithStmt) { + std::string Code = "void f() { int i = false ? 1 : i * 2; }"; + std::string Expected = "void f() { int i = i * 2; }"; + ReplaceStmtWithStmt Callback("always-false", "should-be"); + expectRewritten(Code, Expected, + id("always-false", conditionalOperator( + hasCondition(boolLiteral(equals(false))), + hasFalseExpression(id("should-be", expression())))), + Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesIfStmt) { + std::string Code = "bool a; void f() { if (a) f(); else a = true; }"; + std::string Expected = "bool a; void f() { f(); }"; + ReplaceIfStmtWithItsBody Callback("id", true); + expectRewritten(Code, Expected, + id("id", ifStmt( + hasCondition(implicitCast(hasSourceExpression( + declarationReference(to(variable(hasName("a"))))))))), + Callback); +} + +TEST(RefactoringCallbacksTest, RemovesEntireIfOnEmptyElse) { + std::string Code = "void f() { if (false) int i = 0; }"; + std::string Expected = "void f() { }"; + ReplaceIfStmtWithItsBody Callback("id", false); + expectRewritten(Code, Expected, + id("id", ifStmt(hasCondition(boolLiteral(equals(false))))), + Callback); +} + +} // end namespace ast_matchers +} // end namespace clang diff --git a/unittests/Tooling/RefactoringTest.cpp b/unittests/Tooling/RefactoringTest.cpp new file mode 100644 index 0000000..8d96955 --- /dev/null +++ b/unittests/Tooling/RefactoringTest.cpp @@ -0,0 +1,305 @@ +//===- unittest/Tooling/RefactoringTest.cpp - Refactoring unit tests ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RewriterTestContext.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclGroup.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/DiagnosticOptions.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Rewriter.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Path.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tooling { + +class ReplacementTest : public ::testing::Test { + protected: + Replacement createReplacement(SourceLocation Start, unsigned Length, + llvm::StringRef ReplacementText) { + return Replacement(Context.Sources, Start, Length, ReplacementText); + } + + RewriterTestContext Context; +}; + +TEST_F(ReplacementTest, CanDeleteAllText) { + FileID ID = Context.createInMemoryFile("input.cpp", "text"); + SourceLocation Location = Context.getLocation(ID, 1, 1); + Replacement Replace(createReplacement(Location, 4, "")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanDeleteAllTextInTextWithNewlines) { + FileID ID = Context.createInMemoryFile("input.cpp", "line1\nline2\nline3"); + SourceLocation Location = Context.getLocation(ID, 1, 1); + Replacement Replace(createReplacement(Location, 17, "")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanAddText) { + FileID ID = Context.createInMemoryFile("input.cpp", ""); + SourceLocation Location = Context.getLocation(ID, 1, 1); + Replacement Replace(createReplacement(Location, 0, "result")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("result", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanReplaceTextAtPosition) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + SourceLocation Location = Context.getLocation(ID, 2, 3); + Replacement Replace(createReplacement(Location, 12, "x")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("line1\nlixne4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanReplaceTextAtPositionMultipleTimes) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + SourceLocation Location1 = Context.getLocation(ID, 2, 3); + Replacement Replace1(createReplacement(Location1, 12, "x\ny\n")); + EXPECT_TRUE(Replace1.apply(Context.Rewrite)); + EXPECT_EQ("line1\nlix\ny\nne4", Context.getRewrittenText(ID)); + + // Since the original source has not been modified, the (4, 4) points to the + // 'e' in the original content. + SourceLocation Location2 = Context.getLocation(ID, 4, 4); + Replacement Replace2(createReplacement(Location2, 1, "f")); + EXPECT_TRUE(Replace2.apply(Context.Rewrite)); + EXPECT_EQ("line1\nlix\ny\nnf4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, ApplyFailsForNonExistentLocation) { + Replacement Replace("nonexistent-file.cpp", 0, 1, ""); + EXPECT_FALSE(Replace.apply(Context.Rewrite)); +} + +TEST_F(ReplacementTest, CanRetrivePath) { + Replacement Replace("/path/to/file.cpp", 0, 1, ""); + EXPECT_EQ("/path/to/file.cpp", Replace.getFilePath()); +} + +TEST_F(ReplacementTest, ReturnsInvalidPath) { + Replacement Replace1(Context.Sources, SourceLocation(), 0, ""); + EXPECT_TRUE(Replace1.getFilePath().empty()); + + Replacement Replace2; + EXPECT_TRUE(Replace2.getFilePath().empty()); +} + +TEST_F(ReplacementTest, CanApplyReplacements) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 3, 1), + 5, "other")); + EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_EQ("line1\nreplaced\nother\nline4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, SkipsDuplicateReplacements) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_EQ("line1\nreplaced\nline3\nline4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, ApplyAllFailsIfOneApplyFails) { + // This test depends on the value of the file name of an invalid source + // location being in the range ]a, z[. + FileID IDa = Context.createInMemoryFile("a.cpp", "text"); + FileID IDz = Context.createInMemoryFile("z.cpp", "text"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDa, 1, 1), + 4, "a")); + Replaces.insert(Replacement(Context.Sources, SourceLocation(), + 5, "2")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDz, 1, 1), + 4, "z")); + EXPECT_FALSE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_EQ("a", Context.getRewrittenText(IDa)); + EXPECT_EQ("z", Context.getRewrittenText(IDz)); +} + +class FlushRewrittenFilesTest : public ::testing::Test { + public: + FlushRewrittenFilesTest() { + std::string ErrorInfo; + TemporaryDirectory = llvm::sys::Path::GetTemporaryDirectory(&ErrorInfo); + assert(ErrorInfo.empty()); + } + + ~FlushRewrittenFilesTest() { + std::string ErrorInfo; + TemporaryDirectory.eraseFromDisk(true, &ErrorInfo); + assert(ErrorInfo.empty()); + } + + FileID createFile(llvm::StringRef Name, llvm::StringRef Content) { + llvm::SmallString<1024> Path(TemporaryDirectory.str()); + llvm::sys::path::append(Path, Name); + std::string ErrorInfo; + llvm::raw_fd_ostream OutStream(Path.c_str(), + ErrorInfo, llvm::raw_fd_ostream::F_Binary); + assert(ErrorInfo.empty()); + OutStream << Content; + OutStream.close(); + const FileEntry *File = Context.Files.getFile(Path); + assert(File != NULL); + return Context.Sources.createFileID(File, SourceLocation(), SrcMgr::C_User); + } + + std::string getFileContentFromDisk(llvm::StringRef Name) { + llvm::SmallString<1024> Path(TemporaryDirectory.str()); + llvm::sys::path::append(Path, Name); + // We need to read directly from the FileManager without relaying through + // a FileEntry, as otherwise we'd read through an already opened file + // descriptor, which might not see the changes made. + // FIXME: Figure out whether there is a way to get the SourceManger to + // reopen the file. + return Context.Files.getBufferForFile(Path, NULL)->getBuffer(); + } + + llvm::sys::Path TemporaryDirectory; + RewriterTestContext Context; +}; + +TEST_F(FlushRewrittenFilesTest, StoresChangesOnDisk) { + FileID ID = createFile("input.cpp", "line1\nline2\nline3\nline4"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_FALSE(Context.Rewrite.overwriteChangedFiles()); + EXPECT_EQ("line1\nreplaced\nline3\nline4", + getFileContentFromDisk("input.cpp")); +} + +namespace { +template <typename T> +class TestVisitor : public clang::RecursiveASTVisitor<T> { +public: + bool runOver(StringRef Code) { + return runToolOnCode(new TestAction(this), Code); + } + +protected: + clang::SourceManager *SM; + +private: + class FindConsumer : public clang::ASTConsumer { + public: + FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual void HandleTranslationUnit(clang::ASTContext &Context) { + Visitor->TraverseDecl(Context.getTranslationUnitDecl()); + } + + private: + TestVisitor *Visitor; + }; + + class TestAction : public clang::ASTFrontendAction { + public: + TestAction(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual clang::ASTConsumer* CreateASTConsumer( + clang::CompilerInstance& compiler, llvm::StringRef dummy) { + Visitor->SM = &compiler.getSourceManager(); + /// TestConsumer will be deleted by the framework calling us. + return new FindConsumer(Visitor); + } + + private: + TestVisitor *Visitor; + }; +}; +} // end namespace + +void expectReplacementAt(const Replacement &Replace, + StringRef File, unsigned Offset, unsigned Length) { + ASSERT_TRUE(Replace.isApplicable()); + EXPECT_EQ(File, Replace.getFilePath()); + EXPECT_EQ(Offset, Replace.getOffset()); + EXPECT_EQ(Length, Replace.getLength()); +} + +class ClassDeclXVisitor : public TestVisitor<ClassDeclXVisitor> { +public: + bool VisitCXXRecordDecl(CXXRecordDecl *Record) { + if (Record->getName() == "X") { + Replace = Replacement(*SM, Record, ""); + } + return true; + } + Replacement Replace; +}; + +TEST(Replacement, CanBeConstructedFromNode) { + ClassDeclXVisitor ClassDeclX; + EXPECT_TRUE(ClassDeclX.runOver(" class X;")); + expectReplacementAt(ClassDeclX.Replace, "input.cc", 5, 7); +} + +TEST(Replacement, ReplacesAtSpellingLocation) { + ClassDeclXVisitor ClassDeclX; + EXPECT_TRUE(ClassDeclX.runOver("#define A(Y) Y\nA(class X);")); + expectReplacementAt(ClassDeclX.Replace, "input.cc", 17, 7); +} + +class CallToFVisitor : public TestVisitor<CallToFVisitor> { +public: + bool VisitCallExpr(CallExpr *Call) { + if (Call->getDirectCallee()->getName() == "F") { + Replace = Replacement(*SM, Call, ""); + } + return true; + } + Replacement Replace; +}; + +TEST(Replacement, FunctionCall) { + CallToFVisitor CallToF; + EXPECT_TRUE(CallToF.runOver("void F(); void G() { F(); }")); + expectReplacementAt(CallToF.Replace, "input.cc", 21, 3); +} + +TEST(Replacement, TemplatedFunctionCall) { + CallToFVisitor CallToF; + EXPECT_TRUE(CallToF.runOver( + "template <typename T> void F(); void G() { F<int>(); }")); + expectReplacementAt(CallToF.Replace, "input.cc", 43, 8); +} + +} // end namespace tooling +} // end namespace clang diff --git a/unittests/Tooling/RewriterTest.cpp b/unittests/Tooling/RewriterTest.cpp new file mode 100644 index 0000000..c53e50a --- /dev/null +++ b/unittests/Tooling/RewriterTest.cpp @@ -0,0 +1,37 @@ +//===- unittest/Tooling/RewriterTest.cpp ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RewriterTestContext.h" +#include "gtest/gtest.h" + +namespace clang { + +TEST(Rewriter, OverwritesChangedFiles) { + RewriterTestContext Context; + FileID ID = Context.createOnDiskFile("t.cpp", "line1\nline2\nline3\nline4"); + Context.Rewrite.ReplaceText(Context.getLocation(ID, 2, 1), 5, "replaced"); + EXPECT_FALSE(Context.Rewrite.overwriteChangedFiles()); + EXPECT_EQ("line1\nreplaced\nline3\nline4", + Context.getFileContentFromDisk("t.cpp")); +} + +TEST(Rewriter, ContinuesOverwritingFilesOnError) { + RewriterTestContext Context; + FileID FailingID = Context.createInMemoryFile("invalid/failing.cpp", "test"); + Context.Rewrite.ReplaceText(Context.getLocation(FailingID, 1, 2), 1, "other"); + FileID WorkingID = Context.createOnDiskFile( + "working.cpp", "line1\nline2\nline3\nline4"); + Context.Rewrite.ReplaceText(Context.getLocation(WorkingID, 2, 1), 5, + "replaced"); + EXPECT_TRUE(Context.Rewrite.overwriteChangedFiles()); + EXPECT_EQ("line1\nreplaced\nline3\nline4", + Context.getFileContentFromDisk("working.cpp")); +} + +} // end namespace clang diff --git a/unittests/Tooling/RewriterTestContext.h b/unittests/Tooling/RewriterTestContext.h new file mode 100644 index 0000000..f68be6b --- /dev/null +++ b/unittests/Tooling/RewriterTestContext.h @@ -0,0 +1,125 @@ +//===--- RewriterTestContext.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a utility class for Rewriter related tests. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_REWRITER_TEST_CONTEXT_H +#define LLVM_CLANG_REWRITER_TEST_CONTEXT_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/DiagnosticOptions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Rewriter.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { + +/// \brief A class that sets up a ready to use Rewriter. +/// +/// Useful in unit tests that need a Rewriter. Creates all dependencies +/// of a Rewriter with default values for testing and provides convenience +/// methods, which help with writing tests that change files. +class RewriterTestContext { + public: + RewriterTestContext() + : Diagnostics(llvm::IntrusiveRefCntPtr<DiagnosticIDs>()), + DiagnosticPrinter(llvm::outs(), DiagnosticOptions()), + Files((FileSystemOptions())), + Sources(Diagnostics, Files), + Rewrite(Sources, Options) { + Diagnostics.setClient(&DiagnosticPrinter, false); + } + + ~RewriterTestContext() { + if (!TemporaryDirectory.empty()) { + uint32_t RemovedCount = 0; + llvm::sys::fs::remove_all(TemporaryDirectory.str(), RemovedCount); + } + } + + FileID createInMemoryFile(StringRef Name, StringRef Content) { + const llvm::MemoryBuffer *Source = + llvm::MemoryBuffer::getMemBuffer(Content); + const FileEntry *Entry = + Files.getVirtualFile(Name, Source->getBufferSize(), 0); + Sources.overrideFileContents(Entry, Source, true); + assert(Entry != NULL); + return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User); + } + + FileID createOnDiskFile(StringRef Name, StringRef Content) { + if (TemporaryDirectory.empty()) { + int FD; + bool error = + llvm::sys::fs::unique_file("rewriter-test-%%-%%-%%-%%/anchor", FD, + TemporaryDirectory); + assert(!error); (void)error; + llvm::raw_fd_ostream Closer(FD, /*shouldClose=*/true); + TemporaryDirectory = llvm::sys::path::parent_path(TemporaryDirectory); + } + llvm::SmallString<1024> Path(TemporaryDirectory); + llvm::sys::path::append(Path, Name); + std::string ErrorInfo; + llvm::raw_fd_ostream OutStream(Path.c_str(), + ErrorInfo, llvm::raw_fd_ostream::F_Binary); + assert(ErrorInfo.empty()); + OutStream << Content; + OutStream.close(); + const FileEntry *File = Files.getFile(Path); + assert(File != NULL); + return Sources.createFileID(File, SourceLocation(), SrcMgr::C_User); + } + + SourceLocation getLocation(FileID ID, unsigned Line, unsigned Column) { + SourceLocation Result = Sources.translateFileLineCol( + Sources.getFileEntryForID(ID), Line, Column); + assert(Result.isValid()); + return Result; + } + + std::string getRewrittenText(FileID ID) { + std::string Result; + llvm::raw_string_ostream OS(Result); + Rewrite.getEditBuffer(ID).write(OS); + OS.flush(); + return Result; + } + + std::string getFileContentFromDisk(StringRef Name) { + llvm::SmallString<1024> Path(TemporaryDirectory.str()); + llvm::sys::path::append(Path, Name); + // We need to read directly from the FileManager without relaying through + // a FileEntry, as otherwise we'd read through an already opened file + // descriptor, which might not see the changes made. + // FIXME: Figure out whether there is a way to get the SourceManger to + // reopen the file. + return Files.getBufferForFile(Path, NULL)->getBuffer(); + } + + DiagnosticsEngine Diagnostics; + TextDiagnosticPrinter DiagnosticPrinter; + FileManager Files; + SourceManager Sources; + LangOptions Options; + Rewriter Rewrite; + + // Will be set once on disk files are generated. + SmallString<128> TemporaryDirectory; +}; + +} // end namespace clang + +#endif diff --git a/unittests/Tooling/TestVisitor.h b/unittests/Tooling/TestVisitor.h new file mode 100644 index 0000000..d439d81 --- /dev/null +++ b/unittests/Tooling/TestVisitor.h @@ -0,0 +1,144 @@ +//===--- TestVisitor.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a utility class for RecursiveASTVisitor related tests. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TEST_VISITOR_H +#define LLVM_CLANG_TEST_VISITOR_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { + +/// \brief Base class for simple RecursiveASTVisitor based tests. +/// +/// This is a drop-in replacement for RecursiveASTVisitor itself, with the +/// additional capability of running it over a snippet of code. +/// +/// Visits template instantiations by default. +template <typename T> +class TestVisitor : public RecursiveASTVisitor<T> { +public: + TestVisitor() { } + + virtual ~TestVisitor() { } + + /// \brief Runs the current AST visitor over the given code. + bool runOver(StringRef Code) { + return tooling::runToolOnCode(CreateTestAction(), Code); + } + + bool shouldVisitTemplateInstantiations() const { + return true; + } + +protected: + virtual ASTFrontendAction* CreateTestAction() { + return new TestAction(this); + } + + class FindConsumer : public ASTConsumer { + public: + FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual void HandleTranslationUnit(clang::ASTContext &Context) { + Visitor->Context = &Context; + Visitor->TraverseDecl(Context.getTranslationUnitDecl()); + } + + private: + TestVisitor *Visitor; + }; + + class TestAction : public ASTFrontendAction { + public: + TestAction(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual clang::ASTConsumer* CreateASTConsumer( + CompilerInstance&, llvm::StringRef dummy) { + /// TestConsumer will be deleted by the framework calling us. + return new FindConsumer(Visitor); + } + + protected: + TestVisitor *Visitor; + }; + + ASTContext *Context; +}; + + +/// \brief A RecursiveASTVisitor for testing the RecursiveASTVisitor itself. +/// +/// Allows simple creation of test visitors running matches on only a small +/// subset of the Visit* methods. +template <typename T, template <typename> class Visitor = TestVisitor> +class ExpectedLocationVisitor : public Visitor<T> { +public: + ExpectedLocationVisitor() + : ExpectedLine(0), ExpectedColumn(0), Found(false) {} + + virtual ~ExpectedLocationVisitor() { + EXPECT_TRUE(Found) + << "Expected \"" << ExpectedMatch << "\" at " << ExpectedLine + << ":" << ExpectedColumn << PartialMatches; + } + + /// \brief Expect 'Match' to occur at the given 'Line' and 'Column'. + void ExpectMatch(Twine Match, unsigned Line, unsigned Column) { + ExpectedMatch = Match.str(); + ExpectedLine = Line; + ExpectedColumn = Column; + } + +protected: + /// \brief Convenience method to simplify writing test visitors. + /// + /// Sets 'Found' to true if 'Name' and 'Location' match the expected + /// values. If only a partial match is found, record the information + /// to produce nice error output when a test fails. + /// + /// Implementations are required to call this with appropriate values + /// for 'Name' during visitation. + void Match(StringRef Name, SourceLocation Location) { + FullSourceLoc FullLocation = this->Context->getFullLoc(Location); + if (Name == ExpectedMatch && + FullLocation.isValid() && + FullLocation.getSpellingLineNumber() == ExpectedLine && + FullLocation.getSpellingColumnNumber() == ExpectedColumn) { + EXPECT_TRUE(!Found); + Found = true; + } else if (Name == ExpectedMatch || + (FullLocation.isValid() && + FullLocation.getSpellingLineNumber() == ExpectedLine && + FullLocation.getSpellingColumnNumber() == ExpectedColumn)) { + // If we did not match, record information about partial matches. + llvm::raw_string_ostream Stream(PartialMatches); + Stream << ", partial match: \"" << Name << "\" at "; + Location.print(Stream, this->Context->getSourceManager()); + } + } + + std::string ExpectedMatch; + unsigned ExpectedLine; + unsigned ExpectedColumn; + std::string PartialMatches; + bool Found; +}; +} + +#endif /* LLVM_CLANG_TEST_VISITOR_H */ diff --git a/unittests/Tooling/ToolingTest.cpp b/unittests/Tooling/ToolingTest.cpp index c7b2210..fb3af26 100644 --- a/unittests/Tooling/ToolingTest.cpp +++ b/unittests/Tooling/ToolingTest.cpp @@ -15,6 +15,7 @@ #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Tooling.h" #include "gtest/gtest.h" +#include <string> namespace clang { namespace tooling { @@ -52,11 +53,16 @@ class FindTopLevelDeclConsumer : public clang::ASTConsumer { }; } // end namespace -TEST(runToolOnCode, FindsTopLevelDeclOnEmptyCode) { +TEST(runToolOnCode, FindsNoTopLevelDeclOnEmptyCode) { bool FoundTopLevelDecl = false; EXPECT_TRUE(runToolOnCode( new TestAction(new FindTopLevelDeclConsumer(&FoundTopLevelDecl)), "")); +#if !defined(_MSC_VER) + EXPECT_FALSE(FoundTopLevelDecl); +#else + // FIXME: LangOpts.MicrosoftExt appends "class type_info;" EXPECT_TRUE(FoundTopLevelDecl); +#endif } namespace { @@ -98,7 +104,9 @@ TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) { } struct IndependentFrontendActionCreator { - FrontendAction *newFrontendAction() { return new SyntaxOnlyAction; } + ASTConsumer *newASTConsumer() { + return new FindTopLevelDeclConsumer(NULL); + } }; TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) { @@ -109,5 +117,18 @@ TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) { EXPECT_TRUE(Action.get() != NULL); } +TEST(ToolInvocation, TestMapVirtualFile) { + clang::FileManager Files((clang::FileSystemOptions())); + std::vector<std::string> Args; + Args.push_back("tool-executable"); + Args.push_back("-Idef"); + Args.push_back("-fsyntax-only"); + Args.push_back("test.cpp"); + clang::tooling::ToolInvocation Invocation(Args, new SyntaxOnlyAction, &Files); + Invocation.mapVirtualFile("test.cpp", "#include <abc>\n"); + Invocation.mapVirtualFile("def/abc", "\n"); + EXPECT_TRUE(Invocation.run()); +} + } // end namespace tooling } // end namespace clang |