Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -1,5 +1,8 @@ +check_library_exists(edit el_init "" HAVE_LIBEDIT) + add_subdirectory(clang-apply-replacements) add_subdirectory(clang-modernize) +add_subdirectory(clang-query) add_subdirectory(clang-tidy) add_subdirectory(modularize) add_subdirectory(pp-trace) Index: Makefile =================================================================== --- Makefile +++ Makefile @@ -12,7 +12,8 @@ include $(CLANG_LEVEL)/../../Makefile.config PARALLEL_DIRS := remove-cstr-calls tool-template modularize pp-trace -DIRS := clang-apply-replacements clang-modernize clang-tidy unittests +DIRS := clang-apply-replacements clang-modernize clang-tidy clang-query \ + unittests include $(CLANG_LEVEL)/Makefile Index: clang-query/CMakeLists.txt =================================================================== --- /dev/null +++ clang-query/CMakeLists.txt @@ -0,0 +1,13 @@ +add_clang_library(clangQuery + QueryEngine.cpp + QueryParser.cpp + ) +target_link_libraries(clangQuery + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + ) + +add_subdirectory(tool) Index: clang-query/Makefile =================================================================== --- clang-query/Makefile +++ clang-query/Makefile @@ -1,4 +1,4 @@ -##===- tools/extra/test/Unit/Makefile ----------------------*- Makefile -*-===## +##===- tools/extra/clang-query/Makefile --------------------*- Makefile -*-===## # # The LLVM Compiler Infrastructure # @@ -8,8 +8,7 @@ ##===----------------------------------------------------------------------===## CLANG_LEVEL := ../../.. +LIBRARYNAME := clangQuery include $(CLANG_LEVEL)/../../Makefile.config -PARALLEL_DIRS := clang-apply-replacements clang-modernize clang-tidy - include $(CLANG_LEVEL)/Makefile Index: clang-query/Query.h =================================================================== --- /dev/null +++ clang-query/Query.h @@ -0,0 +1,57 @@ +//===--- Query.h - clang-query ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H + +#include +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "llvm/ADT/Optional.h" + +namespace clang { +namespace query { + +enum OutputKind { + OK_Diag, + OK_Print, + OK_Dump +}; + +enum QueryKind { + QK_Invalid, + QK_NoOp, + QK_Match, + QK_Set +}; + +enum QueryVariable { + QV_Output, + QV_BindRoot +}; + +struct Query { + QueryKind Kind; + + // QK_Invalid + std::string ErrStr; + + // QK_Match + llvm::Optional Matcher; + + // QK_Set + QueryVariable Var; + + OutputKind OutKind; + bool BindRoot; +}; + +} // namespace query +} // namespace clang + +#endif Index: clang-query/QueryEngine.h =================================================================== --- /dev/null +++ clang-query/QueryEngine.h @@ -0,0 +1,43 @@ +//===--- QueryEngine.h - clang-query ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_ENGINE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_ENGINE_H + +#include "llvm/ADT/ArrayRef.h" +#include "Query.h" + +namespace llvm { + +class raw_ostream; + +} + +namespace clang { + +class ASTUnit; + +namespace query { + +class QueryEngine { + llvm::raw_ostream &Out; + llvm::ArrayRef ASTs; + OutputKind OutKind; + bool BindRoot; + +public: + QueryEngine(llvm::raw_ostream &Out, llvm::ArrayRef ASTs) + : Out(Out), ASTs(ASTs), OutKind(OK_Diag), BindRoot(true) {} + void ActOnQuery(const Query &Q); +}; + +} // namespace query +} // namespace clang + +#endif Index: clang-query/QueryEngine.cpp =================================================================== --- /dev/null +++ clang-query/QueryEngine.cpp @@ -0,0 +1,126 @@ +//===---- QueryEngine.cpp - clang-query engine ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "QueryEngine.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/TextDiagnostic.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace query { + +namespace { + +struct CollectBoundNodes : MatchFinder::MatchCallback { + std::vector &Bindings; + CollectBoundNodes(std::vector &Bindings) : Bindings(Bindings) {} + void run(const MatchFinder::MatchResult &Result) { + Bindings.push_back(Result.Nodes); + } +}; + +} + +void QueryEngine::ActOnQuery(const Query &Q) { + switch (Q.Kind) { + case QK_Invalid: + Out << Q.ErrStr << "\n"; + break; + + case QK_NoOp: + break; + + case QK_Match: { + unsigned MatchCount = 0; + + for (llvm::ArrayRef::iterator I = ASTs.begin(), E = ASTs.end(); + I != E; ++I) { + ASTUnit *AST = *I; + MatchFinder Finder; + std::vector Matches; + CollectBoundNodes Collect(Matches); + if (Q.Matcher->canConvertTo()) { + DeclarationMatcher M = Q.Matcher->convertTo(); + if (BindRoot) + M = decl(M).bind("root"); + Finder.addMatcher(decl(forEachDescendant(M)), &Collect); + } else if (Q.Matcher->canConvertTo()) { + StatementMatcher M = Q.Matcher->convertTo(); + if (BindRoot) + M = stmt(M).bind("root"); + Finder.addMatcher(decl(forEachDescendant(M)), &Collect); + } + Finder.match(*AST->getASTContext().getTranslationUnitDecl(), + AST->getASTContext()); + + for (std::vector::iterator MI = Matches.begin(), + ME = Matches.end(); + MI != ME; ++MI) { + Out << "\nMatch #" << ++MatchCount << ":\n\n"; + + for (BoundNodes::IDToNodeMap::const_iterator BI = MI->getMap().begin(), + BE = MI->getMap().end(); + BI != BE; ++BI) { + switch (OutKind) { + case OK_Diag: { + clang::SourceRange R = BI->second.getSourceRange(); + if (R.isValid()) { + TextDiagnostic TD(Out, AST->getASTContext().getLangOpts(), + &AST->getDiagnostics().getDiagnosticOptions()); + TD.emitDiagnostic( + R.getBegin(), DiagnosticsEngine::Note, + "\"" + BI->first + "\" binds here", + ArrayRef(CharSourceRange::getTokenRange(R)), + ArrayRef(), &AST->getSourceManager()); + } + break; + } + case OK_Print: { + Out << "Binding for \"" << BI->first << "\":\n"; + BI->second.print(Out, AST->getASTContext().getPrintingPolicy()); + Out << "\n"; + break; + } + case OK_Dump: { + Out << "Binding for \"" << BI->first << "\":\n"; + BI->second.dump(Out, AST->getSourceManager()); + Out << "\n"; + break; + } + } + } + + if (MI->getMap().empty()) + Out << "No bindings.\n"; + } + } + + Out << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n"); + break; + } + + case QK_Set: + switch (Q.Var) { + case QV_Output: + OutKind = Q.OutKind; + break; + + case QV_BindRoot: + BindRoot = Q.BindRoot; + break; + } + break; + } +} + +} // namespace query +} // namespace clang Index: clang-query/QueryParser.h =================================================================== --- /dev/null +++ clang-query/QueryParser.h @@ -0,0 +1,23 @@ +//===--- QueryParser.h - clang-query ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H + +namespace clang { +namespace query { + +struct Query; + +Query ParseQuery(const char *Line); + +} // namespace query +} // namespace clang + +#endif Index: clang-query/QueryParser.cpp =================================================================== --- /dev/null +++ clang-query/QueryParser.cpp @@ -0,0 +1,154 @@ +//===---- QueryParser.cpp - clang-query command parser --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "QueryParser.h" +#include "Query.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" + +using namespace llvm; +using namespace clang::ast_matchers::dynamic; + +namespace clang { +namespace query { + +static bool IsWhitespace(char C) { + return C == ' ' || C == '\t' || C == '\n' || C == '\r'; +} + +// Lex any amount of whitespace followed by a "word" (any sequence of +// non-whitespace characters). If no word is found before the end of input, +// return StringRef(). +static StringRef LexWord(const char *&Line) { + while (true) { + if (*Line == '\0') + return StringRef(); + + if (!IsWhitespace(*Line)) + break; + + ++Line; + } + + const char *Start = Line; + ++Line; + + while (true) { + if (*Line == '\0' || IsWhitespace(*Line)) + return StringRef(Start, Line-Start); + + ++Line; + } +} + +Query ParseQuery(const char *Line) { + Query Q; + StringRef CommandStr = LexWord(Line); + Q.Kind = StringSwitch(CommandStr) + .Case("", QK_NoOp) + .Case("m", QK_Match) + .Case("match", QK_Match) + .Case("set", QK_Set) + .Default(QK_Invalid); + + switch (Q.Kind) { + case QK_NoOp: + break; + + case QK_Match: { + Diagnostics Diag; + Optional Matcher = + Parser::parseMatcherExpression(Line, &Diag); + if (!Matcher) { + llvm::raw_string_ostream OS(Q.ErrStr); + Diag.printToStreamFull(OS); + Q.Kind = QK_Invalid; + } + Q.Matcher = *Matcher; + break; + } + + case QK_Set: { + StringRef VarStr = LexWord(Line); + if (VarStr.empty()) { + Q.ErrStr = "expected variable name"; + Q.Kind = QK_Invalid; + return Q; + } + unsigned Var = StringSwitch(VarStr) + .Case("output", QV_Output) + .Case("bind-root", QV_BindRoot) + .Default(~0u); + if (Var == ~0u) { + Q.ErrStr = ("unknown variable: '" + VarStr + "'").str(); + Q.Kind = QK_Invalid; + return Q; + } + Q.Var = QueryVariable(Var); + + StringRef ValStr = LexWord(Line); + if (ValStr.empty()) { + Q.ErrStr = "expected variable value"; + Q.Kind = QK_Invalid; + return Q; + } + + switch (Q.Var) { + case QV_Output: { + unsigned OutKind = StringSwitch(ValStr) + .Case("diag", OK_Diag) + .Case("print", OK_Print) + .Case("dump", OK_Dump) + .Default(~0u); + if (OutKind == ~0u) { + Q.ErrStr = + ("expected 'diag', 'print' or 'dump', got '" + ValStr + "'").str(); + Q.Kind = QK_Invalid; + return Q; + } + Q.OutKind = OutputKind(OutKind); + break; + } + case QV_BindRoot: { + unsigned BindRoot = StringSwitch(ValStr) + .Case("false", 0) + .Case("true", 1) + .Default(~0u); + if (BindRoot == ~0u) { + Q.ErrStr = ("expected 'true' or 'false', got '" + ValStr + "'").str(); + Q.Kind = QK_Invalid; + return Q; + } + Q.BindRoot = BindRoot; + break; + } + } + + const char *End = Line; + if (!LexWord(Line).empty()) { + Q.ErrStr = std::string("unexpected extra input: '") + End + "'"; + Q.Kind = QK_Invalid; + return Q; + } + + break; + } + + case QK_Invalid: { + Q.ErrStr = ("unknown command: " + CommandStr).str(); + break; + } + } + + return Q; +} + +} // namespace query +} // namespace clang Index: clang-query/tool/CMakeLists.txt =================================================================== --- /dev/null +++ clang-query/tool/CMakeLists.txt @@ -0,0 +1,11 @@ +if(HAVE_LIBEDIT) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + + add_clang_executable(clang-query ClangQuery.cpp) + target_link_libraries(clang-query + ${LIBEDIT} + clangFrontend + clangQuery + clangTooling + ) +endif() Index: clang-query/tool/ClangQuery.cpp =================================================================== --- /dev/null +++ clang-query/tool/ClangQuery.cpp @@ -0,0 +1,117 @@ +//===---- ClangQuery.cpp - clang-query tool -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This tool is for interactive exploration of the Clang AST using AST matchers. +// It currently allows the user to enter a matcher at an interactive prompt and +// view the resulting bindings as diagnostics, AST pretty prints or AST dumps. +// Example session: +// +// $ cat foo.c +// void foo(void) {} +// $ clang-query foo.c -- +// clang-query> match functionDecl() +// +// Match #1: +// +// foo.c:1:1: note: "root" binds here +// void foo(void) {} +// ^~~~~~~~~~~~~~~~~ +// 1 match. +// +//===----------------------------------------------------------------------===// + +#include "QueryEngine.h" +#include "QueryParser.h" + +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Signals.h" + +#include + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::dynamic; +using namespace clang::query; +using namespace clang::tooling; +using namespace llvm; + +// Set up the command line options +cl::opt BuildPath( + cl::Positional, + cl::desc("")); + +cl::list SourcePaths( + cl::Positional, + cl::desc(" [... ]"), + cl::OneOrMore); + +static char *ReturnPrompt(EditLine *EL) { + static char Prompt[] = "clang-query> "; + return Prompt; +} + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(); + llvm::OwningPtr Compilations( + FixedCompilationDatabase::loadFromCommandLine(argc, argv)); + cl::ParseCommandLineOptions(argc, argv); + if (!Compilations) { // Couldn't find a compilation DB from the command line + std::string ErrorMessage; + Compilations.reset( + !BuildPath.empty() ? + CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage) : + CompilationDatabase::autoDetectFromSource(SourcePaths[0], ErrorMessage) + ); + + // Still no compilation DB? - bail. + if (!Compilations) + llvm::report_fatal_error(ErrorMessage); + } + + ClangTool Tool(*Compilations, SourcePaths); + std::vector ASTs; + if (Tool.buildASTs(ASTs) != 0) + return 1; + + QueryEngine QE(llvm::outs(), ASTs); + + History *Hist = history_init(); + HistEvent Event; + history(Hist, &Event, H_SETSIZE, 100); + + EditLine *EL = el_init("clang-query", stdin, stdout, stderr); + el_set(EL, EL_PROMPT, ReturnPrompt); + el_set(EL, EL_EDITOR, "emacs"); + el_set(EL, EL_HIST, history, Hist); + + int Count; + while (const char *Line = el_gets(EL, &Count)) { + if (Count == 0) + break; + + history(Hist, &Event, H_ENTER, Line); + + Query Q = ParseQuery(Line); + QE.ActOnQuery(Q); + } + + llvm::DeleteContainerPointers(ASTs); + + history_end(Hist); + el_end(EL); + + llvm::outs() << "\n"; + + return 0; +} Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -38,6 +38,10 @@ ExtraToolsUnitTests ) +if(HAVE_LIBEDIT) + list(APPEND CLANG_TOOLS_TEST_DEPS clang-query) +endif() + add_lit_testsuite(check-clang-tools "Running the Clang extra tools' regression tests" ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CLANG_TOOLS_TEST_DEPS} Index: test/clang-query/function-decl.c =================================================================== --- /dev/null +++ test/clang-query/function-decl.c @@ -0,0 +1,5 @@ +// RUN: echo 'match functionDecl(hasName("foo"))' | clang-query %s -- | FileCheck %s +// REQUIRES: libedit + +// CHECK: function-decl.c:5:1: note: "root" binds here +void foo(void) {} Index: test/lit.cfg =================================================================== --- test/lit.cfg +++ test/lit.cfg @@ -229,3 +229,6 @@ # ANSI escape sequences in non-dumb terminal if platform.system() not in ['Windows']: config.available_features.add('ansi-escape-sequences') + +if config.have_libedit == "1": + config.available_features.add('libedit') Index: test/lit.site.cfg.in =================================================================== --- test/lit.site.cfg.in +++ test/lit.site.cfg.in @@ -7,6 +7,7 @@ config.lit_tools_dir = "@LLVM_LIT_TOOLS_DIR@" config.clang_tools_binary_dir = "@CLANG_TOOLS_BINARY_DIR@" config.target_triple = "@TARGET_TRIPLE@" +config.have_libedit = "@HAVE_LIBEDIT@" # Support substitution of the tools and libs dirs with user parameters. This is # used when we can't determine the tool dir at configuration time. Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -7,4 +7,5 @@ add_subdirectory(clang-apply-replacements) add_subdirectory(clang-modernize) +add_subdirectory(clang-query) add_subdirectory(clang-tidy) Index: unittests/Makefile =================================================================== --- unittests/Makefile +++ unittests/Makefile @@ -10,6 +10,6 @@ CLANG_LEVEL := ../../.. include $(CLANG_LEVEL)/../../Makefile.config -PARALLEL_DIRS := clang-apply-replacements clang-modernize clang-tidy +PARALLEL_DIRS := clang-apply-replacements clang-modernize clang-query clang-tidy include $(CLANG_LEVEL)/Makefile Index: unittests/clang-query/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/clang-query/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-query + ) + +add_extra_unittest(ClangQueryTests + QueryEngineTest.cpp + QueryParserTest.cpp + ) + +target_link_libraries(ClangQueryTests + clangASTMatchers + clangQuery + clangTooling + ) Index: unittests/clang-query/Makefile =================================================================== --- /dev/null +++ unittests/clang-query/Makefile @@ -0,0 +1,24 @@ +##===- unittests/clang-query/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 = ../../../.. +include $(CLANG_LEVEL)/../../Makefile.config + +TESTNAME = ClangQuery +LINK_COMPONENTS := asmparser bitreader support MC MCParser option \ + TransformUtils +USEDLIBS = clangQuery.a clangTooling.a clangFrontend.a clangSerialization.a \ + clangDriver.a clangParse.a clangSema.a clangEdit.a clangAnalysis.a \ + clangAST.a clangASTMatchers.a clangDynamicASTMatchers.a clangLex.a \ + clangBasic.a + +include $(CLANG_LEVEL)/Makefile +MAKEFILE_UNITTEST_NO_INCLUDE_COMMON := 1 +CPP.Flags += -I$(PROJ_SRC_DIR)/../../clang-query +include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest Index: unittests/clang-query/QueryEngineTest.cpp =================================================================== --- /dev/null +++ unittests/clang-query/QueryEngineTest.cpp @@ -0,0 +1,104 @@ +//===---- QueryEngineTest.cpp - clang-query test --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "QueryEngine.h" +#include "Query.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" +#include + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::query; +using namespace clang::tooling; + +TEST(QueryEngine, Basic) { + OwningPtr FooAST( + buildASTFromCode("void foo1(void) {}\nvoid foo2(void) {}", "foo.cc")); + ASSERT_TRUE(FooAST.get()); + OwningPtr BarAST( + buildASTFromCode("void bar1(void) {}\nvoid bar2(void) {}", "bar.cc")); + ASSERT_TRUE(BarAST.get()); + + ASTUnit *ASTs[] = { FooAST.get(), BarAST.get() }; + + std::string Str; + llvm::raw_string_ostream OS(Str); + QueryEngine E(OS, ASTs); + + DeclarationMatcher FnMatcher = functionDecl(); + DeclarationMatcher FooMatcher = functionDecl(hasName("foo1")); + Query Q; + Q.Kind = QK_Match; + Q.Matcher = FnMatcher; + E.ActOnQuery(Q); + + EXPECT_TRUE(OS.str().find("foo.cc:1:1: note: \"root\" binds here") != + std::string::npos); + EXPECT_TRUE(OS.str().find("foo.cc:2:1: note: \"root\" binds here") != + std::string::npos); + EXPECT_TRUE(OS.str().find("bar.cc:1:1: note: \"root\" binds here") != + std::string::npos); + EXPECT_TRUE(OS.str().find("bar.cc:2:1: note: \"root\" binds here") != + std::string::npos); + EXPECT_TRUE(OS.str().find("4 matches.") != std::string::npos); + + Str.clear(); + + Q.Matcher = FooMatcher; + E.ActOnQuery(Q); + + EXPECT_TRUE(OS.str().find("foo.cc:1:1: note: \"root\" binds here") != + std::string::npos); + EXPECT_TRUE(OS.str().find("1 match.") != std::string::npos); + + Str.clear(); + + Q.Kind = QK_Set; + Q.Var = QV_Output; + Q.OutKind = OK_Print; + E.ActOnQuery(Q); + + Q.Kind = QK_Match; + Q.Matcher = FooMatcher; + E.ActOnQuery(Q); + + EXPECT_TRUE(OS.str().find("Binding for \"root\":\nvoid foo1()") != + std::string::npos); + + Str.clear(); + + Q.Kind = QK_Set; + Q.Var = QV_Output; + Q.OutKind = OK_Dump; + E.ActOnQuery(Q); + + Q.Kind = QK_Match; + Q.Matcher = FooMatcher; + E.ActOnQuery(Q); + + EXPECT_TRUE(OS.str().find("FunctionDecl") != std::string::npos); + + Str.clear(); + + Q.Kind = QK_Set; + Q.Var = QV_BindRoot; + Q.BindRoot = false; + E.ActOnQuery(Q); + + Q.Kind = QK_Match; + Q.Matcher = FooMatcher; + E.ActOnQuery(Q); + + EXPECT_TRUE(OS.str().find("No bindings.") != std::string::npos); +} Index: unittests/clang-query/QueryParserTest.cpp =================================================================== --- /dev/null +++ unittests/clang-query/QueryParserTest.cpp @@ -0,0 +1,69 @@ +//===---- QueryParserTest.cpp - clang-query test --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "QueryParser.h" +#include "Query.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace clang::query; + +TEST(QueryParser, NoOp) { + Query Q = ParseQuery(""); + EXPECT_EQ(QK_NoOp, Q.Kind); + + Q = ParseQuery("\n"); + EXPECT_EQ(QK_NoOp, Q.Kind); +} + +TEST(QueryParser, Set) { + Query Q = ParseQuery("set"); + EXPECT_EQ(QK_Invalid, Q.Kind); + EXPECT_EQ("expected variable name", Q.ErrStr); + + Q = ParseQuery("set foo bar"); + EXPECT_EQ(QK_Invalid, Q.Kind); + EXPECT_EQ("unknown variable: 'foo'", Q.ErrStr); + + Q = ParseQuery("set output"); + EXPECT_EQ(QK_Invalid, Q.Kind); + EXPECT_EQ("expected variable value", Q.ErrStr); + + Q = ParseQuery("set bind-root true foo"); + EXPECT_EQ(QK_Invalid, Q.Kind); + EXPECT_EQ("unexpected extra input: ' foo'", Q.ErrStr); + + Q = ParseQuery("set output foo"); + EXPECT_EQ(QK_Invalid, Q.Kind); + EXPECT_EQ("expected 'diag', 'print' or 'dump', got 'foo'", Q.ErrStr); + + Q = ParseQuery("set output dump"); + EXPECT_EQ(QK_Set, Q.Kind); + EXPECT_EQ(QV_Output, Q.Var); + EXPECT_EQ(OK_Dump, Q.OutKind); + + Q = ParseQuery("set bind-root foo"); + EXPECT_EQ(QK_Invalid, Q.Kind); + EXPECT_EQ("expected 'true' or 'false', got 'foo'", Q.ErrStr); + + Q = ParseQuery("set bind-root true"); + EXPECT_EQ(QK_Set, Q.Kind); + EXPECT_EQ(QV_BindRoot, Q.Var); + EXPECT_EQ(true, Q.BindRoot); +} + +TEST(QueryParser, Match) { + Query Q = ParseQuery("match decl()"); + ASSERT_EQ(QK_Match, Q.Kind); + EXPECT_TRUE(Q.Matcher->canConvertTo()); + + Q = ParseQuery("m stmt()"); + ASSERT_EQ(QK_Match, Q.Kind); + EXPECT_TRUE(Q.Matcher->canConvertTo()); +}