|  | //===-- IncludeFixerTest.cpp - Include fixer unit tests -------------------===// | 
|  | // | 
|  | //                     The LLVM Compiler Infrastructure | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "InMemorySymbolIndex.h" | 
|  | #include "IncludeFixer.h" | 
|  | #include "SymbolIndexManager.h" | 
|  | #include "unittests/Tooling/RewriterTestContext.h" | 
|  | #include "clang/Tooling/Tooling.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | namespace clang { | 
|  | namespace include_fixer { | 
|  | namespace { | 
|  |  | 
|  | using find_all_symbols::SymbolInfo; | 
|  |  | 
|  | static bool runOnCode(tooling::ToolAction *ToolAction, StringRef Code, | 
|  | StringRef FileName, | 
|  | const std::vector<std::string> &ExtraArgs) { | 
|  | llvm::IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem( | 
|  | new vfs::InMemoryFileSystem); | 
|  | llvm::IntrusiveRefCntPtr<FileManager> Files( | 
|  | new FileManager(FileSystemOptions(), InMemoryFileSystem)); | 
|  | // FIXME: Investigate why -fms-compatibility breaks tests. | 
|  | std::vector<std::string> Args = {"include_fixer", "-fsyntax-only", | 
|  | "-fno-ms-compatibility", FileName}; | 
|  | Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); | 
|  | tooling::ToolInvocation Invocation( | 
|  | Args, ToolAction, Files.get(), | 
|  | std::make_shared<PCHContainerOperations>()); | 
|  |  | 
|  | InMemoryFileSystem->addFile(FileName, 0, | 
|  | llvm::MemoryBuffer::getMemBuffer(Code)); | 
|  |  | 
|  | InMemoryFileSystem->addFile("foo.h", 0, | 
|  | llvm::MemoryBuffer::getMemBuffer("\n")); | 
|  | InMemoryFileSystem->addFile("dir/bar.h", 0, | 
|  | llvm::MemoryBuffer::getMemBuffer("\n")); | 
|  | InMemoryFileSystem->addFile("dir/otherdir/qux.h", 0, | 
|  | llvm::MemoryBuffer::getMemBuffer("\n")); | 
|  | InMemoryFileSystem->addFile("header.h", 0, | 
|  | llvm::MemoryBuffer::getMemBuffer("bar b;")); | 
|  | return Invocation.run(); | 
|  | } | 
|  |  | 
|  | static std::string runIncludeFixer( | 
|  | StringRef Code, | 
|  | const std::vector<std::string> &ExtraArgs = std::vector<std::string>()) { | 
|  | std::vector<SymbolInfo> Symbols = { | 
|  | SymbolInfo("string", SymbolInfo::SymbolKind::Class, "<string>", 1, | 
|  | {{SymbolInfo::ContextType::Namespace, "std"}}), | 
|  | SymbolInfo("sting", SymbolInfo::SymbolKind::Class, "\"sting\"", 1, | 
|  | {{SymbolInfo::ContextType::Namespace, "std"}}), | 
|  | SymbolInfo("foo", SymbolInfo::SymbolKind::Class, "\"dir/otherdir/qux.h\"", | 
|  | 1, {{SymbolInfo::ContextType::Namespace, "b"}, | 
|  | {SymbolInfo::ContextType::Namespace, "a"}}), | 
|  | SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar.h\"", 1, | 
|  | {{SymbolInfo::ContextType::Namespace, "b"}, | 
|  | {SymbolInfo::ContextType::Namespace, "a"}}), | 
|  | SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar2.h\"", 1, | 
|  | {{SymbolInfo::ContextType::Namespace, "c"}, | 
|  | {SymbolInfo::ContextType::Namespace, "a"}}), | 
|  | SymbolInfo("Green", SymbolInfo::SymbolKind::Class, "\"color.h\"", 1, | 
|  | {{SymbolInfo::ContextType::EnumDecl, "Color"}, | 
|  | {SymbolInfo::ContextType::Namespace, "b"}, | 
|  | {SymbolInfo::ContextType::Namespace, "a"}}), | 
|  | SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"", 1, | 
|  | {{SymbolInfo::ContextType::Namespace, "__a"}, | 
|  | {SymbolInfo::ContextType::Namespace, "a"}}, | 
|  | /*num_occurrences=*/2), | 
|  | SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"", 2, | 
|  | {{SymbolInfo::ContextType::Namespace, "a"}}, | 
|  | /*num_occurrences=*/1), | 
|  | SymbolInfo("StrCat", SymbolInfo::SymbolKind::Class, "\"strcat.h\"", | 
|  | 1, {{SymbolInfo::ContextType::Namespace, "str"}}), | 
|  | SymbolInfo("str", SymbolInfo::SymbolKind::Class, "\"str.h\"", | 
|  | 1, {}), | 
|  | SymbolInfo("foo2", SymbolInfo::SymbolKind::Class, "\"foo2.h\"", | 
|  | 1, {}), | 
|  | }; | 
|  | auto SymbolIndexMgr = llvm::make_unique<include_fixer::SymbolIndexManager>(); | 
|  | SymbolIndexMgr->addSymbolIndex( | 
|  | llvm::make_unique<include_fixer::InMemorySymbolIndex>(Symbols)); | 
|  |  | 
|  | std::vector<IncludeFixerContext> FixerContexts; | 
|  | IncludeFixerActionFactory Factory(*SymbolIndexMgr, FixerContexts, "llvm"); | 
|  | std::string FakeFileName = "input.cc"; | 
|  | runOnCode(&Factory, Code, FakeFileName, ExtraArgs); | 
|  | assert(FixerContexts.size() == 1); | 
|  | if (FixerContexts.front().getHeaderInfos().empty()) | 
|  | return Code; | 
|  | auto Replaces = clang::include_fixer::createIncludeFixerReplacements( | 
|  | Code, FixerContexts.front()); | 
|  | EXPECT_TRUE(static_cast<bool>(Replaces)) | 
|  | << llvm::toString(Replaces.takeError()) << "\n"; | 
|  | if (!Replaces) | 
|  | return ""; | 
|  | clang::RewriterTestContext Context; | 
|  | clang::FileID ID = Context.createInMemoryFile(FakeFileName, Code); | 
|  | clang::tooling::applyAllReplacements(*Replaces, Context.Rewrite); | 
|  | return Context.getRewrittenText(ID); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, Typo) { | 
|  | EXPECT_EQ("#include <string>\nstd::string foo;\n", | 
|  | runIncludeFixer("std::string foo;\n")); | 
|  |  | 
|  | EXPECT_EQ("// comment\n#include \"foo.h\"\n#include <string>\n" | 
|  | "std::string foo;\n#include \"dir/bar.h\"\n", | 
|  | runIncludeFixer("// comment\n#include \"foo.h\"\nstd::string foo;\n" | 
|  | "#include \"dir/bar.h\"\n")); | 
|  |  | 
|  | EXPECT_EQ("#include \"foo.h\"\n#include <string>\nstd::string foo;\n", | 
|  | runIncludeFixer("#include \"foo.h\"\nstd::string foo;\n")); | 
|  |  | 
|  | EXPECT_EQ( | 
|  | "#include \"foo.h\"\n#include <string>\nstd::string::size_type foo;\n", | 
|  | runIncludeFixer("#include \"foo.h\"\nstd::string::size_type foo;\n")); | 
|  |  | 
|  | EXPECT_EQ("#include <string>\nstd::string foo;\n", | 
|  | runIncludeFixer("string foo;\n")); | 
|  |  | 
|  | // Should not match std::string. | 
|  | EXPECT_EQ("::string foo;\n", runIncludeFixer("::string foo;\n")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, IncompleteType) { | 
|  | EXPECT_EQ( | 
|  | "#include \"foo.h\"\n#include <string>\n" | 
|  | "namespace std {\nclass string;\n}\nstd::string foo;\n", | 
|  | runIncludeFixer("#include \"foo.h\"\n" | 
|  | "namespace std {\nclass string;\n}\nstring foo;\n")); | 
|  |  | 
|  | EXPECT_EQ("#include <string>\n" | 
|  | "class string;\ntypedef string foo;\nfoo f;\n", | 
|  | runIncludeFixer("class string;\ntypedef string foo;\nfoo f;\n")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, MinimizeInclude) { | 
|  | std::vector<std::string> IncludePath = {"-Idir/"}; | 
|  | EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n", | 
|  | runIncludeFixer("a::b::foo bar;\n", IncludePath)); | 
|  |  | 
|  | IncludePath = {"-isystemdir"}; | 
|  | EXPECT_EQ("#include <otherdir/qux.h>\na::b::foo bar;\n", | 
|  | runIncludeFixer("a::b::foo bar;\n", IncludePath)); | 
|  |  | 
|  | IncludePath = {"-iquotedir"}; | 
|  | EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n", | 
|  | runIncludeFixer("a::b::foo bar;\n", IncludePath)); | 
|  |  | 
|  | IncludePath = {"-Idir", "-Idir/otherdir"}; | 
|  | EXPECT_EQ("#include \"qux.h\"\na::b::foo bar;\n", | 
|  | runIncludeFixer("a::b::foo bar;\n", IncludePath)); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, NestedName) { | 
|  | EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n" | 
|  | "int x = a::b::foo(0);\n", | 
|  | runIncludeFixer("int x = a::b::foo(0);\n")); | 
|  |  | 
|  | // FIXME: Handle simple macros. | 
|  | EXPECT_EQ("#define FOO a::b::foo\nint x = FOO;\n", | 
|  | runIncludeFixer("#define FOO a::b::foo\nint x = FOO;\n")); | 
|  | EXPECT_EQ("#define FOO(x) a::##x\nint x = FOO(b::foo);\n", | 
|  | runIncludeFixer("#define FOO(x) a::##x\nint x = FOO(b::foo);\n")); | 
|  |  | 
|  | // The empty namespace is cleaned up by clang-format after include-fixer | 
|  | // finishes. | 
|  | EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n" | 
|  | "\nint a = a::b::foo(0);\n", | 
|  | runIncludeFixer("namespace a {}\nint a = a::b::foo(0);\n")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, MultipleMissingSymbols) { | 
|  | EXPECT_EQ("#include <string>\nstd::string bar;\nstd::sting foo;\n", | 
|  | runIncludeFixer("std::string bar;\nstd::sting foo;\n")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, ScopedNamespaceSymbols) { | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}", | 
|  | runIncludeFixer("namespace a {\nb::bar b;\n}")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}", | 
|  | runIncludeFixer("namespace A {\na::b::bar b;\n}")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nvoid func() { b::bar b; }\n}", | 
|  | runIncludeFixer("namespace a {\nvoid func() { b::bar b; }\n}")); | 
|  | EXPECT_EQ("namespace A { c::b::bar b; }\n", | 
|  | runIncludeFixer("namespace A { c::b::bar b; }\n")); | 
|  | // FIXME: The header should not be added here. Remove this after we support | 
|  | // full match. | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}", | 
|  | runIncludeFixer("namespace A {\nb::bar b;\n}")); | 
|  |  | 
|  | // Finds candidates for "str::StrCat". | 
|  | EXPECT_EQ("#include \"strcat.h\"\nnamespace foo2 {\nstr::StrCat b;\n}", | 
|  | runIncludeFixer("namespace foo2 {\nstr::StrCat b;\n}")); | 
|  | // str::StrCat2 doesn't exist. | 
|  | // In these two cases, StrCat2 is a nested class of class str. | 
|  | EXPECT_EQ("#include \"str.h\"\nnamespace foo2 {\nstr::StrCat2 b;\n}", | 
|  | runIncludeFixer("namespace foo2 {\nstr::StrCat2 b;\n}")); | 
|  | EXPECT_EQ("#include \"str.h\"\nnamespace ns {\nstr::StrCat2 b;\n}", | 
|  | runIncludeFixer("namespace ns {\nstr::StrCat2 b;\n}")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, EnumConstantSymbols) { | 
|  | EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n", | 
|  | runIncludeFixer("int test = a::b::Green;\n")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, IgnoreSymbolFromHeader) { | 
|  | std::string Code = "#include \"header.h\""; | 
|  | EXPECT_EQ(Code, runIncludeFixer(Code)); | 
|  | } | 
|  |  | 
|  | // FIXME: add test cases for inserting and sorting multiple headers when | 
|  | // include-fixer supports multiple headers insertion. | 
|  | TEST(IncludeFixer, InsertAndSortSingleHeader) { | 
|  | // Insert one header. | 
|  | std::string Code = "#include \"a.h\"\n" | 
|  | "#include \"foo.h\"\n" | 
|  | "\n" | 
|  | "namespace a {\nb::bar b;\n}\n"; | 
|  | std::string Expected = "#include \"a.h\"\n" | 
|  | "#include \"bar.h\"\n" | 
|  | "#include \"foo.h\"\n" | 
|  | "\n" | 
|  | "namespace a {\nb::bar b;\n}\n"; | 
|  | EXPECT_EQ(Expected, runIncludeFixer(Code)); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, DoNotDeleteMatchedSymbol) { | 
|  | EXPECT_EQ("#include \"Vector.h\"\na::Vector v;", | 
|  | runIncludeFixer("a::Vector v;")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, FixNamespaceQualifiers) { | 
|  | EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", | 
|  | runIncludeFixer("b::bar b;\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", | 
|  | runIncludeFixer("a::b::bar b;\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n", | 
|  | runIncludeFixer("bar b;\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n", | 
|  | runIncludeFixer("namespace a {\nb::bar b;\n}\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n", | 
|  | runIncludeFixer("namespace a {\nbar b;\n}\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace b{\nbar b;\n}\n}\n", | 
|  | runIncludeFixer("namespace a {\nnamespace b{\nbar b;\n}\n}\n")); | 
|  | EXPECT_EQ("c::b::bar b;\n", | 
|  | runIncludeFixer("c::b::bar b;\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar b;\n}\n", | 
|  | runIncludeFixer("namespace d {\nbar b;\n}\n")); | 
|  | EXPECT_EQ("#include \"bar2.h\"\nnamespace c {\na::c::bar b;\n}\n", | 
|  | runIncludeFixer("namespace c {\nbar b;\n}\n")); | 
|  |  | 
|  | // Test common qualifers reduction. | 
|  | EXPECT_EQ( | 
|  | "#include \"bar.h\"\nnamespace a {\nnamespace d {\nb::bar b;\n}\n}\n", | 
|  | runIncludeFixer("namespace a {\nnamespace d {\nbar b;\n}\n}\n")); | 
|  | EXPECT_EQ( | 
|  | "#include \"bar.h\"\nnamespace d {\nnamespace a {\na::b::bar b;\n}\n}\n", | 
|  | runIncludeFixer("namespace d {\nnamespace a {\nbar b;\n}\n}\n")); | 
|  |  | 
|  | // Test nested classes. | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar::t b;\n}\n", | 
|  | runIncludeFixer("namespace d {\nbar::t b;\n}\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace c {\na::b::bar::t b;\n}\n", | 
|  | runIncludeFixer("namespace c {\nbar::t b;\n}\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar::t b;\n}\n", | 
|  | runIncludeFixer("namespace a {\nbar::t b;\n}\n")); | 
|  |  | 
|  | EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n", | 
|  | runIncludeFixer("int test = Green;\n")); | 
|  | EXPECT_EQ("#include \"color.h\"\nnamespace d {\nint test = a::b::Green;\n}\n", | 
|  | runIncludeFixer("namespace d {\nint test = Green;\n}\n")); | 
|  | EXPECT_EQ("#include \"color.h\"\nnamespace a {\nint test = b::Green;\n}\n", | 
|  | runIncludeFixer("namespace a {\nint test = Green;\n}\n")); | 
|  |  | 
|  | // Test global scope operator. | 
|  | EXPECT_EQ("#include \"bar.h\"\n::a::b::bar b;\n", | 
|  | runIncludeFixer("::a::b::bar b;\n")); | 
|  | EXPECT_EQ("#include \"bar.h\"\nnamespace a {\n::a::b::bar b;\n}\n", | 
|  | runIncludeFixer("namespace a {\n::a::b::bar b;\n}\n")); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, FixNamespaceQualifiersForAllInstances) { | 
|  | const char TestCode[] = R"( | 
|  | namespace a { | 
|  | bar b; | 
|  | int func1() { | 
|  | bar a; | 
|  | bar *p = new bar(); | 
|  | return 0; | 
|  | } | 
|  | } // namespace a | 
|  |  | 
|  | namespace a { | 
|  | bar func2() { | 
|  | bar f; | 
|  | return f; | 
|  | } | 
|  | } // namespace a | 
|  |  | 
|  | // Non-fixed cases: | 
|  | void f() { | 
|  | bar b; | 
|  | } | 
|  |  | 
|  | namespace a { | 
|  | namespace c { | 
|  | bar b; | 
|  | } // namespace c | 
|  | } // namespace a | 
|  | )"; | 
|  |  | 
|  | const char ExpectedCode[] = R"( | 
|  | #include "bar.h" | 
|  | namespace a { | 
|  | b::bar b; | 
|  | int func1() { | 
|  | b::bar a; | 
|  | b::bar *p = new b::bar(); | 
|  | return 0; | 
|  | } | 
|  | } // namespace a | 
|  |  | 
|  | namespace a { | 
|  | b::bar func2() { | 
|  | b::bar f; | 
|  | return f; | 
|  | } | 
|  | } // namespace a | 
|  |  | 
|  | // Non-fixed cases: | 
|  | void f() { | 
|  | bar b; | 
|  | } | 
|  |  | 
|  | namespace a { | 
|  | namespace c { | 
|  | bar b; | 
|  | } // namespace c | 
|  | } // namespace a | 
|  | )"; | 
|  |  | 
|  | EXPECT_EQ(ExpectedCode, runIncludeFixer(TestCode)); | 
|  | } | 
|  |  | 
|  | TEST(IncludeFixer, DontAddQualifiersForMissingCompleteType) { | 
|  | EXPECT_EQ("#include \"bar.h\"\nclass bar;\nvoid f() {\nbar* b;\nb->f();\n}", | 
|  | runIncludeFixer("class bar;\nvoid f() {\nbar* b;\nb->f();\n}")); | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  | } // namespace include_fixer | 
|  | } // namespace clang |