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-exception |
6 | // |
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 | |
16 | namespace clang { |
17 | namespace include_fixer { |
18 | namespace { |
19 | |
20 | using find_all_symbols::SymbolInfo; |
21 | using find_all_symbols::SymbolAndSignals; |
22 | |
23 | static bool runOnCode(tooling::ToolAction *ToolAction, StringRef Code, |
24 | StringRef FileName, |
25 | const std::vector<std::string> &) { |
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(position: Args.end(), first: ExtraArgs.begin(), last: ExtraArgs.end()); |
35 | tooling::ToolInvocation Invocation( |
36 | Args, ToolAction, Files.get(), |
37 | std::make_shared<PCHContainerOperations>()); |
38 | |
39 | InMemoryFileSystem->addFile(Path: FileName, ModificationTime: 0, |
40 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: Code)); |
41 | |
42 | InMemoryFileSystem->addFile(Path: "foo.h" , ModificationTime: 0, |
43 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
44 | InMemoryFileSystem->addFile(Path: "dir/bar.h" , ModificationTime: 0, |
45 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
46 | InMemoryFileSystem->addFile(Path: "dir/otherdir/qux.h" , ModificationTime: 0, |
47 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
48 | InMemoryFileSystem->addFile(Path: "header.h" , ModificationTime: 0, |
49 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "bar b;" )); |
50 | return Invocation.run(); |
51 | } |
52 | |
53 | static std::string runIncludeFixer( |
54 | StringRef Code, |
55 | const std::vector<std::string> & = std::vector<std::string>()) { |
56 | std::vector<SymbolAndSignals> Symbols = { |
57 | {.Symbol: SymbolInfo("string" , SymbolInfo::SymbolKind::Class, "<string>" , |
58 | {{SymbolInfo::ContextType::Namespace, "std" }}), |
59 | .Signals: SymbolInfo::Signals{}}, |
60 | {.Symbol: SymbolInfo("sting" , SymbolInfo::SymbolKind::Class, "\"sting\"" , |
61 | {{SymbolInfo::ContextType::Namespace, "std" }}), |
62 | .Signals: SymbolInfo::Signals{}}, |
63 | {.Symbol: SymbolInfo("foo" , SymbolInfo::SymbolKind::Class, |
64 | "\"dir/otherdir/qux.h\"" , |
65 | {{SymbolInfo::ContextType::Namespace, "b" }, |
66 | {SymbolInfo::ContextType::Namespace, "a" }}), |
67 | .Signals: SymbolInfo::Signals{}}, |
68 | {.Symbol: SymbolInfo("bar" , SymbolInfo::SymbolKind::Class, "\"bar.h\"" , |
69 | {{SymbolInfo::ContextType::Namespace, "b" }, |
70 | {SymbolInfo::ContextType::Namespace, "a" }}), |
71 | .Signals: SymbolInfo::Signals{}}, |
72 | {.Symbol: SymbolInfo("bar" , SymbolInfo::SymbolKind::Class, "\"bar2.h\"" , |
73 | {{SymbolInfo::ContextType::Namespace, "c" }, |
74 | {SymbolInfo::ContextType::Namespace, "a" }}), |
75 | .Signals: SymbolInfo::Signals{}}, |
76 | {.Symbol: SymbolInfo("Green" , SymbolInfo::SymbolKind::Class, "\"color.h\"" , |
77 | {{SymbolInfo::ContextType::EnumDecl, "Color" }, |
78 | {SymbolInfo::ContextType::Namespace, "b" }, |
79 | {SymbolInfo::ContextType::Namespace, "a" }}), |
80 | .Signals: SymbolInfo::Signals{}}, |
81 | {.Symbol: SymbolInfo("Vector" , SymbolInfo::SymbolKind::Class, "\"Vector.h\"" , |
82 | {{SymbolInfo::ContextType::Namespace, "__a" }, |
83 | {SymbolInfo::ContextType::Namespace, "a" }}), |
84 | .Signals: SymbolInfo::Signals{/*Seen=*/2, 0}}, |
85 | {.Symbol: SymbolInfo("Vector" , SymbolInfo::SymbolKind::Class, "\"Vector.h\"" , |
86 | {{SymbolInfo::ContextType::Namespace, "a" }}), |
87 | .Signals: SymbolInfo::Signals{/*Seen=*/2, 0}}, |
88 | {.Symbol: SymbolInfo("StrCat" , SymbolInfo::SymbolKind::Class, "\"strcat.h\"" , |
89 | {{SymbolInfo::ContextType::Namespace, "str" }}), |
90 | .Signals: SymbolInfo::Signals{}}, |
91 | {.Symbol: SymbolInfo("str" , SymbolInfo::SymbolKind::Class, "\"str.h\"" , {}), |
92 | .Signals: SymbolInfo::Signals{}}, |
93 | {.Symbol: SymbolInfo("foo2" , SymbolInfo::SymbolKind::Class, "\"foo2.h\"" , {}), |
94 | .Signals: SymbolInfo::Signals{}}, |
95 | }; |
96 | auto SymbolIndexMgr = std::make_unique<SymbolIndexManager>(); |
97 | SymbolIndexMgr->addSymbolIndex( |
98 | F: [=]() { return std::make_unique<InMemorySymbolIndex>(args: Symbols); }); |
99 | |
100 | std::vector<IncludeFixerContext> FixerContexts; |
101 | IncludeFixerActionFactory Factory(*SymbolIndexMgr, FixerContexts, "llvm" ); |
102 | std::string FakeFileName = "input.cc" ; |
103 | runOnCode(ToolAction: &Factory, Code, FileName: FakeFileName, ExtraArgs); |
104 | assert(FixerContexts.size() == 1); |
105 | if (FixerContexts.front().getHeaderInfos().empty()) |
106 | return std::string(Code); |
107 | auto Replaces = createIncludeFixerReplacements(Code, Context: FixerContexts.front()); |
108 | EXPECT_TRUE(static_cast<bool>(Replaces)) |
109 | << llvm::toString(E: Replaces.takeError()) << "\n" ; |
110 | if (!Replaces) |
111 | return "" ; |
112 | RewriterTestContext Context; |
113 | FileID ID = Context.createInMemoryFile(Name: FakeFileName, Content: Code); |
114 | tooling::applyAllReplacements(Replaces: *Replaces, Rewrite&: Context.Rewrite); |
115 | return Context.getRewrittenText(ID); |
116 | } |
117 | |
118 | TEST(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 | |
141 | TEST(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 | |
153 | TEST(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 | |
171 | TEST(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-fixer |
183 | // 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 | |
189 | TEST(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 | |
194 | TEST(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 support |
205 | // 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 | |
220 | TEST(IncludeFixer, EnumConstantSymbols) { |
221 | EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n" , |
222 | runIncludeFixer("int test = a::b::Green;\n" )); |
223 | } |
224 | |
225 | TEST(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 when |
231 | // clang-include-fixer supports multiple headers insertion. |
232 | TEST(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 | |
246 | TEST(IncludeFixer, DoNotDeleteMatchedSymbol) { |
247 | EXPECT_EQ("#include \"Vector.h\"\na::Vector v;" , |
248 | runIncludeFixer("a::Vector v;" )); |
249 | } |
250 | |
251 | TEST(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 | |
302 | TEST(IncludeFixer, FixNamespaceQualifiersForAllInstances) { |
303 | const char TestCode[] = R"( |
304 | namespace a { |
305 | bar b; |
306 | int func1() { |
307 | bar a; |
308 | bar *p = new bar(); |
309 | return 0; |
310 | } |
311 | } // namespace a |
312 | |
313 | namespace a { |
314 | bar func2() { |
315 | bar f; |
316 | return f; |
317 | } |
318 | } // namespace a |
319 | |
320 | // Non-fixed cases: |
321 | void f() { |
322 | bar b; |
323 | } |
324 | |
325 | namespace a { |
326 | namespace c { |
327 | bar b; |
328 | } // namespace c |
329 | } // namespace a |
330 | )" ; |
331 | |
332 | const char ExpectedCode[] = R"( |
333 | #include "bar.h" |
334 | namespace a { |
335 | b::bar b; |
336 | int func1() { |
337 | b::bar a; |
338 | b::bar *p = new b::bar(); |
339 | return 0; |
340 | } |
341 | } // namespace a |
342 | |
343 | namespace a { |
344 | b::bar func2() { |
345 | b::bar f; |
346 | return f; |
347 | } |
348 | } // namespace a |
349 | |
350 | // Non-fixed cases: |
351 | void f() { |
352 | bar b; |
353 | } |
354 | |
355 | namespace a { |
356 | namespace c { |
357 | bar b; |
358 | } // namespace c |
359 | } // namespace a |
360 | )" ; |
361 | |
362 | EXPECT_EQ(ExpectedCode, runIncludeFixer(TestCode)); |
363 | } |
364 | |
365 | TEST(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 | } // namespace |
371 | } // namespace include_fixer |
372 | } // namespace clang |
373 | |