//===--- 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 #include 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 { 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(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 points() { auto &SM = AST.sourceManager(); auto FID = SM.getMainFileID(); auto Offsets = Target.points(); std::vector 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 struct ^foo; template struct ^foo {};", "template void ^foo(); template 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::Hint, Eq(Hints::CompleteSymbol)))); } } } } // namespace } // namespace clang::include_cleaner