//===- TreeTest.cpp -------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Tooling/Syntax/Tree.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/Decl.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Tooling/Syntax/BuildTree.h" #include "clang/Tooling/Syntax/Nodes.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include using namespace clang; namespace { class SyntaxTreeTest : public ::testing::Test { protected: // Build a syntax tree for the code. syntax::TranslationUnit *buildTree(llvm::StringRef Code) { // FIXME: this code is almost the identical to the one in TokensTest. Share // it. class BuildSyntaxTree : public ASTConsumer { public: BuildSyntaxTree(syntax::TranslationUnit *&Root, std::unique_ptr &Arena, std::unique_ptr Tokens) : Root(Root), Arena(Arena), Tokens(std::move(Tokens)) { assert(this->Tokens); } void HandleTranslationUnit(ASTContext &Ctx) override { Arena = std::make_unique(Ctx.getSourceManager(), Ctx.getLangOpts(), std::move(*Tokens).consume()); Tokens = nullptr; // make sure we fail if this gets called twice. Root = syntax::buildSyntaxTree(*Arena, *Ctx.getTranslationUnitDecl()); } private: syntax::TranslationUnit *&Root; std::unique_ptr &Arena; std::unique_ptr Tokens; }; class BuildSyntaxTreeAction : public ASTFrontendAction { public: BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, std::unique_ptr &Arena) : Root(Root), Arena(Arena) {} std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { // We start recording the tokens, ast consumer will take on the result. auto Tokens = std::make_unique(CI.getPreprocessor()); return std::make_unique(Root, Arena, std::move(Tokens)); } private: syntax::TranslationUnit *&Root; std::unique_ptr &Arena; }; constexpr const char *FileName = "./input.cpp"; FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy("")); if (!Diags->getClient()) Diags->setClient(new IgnoringDiagConsumer); // Prepare to run a compiler. std::vector Args = {"syntax-test", "-std=c++11", "-fsyntax-only", FileName}; auto CI = createInvocationFromCommandLine(Args, Diags, FS); assert(CI); CI->getFrontendOpts().DisableFree = false; CI->getPreprocessorOpts().addRemappedFile( FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release()); CompilerInstance Compiler; Compiler.setInvocation(std::move(CI)); Compiler.setDiagnostics(Diags.get()); Compiler.setFileManager(FileMgr.get()); Compiler.setSourceManager(SourceMgr.get()); syntax::TranslationUnit *Root = nullptr; BuildSyntaxTreeAction Recorder(Root, this->Arena); if (!Compiler.ExecuteAction(Recorder)) { ADD_FAILURE() << "failed to run the frontend"; std::abort(); } return Root; } // Adds a file to the test VFS. void addFile(llvm::StringRef Path, llvm::StringRef Contents) { if (!FS->addFile(Path, time_t(), llvm::MemoryBuffer::getMemBufferCopy(Contents))) { ADD_FAILURE() << "could not add a file to VFS: " << Path; } } // Data fields. llvm::IntrusiveRefCntPtr Diags = new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions); IntrusiveRefCntPtr FS = new llvm::vfs::InMemoryFileSystem; llvm::IntrusiveRefCntPtr FileMgr = new FileManager(FileSystemOptions(), FS); llvm::IntrusiveRefCntPtr SourceMgr = new SourceManager(*Diags, *FileMgr); // Set after calling buildTree(). std::unique_ptr Arena; }; TEST_F(SyntaxTreeTest, Basic) { std::pair Cases[] = { { R"cpp( int main() {} void foo() {} )cpp", R"txt( *: TranslationUnit |-TopLevelDeclaration | |-int | |-main | |-( | |-) | `-CompoundStatement | |-2: { | `-3: } `-TopLevelDeclaration |-void |-foo |-( |-) `-CompoundStatement |-2: { `-3: } )txt"}, }; for (const auto &T : Cases) { auto *Root = buildTree(T.first); std::string Expected = llvm::StringRef(T.second).trim().str(); std::string Actual = llvm::StringRef(Root->dump(*Arena)).trim(); EXPECT_EQ(Expected, Actual) << "the resulting dump is:\n" << Actual; } } } // namespace