blob: 756757cfd0f09d21ed91a658b375e04de3e3624c [file] [log] [blame]
//===--- LocateSymbolTest.cpp -------------------------------------- C++-*-===//
//
// 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 "AnalysisInternal.h"
#include "TypesInternal.h"
#include "clang-include-cleaner/Types.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Testing/TestAST.h"
#include "clang/Tooling/Inclusions/StandardLibrary.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Testing/Annotations/Annotations.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <tuple>
#include <vector>
namespace clang::include_cleaner {
namespace {
using testing::Each;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Eq;
using testing::Field;
// A helper for building ASTs and getting decls out of it by name. Example usage
// looks like:
// LocateExample X("void ^foo();");
// Decl &Foo = X.findDecl("foo");
// X.points(); // returns all the points in annotated test input.
struct LocateExample {
private:
llvm::Annotations Target;
TestAST AST;
public:
LocateExample(llvm::StringRef AnnotatedCode)
: Target(AnnotatedCode), AST([this] {
TestInputs Inputs(Target.code());
Inputs.ExtraArgs.push_back("-std=c++17");
return Inputs;
}()) {}
const Decl &findDecl(llvm::StringRef SymbolName) {
struct Visitor : RecursiveASTVisitor<Visitor> {
llvm::StringRef NameToFind;
const NamedDecl *Out = nullptr;
bool VisitNamedDecl(const NamedDecl *ND) {
// Skip the templated decls, as they have the same name and matches in
// this file care about the outer template name.
if (auto *TD = ND->getDescribedTemplate())
ND = TD;
if (ND->getName() == NameToFind) {
EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl())
<< "Found multiple matches for " << NameToFind.str();
Out = llvm::cast<NamedDecl>(ND->getCanonicalDecl());
}
return true;
}
};
Visitor V;
V.NameToFind = SymbolName;
V.TraverseDecl(AST.context().getTranslationUnitDecl());
if (!V.Out)
ADD_FAILURE() << "Couldn't find any decls with name: " << SymbolName;
assert(V.Out);
return *V.Out;
}
Macro findMacro(llvm::StringRef Name) {
auto &PP = AST.preprocessor();
auto *II = PP.getIdentifierInfo(Name);
if (!II || !II->hasMacroDefinition()) {
ADD_FAILURE() << "Couldn't find any macros with name: " << Name;
return {};
}
auto MD = PP.getMacroDefinition(II);
assert(MD.getMacroInfo());
return {II, MD.getMacroInfo()->getDefinitionLoc()};
}
std::vector<SymbolLocation> points() {
auto &SM = AST.sourceManager();
auto FID = SM.getMainFileID();
auto Offsets = Target.points();
std::vector<SymbolLocation> Results;
for (auto &Offset : Offsets)
Results.emplace_back(SM.getComposedLoc(FID, Offset));
return Results;
}
};
TEST(LocateSymbol, Decl) {
// Looks for decl with name 'foo' and performs locateSymbol on it.
// Expects all the locations in the case to be returned as a location.
const llvm::StringLiteral Cases[] = {
"struct ^foo; struct ^foo {};",
"namespace ns { void ^foo(); void ^foo() {} }",
"enum class ^foo; enum class ^foo {};",
};
for (auto &Case : Cases) {
SCOPED_TRACE(Case);
LocateExample Test(Case);
EXPECT_THAT(locateSymbol(Test.findDecl("foo")),
ElementsAreArray(Test.points()));
}
}
TEST(LocateSymbol, Stdlib) {
{
LocateExample Test("namespace std { struct vector; }");
EXPECT_THAT(
locateSymbol(Test.findDecl("vector")),
ElementsAre(*tooling::stdlib::Symbol::named("std::", "vector")));
}
{
LocateExample Test("#define assert(x)\nvoid foo() { assert(true); }");
EXPECT_THAT(locateSymbol(Test.findMacro("assert")),
ElementsAre(*tooling::stdlib::Symbol::named("", "assert")));
}
}
TEST(LocateSymbol, Macros) {
// Make sure we preserve the last one.
LocateExample Test("#define FOO\n#undef FOO\n#define ^FOO");
EXPECT_THAT(locateSymbol(Test.findMacro("FOO")),
ElementsAreArray(Test.points()));
}
MATCHER_P2(HintedSymbol, Symbol, Hint, "") {
return std::tie(arg.Hint, arg) == std::tie(Hint, Symbol);
}
TEST(LocateSymbol, CompleteSymbolHint) {
{
// stdlib symbols are always complete.
LocateExample Test("namespace std { struct vector; }");
EXPECT_THAT(locateSymbol(Test.findDecl("vector")),
ElementsAre(HintedSymbol(
*tooling::stdlib::Symbol::named("std::", "vector"),
Hints::CompleteSymbol)));
}
{
// macros are always complete.
LocateExample Test("#define ^FOO");
EXPECT_THAT(locateSymbol(Test.findMacro("FOO")),
ElementsAre(HintedSymbol(Test.points().front(),
Hints::CompleteSymbol)));
}
{
// Completeness is only absent in cases that matters.
const llvm::StringLiteral Cases[] = {
"struct ^foo; struct ^foo {};",
"template <typename> struct ^foo; template <typename> struct ^foo {};",
"template <typename> void ^foo(); template <typename> void ^foo() {};",
};
for (auto &Case : Cases) {
SCOPED_TRACE(Case);
LocateExample Test(Case);
EXPECT_THAT(locateSymbol(Test.findDecl("foo")),
ElementsAre(HintedSymbol(Test.points().front(), Hints::None),
HintedSymbol(Test.points().back(),
Hints::CompleteSymbol)));
}
}
{
// All declarations should be marked as complete in cases that a definition
// is not usually needed.
const llvm::StringLiteral Cases[] = {
"void foo(); void foo() {}",
"extern int foo; int foo;",
};
for (auto &Case : Cases) {
SCOPED_TRACE(Case);
LocateExample Test(Case);
EXPECT_THAT(locateSymbol(Test.findDecl("foo")),
Each(Field(&Hinted<SymbolLocation>::Hint,
Eq(Hints::CompleteSymbol))));
}
}
}
} // namespace
} // namespace clang::include_cleaner