1 //===-- RecordTest.cpp ----------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "clang-include-cleaner/Record.h"
10 #include "clang-include-cleaner/Types.h"
11 #include "clang/AST/Decl.h"
12 #include "clang/Basic/Diagnostic.h"
13 #include "clang/Basic/FileEntry.h"
14 #include "clang/Basic/LLVM.h"
15 #include "clang/Basic/SourceLocation.h"
16 #include "clang/Frontend/CompilerInvocation.h"
17 #include "clang/Frontend/FrontendAction.h"
18 #include "clang/Frontend/FrontendActions.h"
19 #include "clang/Frontend/FrontendOptions.h"
20 #include "clang/Serialization/PCHContainerOperations.h"
21 #include "clang/Testing/TestAST.h"
22 #include "clang/Tooling/Inclusions/StandardLibrary.h"
23 #include "llvm/ADT/ArrayRef.h"
24 #include "llvm/ADT/IntrusiveRefCntPtr.h"
25 #include "llvm/ADT/StringRef.h"
26 #include "llvm/Support/Error.h"
27 #include "llvm/Support/MemoryBuffer.h"
28 #include "llvm/Support/Path.h"
29 #include "llvm/Support/VirtualFileSystem.h"
30 #include "llvm/Support/raw_ostream.h"
31 #include "llvm/Testing/Annotations/Annotations.h"
32 #include "gmock/gmock.h"
33 #include "gtest/gtest.h"
39 namespace clang::include_cleaner
{
41 using testing::ElementsAreArray
;
42 using testing::IsEmpty
;
44 // Matches a Decl* if it is a NamedDecl with the given name.
45 MATCHER_P(named
, N
, "") {
46 if (const NamedDecl
*ND
= llvm::dyn_cast
<NamedDecl
>(arg
)) {
47 if (N
== ND
->getNameAsString())
51 llvm::raw_string_ostream
OS(S
);
53 *result_listener
<< S
;
57 MATCHER_P(FileNamed
, N
, "") {
58 llvm::StringRef ActualName
=
59 llvm::sys::path::remove_leading_dotslash(arg
.getName());
62 *result_listener
<< ActualName
.str();
66 class RecordASTTest
: public ::testing::Test
{
72 struct RecordAction
: public ASTFrontendAction
{
74 RecordAction(RecordedAST
&Out
) : Out(Out
) {}
75 std::unique_ptr
<ASTConsumer
> CreateASTConsumer(CompilerInstance
&CI
,
80 Inputs
.MakeAction
= [this] {
81 return std::make_unique
<RecordAction
>(Recorded
);
85 TestAST
build() { return TestAST(Inputs
); }
88 // Top-level decl from the main file is a root, nested ones aren't.
89 TEST_F(RecordASTTest
, Namespace
) {
100 EXPECT_THAT(Recorded
.Roots
, testing::ElementsAre(named("ns")));
103 // Decl in included file is not a root.
104 TEST_F(RecordASTTest
, Inclusion
) {
105 Inputs
.ExtraFiles
["header.h"] = "void headerFunc();";
111 EXPECT_THAT(Recorded
.Roots
, testing::ElementsAre(named("mainFunc")));
114 // Decl from macro expanded into the main file is a root.
115 TEST_F(RecordASTTest
, Macros
) {
116 Inputs
.ExtraFiles
["header.h"] = "#define X void x();";
122 EXPECT_THAT(Recorded
.Roots
, testing::ElementsAre(named("x")));
125 // Decl from template instantiation is filtered out from roots.
126 TEST_F(RecordASTTest
, ImplicitTemplates
) {
127 Inputs
.ExtraFiles
["dispatch.h"] = R
"cpp(
129 static constexpr int value = 1;
131 template <class Getter>
133 return Getter::template get<A>();
137 #include "dispatch
.h
"
139 template <class T> static int get() { return T::value; }
141 int v = dispatch<MyGetter>();
144 EXPECT_THAT(Recorded
.Roots
,
145 testing::ElementsAre(named("MyGetter"), named("v")));
148 class RecordPPTest
: public ::testing::Test
{
154 struct RecordAction
: public PreprocessOnlyAction
{
156 RecordAction(RecordedPP
&Out
) : Out(Out
) {}
158 void ExecuteAction() override
{
159 auto &PP
= getCompilerInstance().getPreprocessor();
160 PP
.addPPCallbacks(Out
.record(PP
));
161 PreprocessOnlyAction::ExecuteAction();
164 Inputs
.MakeAction
= [this] {
165 return std::make_unique
<RecordAction
>(Recorded
);
169 TestAST
build() { return TestAST(Inputs
); }
172 // Matches an Include with a particular spelling.
173 MATCHER_P(spelled
, S
, "") { return arg
.Spelled
== S
; }
175 TEST_F(RecordPPTest
, CapturesIncludes
) {
176 llvm::Annotations
MainFile(R
"cpp(
177 $H^#include "./header
.h
"
178 $M^#include <missing.h>
180 Inputs
.Code
= MainFile
.code();
181 Inputs
.ExtraFiles
["header.h"] = "";
182 Inputs
.ErrorOK
= true; // missing header
186 Recorded
.Includes
.all(),
187 testing::ElementsAre(spelled("./header.h"), spelled("missing.h")));
189 auto &H
= Recorded
.Includes
.all().front();
190 EXPECT_EQ(H
.Line
, 2u);
191 EXPECT_EQ(H
.HashLocation
,
192 AST
.sourceManager().getComposedLoc(
193 AST
.sourceManager().getMainFileID(), MainFile
.point("H")));
194 EXPECT_EQ(H
.Resolved
, *AST
.fileManager().getOptionalFileRef("header.h"));
195 EXPECT_FALSE(H
.Angled
);
197 auto &M
= Recorded
.Includes
.all().back();
198 EXPECT_EQ(M
.Line
, 3u);
199 EXPECT_EQ(M
.HashLocation
,
200 AST
.sourceManager().getComposedLoc(
201 AST
.sourceManager().getMainFileID(), MainFile
.point("M")));
202 EXPECT_EQ(M
.Resolved
, std::nullopt
);
203 EXPECT_TRUE(M
.Angled
);
206 TEST_F(RecordPPTest
, CapturesMacroRefs
) {
207 llvm::Annotations
Header(R
"cpp(
210 // Refs, but not in main file.
214 llvm::Annotations
MainFile(R
"cpp(
215 #define EARLY X // not a ref, no definition
218 #define LATE2 ^X // a ref even if not expanded
221 int jeden = $exp^LATE; // a ref in LATE's expansion
223 #define IDENT(X) X // not a ref, shadowed
224 int eins = IDENT(^X);
227 // Not refs, rather a new macro with the same name.
231 Inputs
.Code
= MainFile
.code();
232 Inputs
.ExtraFiles
["header.h"] = Header
.code();
234 const auto &SM
= AST
.sourceManager();
236 SourceLocation Def
= SM
.getComposedLoc(
237 SM
.translateFile(*AST
.fileManager().getOptionalFileRef("header.h")),
238 Header
.point("def"));
239 ASSERT_THAT(Recorded
.MacroReferences
, Not(IsEmpty()));
240 Symbol OrigX
= Recorded
.MacroReferences
.front().Target
;
241 EXPECT_EQ("X", OrigX
.macro().Name
->getName());
242 EXPECT_EQ(Def
, OrigX
.macro().Definition
);
244 std::vector
<unsigned> RefOffsets
;
245 std::vector
<unsigned> ExpOffsets
; // Expansion locs of refs in macro locs.
246 for (const auto &Ref
: Recorded
.MacroReferences
) {
247 if (Ref
.Target
== OrigX
) {
248 auto [FID
, Off
] = SM
.getDecomposedLoc(Ref
.RefLocation
);
249 if (FID
== SM
.getMainFileID()) {
250 RefOffsets
.push_back(Off
);
251 } else if (Ref
.RefLocation
.isMacroID() &&
252 SM
.isWrittenInMainFile(SM
.getExpansionLoc(Ref
.RefLocation
))) {
253 ExpOffsets
.push_back(
254 SM
.getDecomposedExpansionLoc(Ref
.RefLocation
).second
);
256 ADD_FAILURE() << Ref
.RefLocation
.printToString(SM
);
260 EXPECT_THAT(RefOffsets
, ElementsAreArray(MainFile
.points()));
261 EXPECT_THAT(ExpOffsets
, ElementsAreArray(MainFile
.points("exp")));
264 TEST_F(RecordPPTest
, CapturesConditionalMacroRefs
) {
265 llvm::Annotations
MainFile(R
"cpp(
286 Inputs
.Code
= MainFile
.code();
287 Inputs
.ExtraArgs
.push_back("-std=c++2b");
290 std::vector
<unsigned> RefOffsets
;
291 SourceManager
&SM
= AST
.sourceManager();
292 for (const auto &Ref
: Recorded
.MacroReferences
) {
293 auto [FID
, Off
] = SM
.getDecomposedLoc(Ref
.RefLocation
);
294 ASSERT_EQ(FID
, SM
.getMainFileID());
295 EXPECT_EQ(Ref
.RT
, RefType::Ambiguous
);
296 EXPECT_EQ("X", Ref
.Target
.macro().Name
->getName());
297 RefOffsets
.push_back(Off
);
299 EXPECT_THAT(RefOffsets
, ElementsAreArray(MainFile
.points()));
302 class PragmaIncludeTest
: public ::testing::Test
{
304 // We don't build an AST, we just run a preprocessor action!
308 PragmaIncludeTest() {
309 Inputs
.MakeAction
= [this] {
310 struct Hook
: public PreprocessOnlyAction
{
312 Hook(PragmaIncludes
*Out
) : Out(Out
) {}
313 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
319 return std::make_unique
<Hook
>(&PI
);
323 TestAST
build(bool ResetPragmaIncludes
= true) {
324 if (ResetPragmaIncludes
)
325 PI
= PragmaIncludes();
326 return TestAST(Inputs
);
329 void createEmptyFiles(llvm::ArrayRef
<StringRef
> FileNames
) {
330 for (llvm::StringRef File
: FileNames
)
331 Inputs
.ExtraFiles
[File
] = "#pragma once";
335 TEST_F(PragmaIncludeTest
, IWYUKeep
) {
337 #include "keep1
.h
" // IWYU pragma: keep
338 #include "keep2
.h
" /* IWYU pragma: keep */
340 #include "export1
.h
" // IWYU pragma: export
341 // IWYU pragma: begin_exports
344 // IWYU pragma: end_exports
348 // IWYU pragma: begin_keep
350 // IWYU pragma: end_keep
352 // IWYU pragma: begin_keep
354 // IWYU pragma: begin_keep
356 // IWYU pragma: end_keep
358 // IWYU pragma: end_keep
360 #include <map> // IWYU pragma: keep
361 #include <set> // IWYU pragma: export
363 createEmptyFiles({"keep1.h", "keep2.h", "keep3.h", "keep4.h", "keep5.h",
364 "keep6.h", "export1.h", "export2.h", "export3.h",
365 "normal.h", "std/vector", "std/map", "std/set"});
367 Inputs
.ExtraArgs
.push_back("-isystemstd");
368 TestAST Processed
= build();
369 auto &FM
= Processed
.fileManager();
371 EXPECT_FALSE(PI
.shouldKeep(*FM
.getOptionalFileRef("normal.h")));
372 EXPECT_FALSE(PI
.shouldKeep(*FM
.getOptionalFileRef("std/vector")));
375 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("keep1.h")));
376 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("keep2.h")));
377 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("keep3.h")));
378 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("keep4.h")));
379 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("keep5.h")));
380 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("keep6.h")));
381 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("std/map")));
384 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("export1.h")));
385 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("export2.h")));
386 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("export3.h")));
387 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("std/set")));
390 TEST_F(PragmaIncludeTest
, AssociatedHeader
) {
391 createEmptyFiles({"foo/main.h", "bar/main.h", "bar/other.h", "std/vector"});
392 auto IsKeep
= [&](llvm::StringRef Name
, TestAST
&AST
) {
393 return PI
.shouldKeep(*AST
.fileManager().getOptionalFileRef(Name
));
396 Inputs
.FileName
= "main.cc";
397 Inputs
.ExtraArgs
.push_back("-isystemstd");
400 #include "foo
/main
.h
"
401 #include "bar
/main
.h
"
404 EXPECT_TRUE(IsKeep("foo/main.h", AST
));
405 EXPECT_FALSE(IsKeep("bar/main.h", AST
)) << "not first include";
410 #include "bar
/other
.h
"
411 #include "bar
/main
.h
"
414 EXPECT_FALSE(IsKeep("bar/other.h", AST
));
415 EXPECT_FALSE(IsKeep("bar/main.h", AST
)) << "not first include";
420 #include "foo
/main
.h
"
421 #include "bar
/other
.h
" // IWYU pragma: associated
422 #include <vector> // IWYU pragma: associated
425 EXPECT_TRUE(IsKeep("foo/main.h", AST
));
426 EXPECT_TRUE(IsKeep("bar/other.h", AST
));
427 EXPECT_TRUE(IsKeep("std/vector", AST
));
430 Inputs
.FileName
= "vector.cc";
436 EXPECT_FALSE(IsKeep("std/vector", AST
)) << "stdlib is not associated";
440 TEST_F(PragmaIncludeTest
, IWYUPrivate
) {
444 Inputs
.ExtraFiles
["public.h"] = R
"cpp(
446 #include "private2
.h
"
448 Inputs
.ExtraFiles
["private.h"] = R
"cpp(
449 // IWYU pragma: private, include "public2
.h
"
451 Inputs
.ExtraFiles
["private2.h"] = R
"cpp(
452 // IWYU pragma: private
454 TestAST Processed
= build();
455 auto PrivateFE
= Processed
.fileManager().getOptionalFileRef("private.h");
457 EXPECT_TRUE(PI
.isPrivate(*PrivateFE
));
458 EXPECT_EQ(PI
.getPublic(*PrivateFE
), "\"public2.h\"");
460 auto PublicFE
= Processed
.fileManager().getOptionalFileRef("public.h");
462 EXPECT_EQ(PI
.getPublic(*PublicFE
), ""); // no mapping.
463 EXPECT_FALSE(PI
.isPrivate(*PublicFE
));
465 auto Private2FE
= Processed
.fileManager().getOptionalFileRef("private2.h");
467 EXPECT_TRUE(PI
.isPrivate(*Private2FE
));
470 TEST_F(PragmaIncludeTest
, IWYUExport
) {
471 Inputs
.Code
= R
"cpp(// Line 1
475 Inputs
.ExtraFiles
["export1.h"] = R
"cpp(
476 #include "private.h
" // IWYU pragma: export
478 Inputs
.ExtraFiles
["export2.h"] = R
"cpp(
481 Inputs
.ExtraFiles
["export3.h"] = R
"cpp(
482 #include "private.h
" // IWYU pragma: export
484 Inputs
.ExtraFiles
["private.h"] = "";
485 TestAST Processed
= build();
486 const auto &SM
= Processed
.sourceManager();
487 auto &FM
= Processed
.fileManager();
489 EXPECT_THAT(PI
.getExporters(*FM
.getOptionalFileRef("private.h"), FM
),
490 testing::UnorderedElementsAre(FileNamed("export1.h"),
491 FileNamed("export3.h")));
493 EXPECT_TRUE(PI
.getExporters(*FM
.getOptionalFileRef("export1.h"), FM
).empty());
494 EXPECT_TRUE(PI
.getExporters(*FM
.getOptionalFileRef("export2.h"), FM
).empty());
495 EXPECT_TRUE(PI
.getExporters(*FM
.getOptionalFileRef("export3.h"), FM
).empty());
497 PI
.getExporters(SM
.getFileEntryForID(SM
.getMainFileID()), FM
).empty());
500 TEST_F(PragmaIncludeTest
, IWYUExportForStandardHeaders
) {
504 Inputs
.ExtraFiles
["export.h"] = R
"cpp(
505 #include <string> // IWYU pragma: export
507 Inputs
.ExtraFiles
["string"] = "";
508 Inputs
.ExtraArgs
= {"-isystem."};
509 TestAST Processed
= build();
510 auto &FM
= Processed
.fileManager();
511 EXPECT_THAT(PI
.getExporters(*tooling::stdlib::Header::named("<string>"), FM
),
512 testing::UnorderedElementsAre(FileNamed("export.h")));
513 EXPECT_THAT(PI
.getExporters(llvm::cantFail(FM
.getFileRef("string")), FM
),
514 testing::UnorderedElementsAre(FileNamed("export.h")));
517 TEST_F(PragmaIncludeTest
, IWYUExportBlock
) {
518 Inputs
.Code
= R
"cpp(// Line 1
521 Inputs
.ExtraFiles
["normal.h"] = R
"cpp(
524 // IWYU pragma: begin_exports
526 #include "private1
.h
"
527 // IWYU pragma: end_exports
529 Inputs
.ExtraFiles
["export1.h"] = R
"cpp(
530 // IWYU pragma: begin_exports
531 #include "private1
.h
"
532 #include "private2
.h
"
533 // IWYU pragma: end_exports
536 #include "private3
.h
" // IWYU pragma: export
539 {"private1.h", "private2.h", "private3.h", "foo.h", "bar.h"});
540 TestAST Processed
= build();
541 auto &FM
= Processed
.fileManager();
543 auto GetNames
= [](llvm::ArrayRef
<FileEntryRef
> FEs
) {
545 llvm::raw_string_ostream
OS(Result
);
546 for (auto &FE
: FEs
) {
547 OS
<< FE
.getName() << " ";
551 auto Exporters
= PI
.getExporters(*FM
.getOptionalFileRef("private1.h"), FM
);
552 EXPECT_THAT(Exporters
, testing::UnorderedElementsAre(FileNamed("export1.h"),
553 FileNamed("normal.h")))
554 << GetNames(Exporters
);
556 Exporters
= PI
.getExporters(*FM
.getOptionalFileRef("private2.h"), FM
);
557 EXPECT_THAT(Exporters
, testing::UnorderedElementsAre(FileNamed("export1.h")))
558 << GetNames(Exporters
);
560 Exporters
= PI
.getExporters(*FM
.getOptionalFileRef("private3.h"), FM
);
561 EXPECT_THAT(Exporters
, testing::UnorderedElementsAre(FileNamed("export1.h")))
562 << GetNames(Exporters
);
564 Exporters
= PI
.getExporters(*FM
.getOptionalFileRef("foo.h"), FM
);
565 EXPECT_TRUE(Exporters
.empty()) << GetNames(Exporters
);
567 Exporters
= PI
.getExporters(*FM
.getOptionalFileRef("bar.h"), FM
);
568 EXPECT_TRUE(Exporters
.empty()) << GetNames(Exporters
);
571 TEST_F(PragmaIncludeTest
, SelfContained
) {
575 #include "unguarded
.h
"
577 Inputs
.ExtraFiles
["guarded.h"] = R
"cpp(
580 Inputs
.ExtraFiles
["unguarded.h"] = "";
581 TestAST Processed
= build();
582 auto &FM
= Processed
.fileManager();
583 EXPECT_TRUE(PI
.isSelfContained(*FM
.getOptionalFileRef("guarded.h")));
584 EXPECT_FALSE(PI
.isSelfContained(*FM
.getOptionalFileRef("unguarded.h")));
587 TEST_F(PragmaIncludeTest
, AlwaysKeep
) {
589 #include "always_keep
.h
"
592 Inputs
.ExtraFiles
["always_keep.h"] = R
"cpp(
594 // IWYU pragma: always_keep
596 Inputs
.ExtraFiles
["usual.h"] = "#pragma once";
597 TestAST Processed
= build();
598 auto &FM
= Processed
.fileManager();
599 EXPECT_TRUE(PI
.shouldKeep(*FM
.getOptionalFileRef("always_keep.h")));
600 EXPECT_FALSE(PI
.shouldKeep(*FM
.getOptionalFileRef("usual.h")));
603 TEST_F(PragmaIncludeTest
, ExportInUnnamedBuffer
) {
604 llvm::StringLiteral Filename
= "test.cpp";
605 auto Code
= R
"cpp(#include "exporter
.h
")cpp";
606 Inputs
.ExtraFiles
["exporter.h"] = R
"cpp(
608 #include "foo
.h
" // IWYU pragma: export
610 Inputs
.ExtraFiles
["foo.h"] = "";
612 auto Clang
= std::make_unique
<CompilerInstance
>(
613 std::make_shared
<PCHContainerOperations
>());
614 Clang
->createDiagnostics();
616 Clang
->setInvocation(std::make_unique
<CompilerInvocation
>());
617 ASSERT_TRUE(CompilerInvocation::CreateFromArgs(
618 Clang
->getInvocation(), {Filename
.data()}, Clang
->getDiagnostics(),
621 // Create unnamed memory buffers for all the files.
622 auto VFS
= llvm::makeIntrusiveRefCnt
<llvm::vfs::InMemoryFileSystem
>();
623 VFS
->addFile(Filename
, /*ModificationTime=*/0,
624 llvm::MemoryBuffer::getMemBufferCopy(Code
, /*BufferName=*/""));
625 for (const auto &Extra
: Inputs
.ExtraFiles
)
626 VFS
->addFile(Extra
.getKey(), /*ModificationTime=*/0,
627 llvm::MemoryBuffer::getMemBufferCopy(Extra
.getValue(),
629 auto *FM
= Clang
->createFileManager(VFS
);
630 ASSERT_TRUE(Clang
->ExecuteAction(*Inputs
.MakeAction()));
632 PI
.getExporters(llvm::cantFail(FM
->getFileRef("foo.h")), *FM
),
633 testing::ElementsAre(llvm::cantFail(FM
->getFileRef("exporter.h"))));
636 TEST_F(PragmaIncludeTest
, OutlivesFMAndSM
) {
640 Inputs
.ExtraFiles
["public.h"] = R
"cpp(
642 #include "private2
.h
" // IWYU pragma: export
644 Inputs
.ExtraFiles
["private.h"] = R
"cpp(
645 // IWYU pragma: private, include "public.h
"
647 Inputs
.ExtraFiles
["private2.h"] = R
"cpp(
648 // IWYU pragma: private
650 build(); // Fills up PI, file/source manager used is destroyed afterwards.
651 Inputs
.MakeAction
= nullptr; // Don't populate PI anymore.
653 // Now this build gives us a new File&Source Manager.
654 TestAST Processed
= build(/*ResetPragmaIncludes=*/false);
655 auto &FM
= Processed
.fileManager();
656 auto PrivateFE
= FM
.getOptionalFileRef("private.h");
658 EXPECT_EQ(PI
.getPublic(*PrivateFE
), "\"public.h\"");
660 auto Private2FE
= FM
.getOptionalFileRef("private2.h");
662 EXPECT_THAT(PI
.getExporters(*Private2FE
, FM
),
663 testing::ElementsAre(llvm::cantFail(FM
.getFileRef("public.h"))));
666 TEST_F(PragmaIncludeTest
, CanRecordManyTimes
) {
670 Inputs
.ExtraFiles
["public.h"] = R
"cpp(
673 Inputs
.ExtraFiles
["private.h"] = R
"cpp(
674 // IWYU pragma: private, include "public.h
"
677 TestAST Processed
= build();
678 auto &FM
= Processed
.fileManager();
679 auto PrivateFE
= FM
.getOptionalFileRef("private.h");
680 llvm::StringRef Public
= PI
.getPublic(*PrivateFE
);
681 EXPECT_EQ(Public
, "\"public.h\"");
683 // This build populates same PI during build, but this time we don't have
684 // any IWYU pragmas. Make sure strings from previous recordings are still
687 build(/*ResetPragmaIncludes=*/false);
688 EXPECT_EQ(Public
, "\"public.h\"");
691 } // namespace clang::include_cleaner