160 lines
5.3 KiB
C++
160 lines
5.3 KiB
C++
//===- 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
|