| //===- 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 <cstdlib> |
| |
| 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<syntax::Arena> &Arena, |
| std::unique_ptr<syntax::TokenCollector> Tokens) |
| : Root(Root), Arena(Arena), Tokens(std::move(Tokens)) { |
| assert(this->Tokens); |
| } |
| |
| void HandleTranslationUnit(ASTContext &Ctx) override { |
| Arena = std::make_unique<syntax::Arena>(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<syntax::Arena> &Arena; |
| std::unique_ptr<syntax::TokenCollector> Tokens; |
| }; |
| |
| class BuildSyntaxTreeAction : public ASTFrontendAction { |
| public: |
| BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, |
| std::unique_ptr<syntax::Arena> &Arena) |
| : Root(Root), Arena(Arena) {} |
| |
| std::unique_ptr<ASTConsumer> |
| CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { |
| // We start recording the tokens, ast consumer will take on the result. |
| auto Tokens = |
| std::make_unique<syntax::TokenCollector>(CI.getPreprocessor()); |
| return std::make_unique<BuildSyntaxTree>(Root, Arena, |
| std::move(Tokens)); |
| } |
| |
| private: |
| syntax::TranslationUnit *&Root; |
| std::unique_ptr<syntax::Arena> &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<const char *> 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<DiagnosticsEngine> Diags = |
| new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions); |
| IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS = |
| new llvm::vfs::InMemoryFileSystem; |
| llvm::IntrusiveRefCntPtr<FileManager> FileMgr = |
| new FileManager(FileSystemOptions(), FS); |
| llvm::IntrusiveRefCntPtr<SourceManager> SourceMgr = |
| new SourceManager(*Diags, *FileMgr); |
| // Set after calling buildTree(). |
| std::unique_ptr<syntax::Arena> Arena; |
| }; |
| |
| TEST_F(SyntaxTreeTest, Basic) { |
| std::pair</*Input*/ std::string, /*Expected*/ std::string> 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 |