373 lines · cpp
1//===-- IncludeFixerTest.cpp - Include fixer unit tests -------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "InMemorySymbolIndex.h"10#include "IncludeFixer.h"11#include "SymbolIndexManager.h"12#include "unittests/Tooling/RewriterTestContext.h"13#include "clang/Tooling/Tooling.h"14#include "gtest/gtest.h"15 16namespace clang {17namespace include_fixer {18namespace {19 20using find_all_symbols::SymbolInfo;21using find_all_symbols::SymbolAndSignals;22 23static bool runOnCode(tooling::ToolAction *ToolAction, StringRef Code,24 StringRef FileName,25 const std::vector<std::string> &ExtraArgs) {26 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(27 new llvm::vfs::InMemoryFileSystem);28 llvm::IntrusiveRefCntPtr<FileManager> Files(29 new FileManager(FileSystemOptions(), InMemoryFileSystem));30 // FIXME: Investigate why -fms-compatibility breaks tests.31 std::vector<std::string> Args = {"include_fixer", "-fsyntax-only",32 "-fno-ms-compatibility",33 std::string(FileName)};34 Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());35 tooling::ToolInvocation Invocation(36 Args, ToolAction, Files.get(),37 std::make_shared<PCHContainerOperations>());38 39 InMemoryFileSystem->addFile(FileName, 0,40 llvm::MemoryBuffer::getMemBuffer(Code));41 42 InMemoryFileSystem->addFile("foo.h", 0,43 llvm::MemoryBuffer::getMemBuffer("\n"));44 InMemoryFileSystem->addFile("dir/bar.h", 0,45 llvm::MemoryBuffer::getMemBuffer("\n"));46 InMemoryFileSystem->addFile("dir/otherdir/qux.h", 0,47 llvm::MemoryBuffer::getMemBuffer("\n"));48 InMemoryFileSystem->addFile("header.h", 0,49 llvm::MemoryBuffer::getMemBuffer("bar b;"));50 return Invocation.run();51}52 53static std::string runIncludeFixer(54 StringRef Code,55 const std::vector<std::string> &ExtraArgs = std::vector<std::string>()) {56 std::vector<SymbolAndSignals> Symbols = {57 {SymbolInfo("string", SymbolInfo::SymbolKind::Class, "<string>",58 {{SymbolInfo::ContextType::Namespace, "std"}}),59 SymbolInfo::Signals{}},60 {SymbolInfo("sting", SymbolInfo::SymbolKind::Class, "\"sting\"",61 {{SymbolInfo::ContextType::Namespace, "std"}}),62 SymbolInfo::Signals{}},63 {SymbolInfo("foo", SymbolInfo::SymbolKind::Class,64 "\"dir/otherdir/qux.h\"",65 {{SymbolInfo::ContextType::Namespace, "b"},66 {SymbolInfo::ContextType::Namespace, "a"}}),67 SymbolInfo::Signals{}},68 {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar.h\"",69 {{SymbolInfo::ContextType::Namespace, "b"},70 {SymbolInfo::ContextType::Namespace, "a"}}),71 SymbolInfo::Signals{}},72 {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar2.h\"",73 {{SymbolInfo::ContextType::Namespace, "c"},74 {SymbolInfo::ContextType::Namespace, "a"}}),75 SymbolInfo::Signals{}},76 {SymbolInfo("Green", SymbolInfo::SymbolKind::Class, "\"color.h\"",77 {{SymbolInfo::ContextType::EnumDecl, "Color"},78 {SymbolInfo::ContextType::Namespace, "b"},79 {SymbolInfo::ContextType::Namespace, "a"}}),80 SymbolInfo::Signals{}},81 {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"",82 {{SymbolInfo::ContextType::Namespace, "__a"},83 {SymbolInfo::ContextType::Namespace, "a"}}),84 SymbolInfo::Signals{/*Seen=*/2, 0}},85 {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"",86 {{SymbolInfo::ContextType::Namespace, "a"}}),87 SymbolInfo::Signals{/*Seen=*/2, 0}},88 {SymbolInfo("StrCat", SymbolInfo::SymbolKind::Class, "\"strcat.h\"",89 {{SymbolInfo::ContextType::Namespace, "str"}}),90 SymbolInfo::Signals{}},91 {SymbolInfo("str", SymbolInfo::SymbolKind::Class, "\"str.h\"", {}),92 SymbolInfo::Signals{}},93 {SymbolInfo("foo2", SymbolInfo::SymbolKind::Class, "\"foo2.h\"", {}),94 SymbolInfo::Signals{}},95 };96 auto SymbolIndexMgr = std::make_unique<SymbolIndexManager>();97 SymbolIndexMgr->addSymbolIndex(98 [=]() { return std::make_unique<InMemorySymbolIndex>(Symbols); });99 100 std::vector<IncludeFixerContext> FixerContexts;101 IncludeFixerActionFactory Factory(*SymbolIndexMgr, FixerContexts, "llvm");102 std::string FakeFileName = "input.cc";103 runOnCode(&Factory, Code, FakeFileName, ExtraArgs);104 assert(FixerContexts.size() == 1);105 if (FixerContexts.front().getHeaderInfos().empty())106 return std::string(Code);107 auto Replaces = createIncludeFixerReplacements(Code, FixerContexts.front());108 EXPECT_TRUE(static_cast<bool>(Replaces))109 << llvm::toString(Replaces.takeError()) << "\n";110 if (!Replaces)111 return "";112 RewriterTestContext Context;113 FileID ID = Context.createInMemoryFile(FakeFileName, Code);114 tooling::applyAllReplacements(*Replaces, Context.Rewrite);115 return Context.getRewrittenText(ID);116}117 118TEST(IncludeFixer, Typo) {119 EXPECT_EQ("#include <string>\nstd::string foo;\n",120 runIncludeFixer("std::string foo;\n"));121 122 EXPECT_EQ("// comment\n#include \"foo.h\"\n#include <string>\n"123 "std::string foo;\n#include \"dir/bar.h\"\n",124 runIncludeFixer("// comment\n#include \"foo.h\"\nstd::string foo;\n"125 "#include \"dir/bar.h\"\n"));126 127 EXPECT_EQ("#include \"foo.h\"\n#include <string>\nstd::string foo;\n",128 runIncludeFixer("#include \"foo.h\"\nstd::string foo;\n"));129 130 EXPECT_EQ(131 "#include \"foo.h\"\n#include <string>\nstd::string::size_type foo;\n",132 runIncludeFixer("#include \"foo.h\"\nstd::string::size_type foo;\n"));133 134 EXPECT_EQ("#include <string>\nstd::string foo;\n",135 runIncludeFixer("string foo;\n"));136 137 // Should not match std::string.138 EXPECT_EQ("::string foo;\n", runIncludeFixer("::string foo;\n"));139}140 141TEST(IncludeFixer, IncompleteType) {142 EXPECT_EQ(143 "#include \"foo.h\"\n#include <string>\n"144 "namespace std {\nclass string;\n}\nstd::string foo;\n",145 runIncludeFixer("#include \"foo.h\"\n"146 "namespace std {\nclass string;\n}\nstring foo;\n"));147 148 EXPECT_EQ("#include <string>\n"149 "class string;\ntypedef string foo;\nfoo f;\n",150 runIncludeFixer("class string;\ntypedef string foo;\nfoo f;\n"));151}152 153TEST(IncludeFixer, MinimizeInclude) {154 std::vector<std::string> IncludePath = {"-Idir/"};155 EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n",156 runIncludeFixer("a::b::foo bar;\n", IncludePath));157 158 IncludePath = {"-isystemdir"};159 EXPECT_EQ("#include <otherdir/qux.h>\na::b::foo bar;\n",160 runIncludeFixer("a::b::foo bar;\n", IncludePath));161 162 IncludePath = {"-iquotedir"};163 EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n",164 runIncludeFixer("a::b::foo bar;\n", IncludePath));165 166 IncludePath = {"-Idir", "-Idir/otherdir"};167 EXPECT_EQ("#include \"qux.h\"\na::b::foo bar;\n",168 runIncludeFixer("a::b::foo bar;\n", IncludePath));169}170 171TEST(IncludeFixer, NestedName) {172 EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n"173 "int x = a::b::foo(0);\n",174 runIncludeFixer("int x = a::b::foo(0);\n"));175 176 // FIXME: Handle simple macros.177 EXPECT_EQ("#define FOO a::b::foo\nint x = FOO;\n",178 runIncludeFixer("#define FOO a::b::foo\nint x = FOO;\n"));179 EXPECT_EQ("#define FOO(x) a::##x\nint x = FOO(b::foo);\n",180 runIncludeFixer("#define FOO(x) a::##x\nint x = FOO(b::foo);\n"));181 182 // The empty namespace is cleaned up by clang-format after clang-include-fixer183 // finishes.184 EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n"185 "\nint a = a::b::foo(0);\n",186 runIncludeFixer("namespace a {}\nint a = a::b::foo(0);\n"));187}188 189TEST(IncludeFixer, MultipleMissingSymbols) {190 EXPECT_EQ("#include <string>\nstd::string bar;\nstd::sting foo;\n",191 runIncludeFixer("std::string bar;\nstd::sting foo;\n"));192}193 194TEST(IncludeFixer, ScopedNamespaceSymbols) {195 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}",196 runIncludeFixer("namespace a {\nb::bar b;\n}"));197 EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}",198 runIncludeFixer("namespace A {\na::b::bar b;\n}"));199 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nvoid func() { b::bar b; }\n} "200 "// namespace a",201 runIncludeFixer("namespace a {\nvoid func() { b::bar b; }\n}"));202 EXPECT_EQ("namespace A { c::b::bar b; }\n",203 runIncludeFixer("namespace A { c::b::bar b; }\n"));204 // FIXME: The header should not be added here. Remove this after we support205 // full match.206 EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}",207 runIncludeFixer("namespace A {\nb::bar b;\n}"));208 209 // Finds candidates for "str::StrCat".210 EXPECT_EQ("#include \"strcat.h\"\nnamespace foo2 {\nstr::StrCat b;\n}",211 runIncludeFixer("namespace foo2 {\nstr::StrCat b;\n}"));212 // str::StrCat2 doesn't exist.213 // In these two cases, StrCat2 is a nested class of class str.214 EXPECT_EQ("#include \"str.h\"\nnamespace foo2 {\nstr::StrCat2 b;\n}",215 runIncludeFixer("namespace foo2 {\nstr::StrCat2 b;\n}"));216 EXPECT_EQ("#include \"str.h\"\nnamespace ns {\nstr::StrCat2 b;\n}",217 runIncludeFixer("namespace ns {\nstr::StrCat2 b;\n}"));218}219 220TEST(IncludeFixer, EnumConstantSymbols) {221 EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n",222 runIncludeFixer("int test = a::b::Green;\n"));223}224 225TEST(IncludeFixer, IgnoreSymbolFromHeader) {226 std::string Code = "#include \"header.h\"";227 EXPECT_EQ(Code, runIncludeFixer(Code));228}229 230// FIXME: add test cases for inserting and sorting multiple headers when231// clang-include-fixer supports multiple headers insertion.232TEST(IncludeFixer, InsertAndSortSingleHeader) {233 // Insert one header.234 std::string Code = "#include \"a.h\"\n"235 "#include \"foo.h\"\n"236 "\n"237 "namespace a {\nb::bar b;\n}\n";238 std::string Expected = "#include \"a.h\"\n"239 "#include \"bar.h\"\n"240 "#include \"foo.h\"\n"241 "\n"242 "namespace a {\nb::bar b;\n}\n";243 EXPECT_EQ(Expected, runIncludeFixer(Code));244}245 246TEST(IncludeFixer, DoNotDeleteMatchedSymbol) {247 EXPECT_EQ("#include \"Vector.h\"\na::Vector v;",248 runIncludeFixer("a::Vector v;"));249}250 251TEST(IncludeFixer, FixNamespaceQualifiers) {252 EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n",253 runIncludeFixer("b::bar b;\n"));254 EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n",255 runIncludeFixer("a::b::bar b;\n"));256 EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n",257 runIncludeFixer("bar b;\n"));258 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n",259 runIncludeFixer("namespace a {\nb::bar b;\n}\n"));260 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n",261 runIncludeFixer("namespace a {\nbar b;\n}\n"));262 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace b{\nbar b;\n}\n} "263 "// namespace a\n",264 runIncludeFixer("namespace a {\nnamespace b{\nbar b;\n}\n}\n"));265 EXPECT_EQ("c::b::bar b;\n",266 runIncludeFixer("c::b::bar b;\n"));267 EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar b;\n}\n",268 runIncludeFixer("namespace d {\nbar b;\n}\n"));269 EXPECT_EQ("#include \"bar2.h\"\nnamespace c {\na::c::bar b;\n}\n",270 runIncludeFixer("namespace c {\nbar b;\n}\n"));271 272 // Test common qualifiers reduction.273 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace d {\nb::bar b;\n}\n} "274 "// namespace a\n",275 runIncludeFixer("namespace a {\nnamespace d {\nbar b;\n}\n}\n"));276 EXPECT_EQ("#include \"bar.h\"\nnamespace d {\nnamespace a {\na::b::bar "277 "b;\n}\n} // namespace d\n",278 runIncludeFixer("namespace d {\nnamespace a {\nbar b;\n}\n}\n"));279 280 // Test nested classes.281 EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar::t b;\n}\n",282 runIncludeFixer("namespace d {\nbar::t b;\n}\n"));283 EXPECT_EQ("#include \"bar.h\"\nnamespace c {\na::b::bar::t b;\n}\n",284 runIncludeFixer("namespace c {\nbar::t b;\n}\n"));285 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar::t b;\n}\n",286 runIncludeFixer("namespace a {\nbar::t b;\n}\n"));287 288 EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n",289 runIncludeFixer("int test = Green;\n"));290 EXPECT_EQ("#include \"color.h\"\nnamespace d {\nint test = a::b::Green;\n}\n",291 runIncludeFixer("namespace d {\nint test = Green;\n}\n"));292 EXPECT_EQ("#include \"color.h\"\nnamespace a {\nint test = b::Green;\n}\n",293 runIncludeFixer("namespace a {\nint test = Green;\n}\n"));294 295 // Test global scope operator.296 EXPECT_EQ("#include \"bar.h\"\n::a::b::bar b;\n",297 runIncludeFixer("::a::b::bar b;\n"));298 EXPECT_EQ("#include \"bar.h\"\nnamespace a {\n::a::b::bar b;\n}\n",299 runIncludeFixer("namespace a {\n::a::b::bar b;\n}\n"));300}301 302TEST(IncludeFixer, FixNamespaceQualifiersForAllInstances) {303 const char TestCode[] = R"(304namespace a {305bar b;306int func1() {307 bar a;308 bar *p = new bar();309 return 0;310}311} // namespace a312 313namespace a {314bar func2() {315 bar f;316 return f;317}318} // namespace a319 320// Non-fixed cases:321void f() {322 bar b;323}324 325namespace a {326namespace c {327 bar b;328} // namespace c329} // namespace a330)";331 332 const char ExpectedCode[] = R"(333#include "bar.h"334namespace a {335b::bar b;336int func1() {337 b::bar a;338 b::bar *p = new b::bar();339 return 0;340}341} // namespace a342 343namespace a {344b::bar func2() {345 b::bar f;346 return f;347}348} // namespace a349 350// Non-fixed cases:351void f() {352 bar b;353}354 355namespace a {356namespace c {357 bar b;358} // namespace c359} // namespace a360)";361 362 EXPECT_EQ(ExpectedCode, runIncludeFixer(TestCode));363}364 365TEST(IncludeFixer, DontAddQualifiersForMissingCompleteType) {366 EXPECT_EQ("#include \"bar.h\"\nclass bar;\nvoid f() {\nbar* b;\nb->f();\n}",367 runIncludeFixer("class bar;\nvoid f() {\nbar* b;\nb->f();\n}"));368}369 370} // namespace371} // namespace include_fixer372} // namespace clang373