[AMDGPU][AsmParser][NFC] Translate parsed MIMG instructions to MCInsts automatically.
[llvm-project.git] / clang-tools-extra / clangd / unittests / IncludeCleanerTests.cpp
blobaefdba05dace7a7d77679e46e74964e4e8d913ed
1 //===--- IncludeCleanerTests.cpp --------------------------------*- C++ -*-===//
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 //===----------------------------------------------------------------------===//
9 #include "Annotations.h"
10 #include "Diagnostics.h"
11 #include "IncludeCleaner.h"
12 #include "ParsedAST.h"
13 #include "SourceCode.h"
14 #include "TestFS.h"
15 #include "TestTU.h"
16 #include "clang-include-cleaner/Analysis.h"
17 #include "clang-include-cleaner/Types.h"
18 #include "support/Context.h"
19 #include "clang/AST/DeclBase.h"
20 #include "clang/Basic/SourceManager.h"
21 #include "clang/Tooling/Syntax/Tokens.h"
22 #include "llvm/ADT/ScopeExit.h"
23 #include "llvm/ADT/StringRef.h"
24 #include "llvm/Support/Casting.h"
25 #include "llvm/Support/Error.h"
26 #include "llvm/Support/ScopedPrinter.h"
27 #include "llvm/Testing/Support/SupportHelpers.h"
28 #include "gmock/gmock.h"
29 #include "gtest/gtest.h"
30 #include <cstddef>
31 #include <optional>
32 #include <string>
33 #include <utility>
34 #include <vector>
36 namespace clang {
37 namespace clangd {
38 namespace {
40 using ::testing::AllOf;
41 using ::testing::ElementsAre;
42 using ::testing::IsEmpty;
43 using ::testing::Matcher;
44 using ::testing::Pointee;
45 using ::testing::UnorderedElementsAre;
47 Matcher<const Diag &>
48 withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
49 return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
52 MATCHER_P2(Diag, Range, Message,
53 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
54 return arg.Range == Range && arg.Message == Message;
57 MATCHER_P3(Fix, Range, Replacement, Message,
58 "Fix " + llvm::to_string(Range) + " => " +
59 ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
60 return arg.Message == Message && arg.Edits.size() == 1 &&
61 arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
63 MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
65 std::string guard(llvm::StringRef Code) {
66 return "#pragma once\n" + Code.str();
69 MATCHER_P(writtenInclusion, Written, "") {
70 if (arg.Written != Written)
71 *result_listener << arg.Written;
72 return arg.Written == Written;
75 TEST(IncludeCleaner, StdlibUnused) {
76 setIncludeCleanerAnalyzesStdlib(true);
77 auto Cleanup =
78 llvm::make_scope_exit([] { setIncludeCleanerAnalyzesStdlib(false); });
80 auto TU = TestTU::withCode(R"cpp(
81 #include <list>
82 #include <queue>
83 std::list<int> x;
84 )cpp");
85 // Layout of std library impl is not relevant.
86 TU.AdditionalFiles["bits"] = R"cpp(
87 #pragma once
88 namespace std {
89 template <typename> class list {};
90 template <typename> class queue {};
92 )cpp";
93 TU.AdditionalFiles["list"] = "#include <bits>";
94 TU.AdditionalFiles["queue"] = "#include <bits>";
95 TU.ExtraArgs = {"-isystem", testRoot()};
96 auto AST = TU.build();
97 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
98 EXPECT_THAT(Findings.UnusedIncludes,
99 ElementsAre(Pointee(writtenInclusion("<queue>"))));
102 TEST(IncludeCleaner, GetUnusedHeaders) {
103 llvm::StringLiteral MainFile = R"cpp(
104 #include "a.h"
105 #include "b.h"
106 #include "dir/c.h"
107 #include "dir/unused.h"
108 #include "unguarded.h"
109 #include "unused.h"
110 #include <system_header.h>
111 void foo() {
112 a();
113 b();
114 c();
115 })cpp";
116 // Build expected ast with symbols coming from headers.
117 TestTU TU;
118 TU.Filename = "foo.cpp";
119 TU.AdditionalFiles["foo.h"] = guard("void foo();");
120 TU.AdditionalFiles["a.h"] = guard("void a();");
121 TU.AdditionalFiles["b.h"] = guard("void b();");
122 TU.AdditionalFiles["dir/c.h"] = guard("void c();");
123 TU.AdditionalFiles["unused.h"] = guard("void unused();");
124 TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();");
125 TU.AdditionalFiles["system/system_header.h"] = guard("");
126 TU.AdditionalFiles["unguarded.h"] = "";
127 TU.ExtraArgs.push_back("-I" + testPath("dir"));
128 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
129 TU.Code = MainFile.str();
130 ParsedAST AST = TU.build();
131 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
132 EXPECT_THAT(
133 Findings.UnusedIncludes,
134 UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
135 Pointee(writtenInclusion("\"dir/unused.h\""))));
138 TEST(IncludeCleaner, ComputeMissingHeaders) {
139 Annotations MainFile(R"cpp(
140 #include "a.h"
142 void foo() {
143 $b[[b]]();
144 })cpp");
145 TestTU TU;
146 TU.Filename = "foo.cpp";
147 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
148 TU.AdditionalFiles["b.h"] = guard("void b();");
150 TU.Code = MainFile.code();
151 ParsedAST AST = TU.build();
153 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
154 const SourceManager &SM = AST.getSourceManager();
155 const NamedDecl *BDecl = nullptr;
156 for (Decl *D : AST.getASTContext().getTranslationUnitDecl()->decls()) {
157 const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D);
158 std::string Name = CandidateDecl->getQualifiedNameAsString();
159 if (Name != "b")
160 continue;
161 BDecl = CandidateDecl;
163 ASSERT_TRUE(BDecl);
164 include_cleaner::Symbol B{*BDecl};
165 auto Range = MainFile.range("b");
166 size_t Start = llvm::cantFail(positionToOffset(MainFile.code(), Range.start));
167 size_t End = llvm::cantFail(positionToOffset(MainFile.code(), Range.end));
168 syntax::FileRange BRange{SM.getMainFileID(), static_cast<unsigned int>(Start),
169 static_cast<unsigned int>(End)};
170 include_cleaner::Header Header{*SM.getFileManager().getFile("b.h")};
171 MissingIncludeDiagInfo BInfo{B, BRange, {Header}};
172 EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
175 TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
176 Annotations MainFile(R"cpp(
177 #include "a.h"
178 #include "all.h"
179 $insert_b[[]]#include "baz.h"
180 #include "dir/c.h"
181 $insert_d[[]]$insert_foo[[]]#include "fuzz.h"
182 #include "header.h"
183 $insert_foobar[[]]#include <e.h>
184 $insert_f[[]]$insert_vector[[]]
186 #define DEF(X) const Foo *X;
187 #define BAZ(X) const X x
189 // No missing include insertion for ambiguous macro refs.
190 #if defined(FOO)
191 #endif
193 void foo() {
194 $b[[b]]();
196 ns::$bar[[Bar]] bar;
197 bar.d();
198 $f[[f]]();
200 // this should not be diagnosed, because it's ignored in the config
201 buzz();
203 $foobar[[foobar]]();
205 std::$vector[[vector]] v;
207 int var = $FOO[[FOO]];
209 $DEF[[DEF]](a);
211 $BAR[[BAR]](b);
213 BAZ($Foo[[Foo]]);
214 })cpp");
216 TestTU TU;
217 TU.Filename = "main.cpp";
218 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
219 TU.AdditionalFiles["b.h"] = guard("void b();");
221 TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\"");
222 TU.AdditionalFiles["dir/d.h"] =
223 guard("namespace ns { struct Bar { void d(); }; }");
225 TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>");
226 TU.AdditionalFiles["system/f.h"] = guard("void f();");
227 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
229 TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\"");
230 TU.AdditionalFiles["buzz.h"] = guard("void buzz();");
232 TU.AdditionalFiles["baz.h"] = guard("#include \"private.h\"");
233 TU.AdditionalFiles["private.h"] = guard(R"cpp(
234 // IWYU pragma: private, include "public.h"
235 void foobar();
236 )cpp");
237 TU.AdditionalFiles["header.h"] = guard(R"cpp(
238 namespace std { class vector {}; }
239 )cpp");
241 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
242 TU.AdditionalFiles["foo.h"] = guard(R"cpp(
243 #define BAR(x) Foo *x
244 #define FOO 1
245 struct Foo{};
246 )cpp");
248 TU.Code = MainFile.code();
249 ParsedAST AST = TU.build();
251 auto Findings = computeIncludeCleanerFindings(AST);
252 Findings.UnusedIncludes.clear();
253 std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics(
254 AST, TU.Code, Findings,
255 {[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }});
256 EXPECT_THAT(
257 Diags,
258 UnorderedElementsAre(
259 AllOf(Diag(MainFile.range("b"),
260 "No header providing \"b\" is directly included"),
261 withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
262 "#include \"b.h\""),
263 FixMessage("add all missing includes")})),
264 AllOf(Diag(MainFile.range("bar"),
265 "No header providing \"ns::Bar\" is directly included"),
266 withFix({Fix(MainFile.range("insert_d"),
267 "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
268 FixMessage("add all missing includes")})),
269 AllOf(Diag(MainFile.range("f"),
270 "No header providing \"f\" is directly included"),
271 withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
272 "#include <f.h>"),
273 FixMessage("add all missing includes")})),
274 AllOf(
275 Diag(MainFile.range("foobar"),
276 "No header providing \"foobar\" is directly included"),
277 withFix({Fix(MainFile.range("insert_foobar"),
278 "#include \"public.h\"\n", "#include \"public.h\""),
279 FixMessage("add all missing includes")})),
280 AllOf(
281 Diag(MainFile.range("vector"),
282 "No header providing \"std::vector\" is directly included"),
283 withFix({
284 Fix(MainFile.range("insert_vector"), "#include <vector>\n",
285 "#include <vector>"),
286 FixMessage("add all missing includes"),
287 })),
288 AllOf(Diag(MainFile.range("FOO"),
289 "No header providing \"FOO\" is directly included"),
290 withFix({Fix(MainFile.range("insert_foo"),
291 "#include \"foo.h\"\n", "#include \"foo.h\""),
292 FixMessage("add all missing includes")})),
293 AllOf(Diag(MainFile.range("DEF"),
294 "No header providing \"Foo\" is directly included"),
295 withFix({Fix(MainFile.range("insert_foo"),
296 "#include \"foo.h\"\n", "#include \"foo.h\""),
297 FixMessage("add all missing includes")})),
298 AllOf(Diag(MainFile.range("BAR"),
299 "No header providing \"BAR\" is directly included"),
300 withFix({Fix(MainFile.range("insert_foo"),
301 "#include \"foo.h\"\n", "#include \"foo.h\""),
302 FixMessage("add all missing includes")})),
303 AllOf(Diag(MainFile.range("Foo"),
304 "No header providing \"Foo\" is directly included"),
305 withFix({Fix(MainFile.range("insert_foo"),
306 "#include \"foo.h\"\n", "#include \"foo.h\""),
307 FixMessage("add all missing includes")}))));
310 TEST(IncludeCleaner, IWYUPragmas) {
311 TestTU TU;
312 TU.Code = R"cpp(
313 #include "behind_keep.h" // IWYU pragma: keep
314 #include "exported.h" // IWYU pragma: export
315 #include "public.h"
317 void bar() { foo(); }
318 )cpp";
319 TU.AdditionalFiles["behind_keep.h"] = guard("");
320 TU.AdditionalFiles["exported.h"] = guard("");
321 TU.AdditionalFiles["public.h"] = guard("#include \"private.h\"");
322 TU.AdditionalFiles["private.h"] = guard(R"cpp(
323 // IWYU pragma: private, include "public.h"
324 void foo() {}
325 )cpp");
326 ParsedAST AST = TU.build();
327 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
328 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
331 TEST(IncludeCleaner, IWYUPragmaExport) {
332 TestTU TU;
333 TU.Code = R"cpp(
334 #include "foo.h"
335 )cpp";
336 TU.AdditionalFiles["foo.h"] = R"cpp(
337 #ifndef FOO_H
338 #define FOO_H
340 #include "bar.h" // IWYU pragma: export
342 #endif
343 )cpp";
344 TU.AdditionalFiles["bar.h"] = guard(R"cpp(
345 void bar() {}
346 )cpp");
347 ParsedAST AST = TU.build();
349 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
350 EXPECT_THAT(Findings.UnusedIncludes,
351 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
354 TEST(IncludeCleaner, NoDiagsForObjC) {
355 TestTU TU;
356 TU.Code = R"cpp(
357 #include "foo.h"
359 void bar() {}
360 )cpp";
361 TU.AdditionalFiles["foo.h"] = R"cpp(
362 #ifndef FOO_H
363 #define FOO_H
365 #endif
366 )cpp";
367 TU.ExtraArgs.emplace_back("-xobjective-c");
369 ParsedAST AST = TU.build();
370 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
371 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
372 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
375 TEST(IncludeCleaner, UmbrellaUsesPrivate) {
376 TestTU TU;
377 TU.Code = R"cpp(
378 #include "private.h"
379 )cpp";
380 TU.AdditionalFiles["private.h"] = guard(R"cpp(
381 // IWYU pragma: private, include "public.h"
382 void foo() {}
383 )cpp");
384 TU.Filename = "public.h";
385 ParsedAST AST = TU.build();
386 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
387 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
390 TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
391 Annotations MainFile(R"cpp(
392 #include "all.h"
393 #define FOO(X) const Foo *X
394 void foo() {
395 #include [["expander.inc"]]
397 )cpp");
399 TestTU TU;
400 TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);");
401 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
402 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
404 TU.Code = MainFile.code();
405 ParsedAST AST = TU.build();
407 auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
408 EXPECT_THAT(Findings, testing::SizeIs(1));
409 auto RefRange = Findings.front().SymRefRange;
410 auto &SM = AST.getSourceManager();
411 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
412 // FIXME: Point at the spelling location, rather than the include.
413 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
416 TEST(IncludeCleaner, MissingIncludesAreUnique) {
417 Annotations MainFile(R"cpp(
418 #include "all.h"
419 FOO([[Foo]]);
420 )cpp");
422 TestTU TU;
423 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
424 TU.AdditionalFiles["all.h"] = guard(R"cpp(
425 #include "foo.h"
426 #define FOO(X) X y; X z
427 )cpp");
429 TU.Code = MainFile.code();
430 ParsedAST AST = TU.build();
432 auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes;
433 EXPECT_THAT(Findings, testing::SizeIs(1));
434 auto RefRange = Findings.front().SymRefRange;
435 auto &SM = AST.getSourceManager();
436 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
437 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
440 TEST(IncludeCleaner, NoCrash) {
441 TestTU TU;
442 Annotations MainCode(R"cpp(
443 #include "all.h"
444 void test() {
445 [[1s]];
447 )cpp");
448 TU.Code = MainCode.code();
449 TU.AdditionalFiles["foo.h"] =
450 guard("int operator\"\"s(unsigned long long) { return 0; }");
451 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
452 ParsedAST AST = TU.build();
453 const auto &MissingIncludes =
454 computeIncludeCleanerFindings(AST).MissingIncludes;
455 EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
456 auto &SM = AST.getSourceManager();
457 EXPECT_EQ(
458 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
459 MainCode.range());
462 TEST(IncludeCleaner, FirstMatchedProvider) {
463 struct {
464 const char *Code;
465 const std::vector<include_cleaner::Header> Providers;
466 const std::optional<include_cleaner::Header> ExpectedProvider;
467 } Cases[] = {
468 {R"cpp(
469 #include "bar.h"
470 #include "foo.h"
471 )cpp",
472 {include_cleaner::Header{"bar.h"}, include_cleaner::Header{"foo.h"}},
473 include_cleaner::Header{"bar.h"}},
474 {R"cpp(
475 #include "bar.h"
476 #include "foo.h"
477 )cpp",
478 {include_cleaner::Header{"foo.h"}, include_cleaner::Header{"bar.h"}},
479 include_cleaner::Header{"foo.h"}},
480 {"#include \"bar.h\"",
481 {include_cleaner::Header{"bar.h"}},
482 include_cleaner::Header{"bar.h"}},
483 {"#include \"bar.h\"", {include_cleaner::Header{"foo.h"}}, std::nullopt},
484 {"#include \"bar.h\"", {}, std::nullopt}};
485 for (const auto &Case : Cases) {
486 Annotations Code{Case.Code};
487 SCOPED_TRACE(Code.code());
489 TestTU TU;
490 TU.Code = Code.code();
491 TU.AdditionalFiles["bar.h"] = "";
492 TU.AdditionalFiles["foo.h"] = "";
494 auto AST = TU.build();
495 std::optional<include_cleaner::Header> MatchedProvider =
496 firstMatchedProvider(
497 convertIncludes(AST.getSourceManager(),
498 AST.getIncludeStructure().MainFileIncludes),
499 Case.Providers);
500 EXPECT_EQ(MatchedProvider, Case.ExpectedProvider);
504 TEST(IncludeCleaner, BatchFix) {
505 TestTU TU;
506 TU.Filename = "main.cpp";
507 TU.AdditionalFiles["foo.h"] = guard("class Foo;");
508 TU.AdditionalFiles["bar.h"] = guard("class Bar;");
509 TU.AdditionalFiles["all.h"] = guard(R"cpp(
510 #include "foo.h"
511 #include "bar.h"
512 )cpp");
514 TU.Code = R"cpp(
515 #include "all.h"
517 Foo* foo;
518 )cpp";
519 auto AST = TU.build();
520 EXPECT_THAT(
521 issueIncludeCleanerDiagnostics(AST, TU.Code,
522 computeIncludeCleanerFindings(AST)),
523 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
524 FixMessage("fix all includes")}),
525 withFix({FixMessage("remove #include directive"),
526 FixMessage("fix all includes")})));
528 TU.Code = R"cpp(
529 #include "all.h"
530 #include "bar.h"
532 Foo* foo;
533 )cpp";
534 AST = TU.build();
535 EXPECT_THAT(
536 issueIncludeCleanerDiagnostics(AST, TU.Code,
537 computeIncludeCleanerFindings(AST)),
538 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
539 FixMessage("fix all includes")}),
540 withFix({FixMessage("remove #include directive"),
541 FixMessage("remove all unused includes"),
542 FixMessage("fix all includes")}),
543 withFix({FixMessage("remove #include directive"),
544 FixMessage("remove all unused includes"),
545 FixMessage("fix all includes")})));
547 TU.Code = R"cpp(
548 #include "all.h"
550 Foo* foo;
551 Bar* bar;
552 )cpp";
553 AST = TU.build();
554 EXPECT_THAT(
555 issueIncludeCleanerDiagnostics(AST, TU.Code,
556 computeIncludeCleanerFindings(AST)),
557 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
558 FixMessage("add all missing includes"),
559 FixMessage("fix all includes")}),
560 withFix({FixMessage("#include \"bar.h\""),
561 FixMessage("add all missing includes"),
562 FixMessage("fix all includes")}),
563 withFix({FixMessage("remove #include directive"),
564 FixMessage("fix all includes")})));
567 } // namespace
568 } // namespace clangd
569 } // namespace clang