1 //===--- IncludeCleanerCheck.cpp - clang-tidy -----------------------------===//
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 "IncludeCleanerCheck.h"
10 #include "../ClangTidyCheck.h"
11 #include "../ClangTidyDiagnosticConsumer.h"
12 #include "../ClangTidyOptions.h"
13 #include "../utils/OptionsUtils.h"
14 #include "clang-include-cleaner/Analysis.h"
15 #include "clang-include-cleaner/IncludeSpeller.h"
16 #include "clang-include-cleaner/Record.h"
17 #include "clang-include-cleaner/Types.h"
18 #include "clang/AST/ASTContext.h"
19 #include "clang/AST/Decl.h"
20 #include "clang/AST/DeclBase.h"
21 #include "clang/ASTMatchers/ASTMatchFinder.h"
22 #include "clang/ASTMatchers/ASTMatchers.h"
23 #include "clang/Basic/Diagnostic.h"
24 #include "clang/Basic/FileEntry.h"
25 #include "clang/Basic/LLVM.h"
26 #include "clang/Basic/LangOptions.h"
27 #include "clang/Basic/SourceLocation.h"
28 #include "clang/Format/Format.h"
29 #include "clang/Lex/Preprocessor.h"
30 #include "clang/Tooling/Core/Replacement.h"
31 #include "clang/Tooling/Inclusions/HeaderIncludes.h"
32 #include "llvm/ADT/DenseSet.h"
33 #include "llvm/ADT/STLExtras.h"
34 #include "llvm/ADT/SmallVector.h"
35 #include "llvm/ADT/StringRef.h"
36 #include "llvm/Support/ErrorHandling.h"
37 #include "llvm/Support/Path.h"
38 #include "llvm/Support/Regex.h"
43 using namespace clang::ast_matchers
;
45 namespace clang::tidy::misc
{
48 struct MissingIncludeInfo
{
49 include_cleaner::SymbolReference SymRef
;
50 include_cleaner::Header Missing
;
54 IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name
,
55 ClangTidyContext
*Context
)
56 : ClangTidyCheck(Name
, Context
),
57 IgnoreHeaders(utils::options::parseStringList(
58 Options
.getLocalOrGlobal("IgnoreHeaders", ""))) {
59 for (const auto &Header
: IgnoreHeaders
) {
60 if (!llvm::Regex
{Header
}.isValid())
61 configurationDiag("Invalid ignore headers regex '%0'") << Header
;
62 std::string HeaderSuffix
{Header
.str()};
63 if (!Header
.ends_with("$"))
65 IgnoreHeadersRegex
.emplace_back(HeaderSuffix
);
69 void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
70 Options
.store(Opts
, "IgnoreHeaders",
71 utils::options::serializeStringList(IgnoreHeaders
));
74 bool IncludeCleanerCheck::isLanguageVersionSupported(
75 const LangOptions
&LangOpts
) const {
76 return !LangOpts
.ObjC
;
79 void IncludeCleanerCheck::registerMatchers(MatchFinder
*Finder
) {
80 Finder
->addMatcher(translationUnitDecl().bind("top"), this);
83 void IncludeCleanerCheck::registerPPCallbacks(const SourceManager
&SM
,
85 Preprocessor
*ModuleExpanderPP
) {
86 PP
->addPPCallbacks(RecordedPreprocessor
.record(*PP
));
87 HS
= &PP
->getHeaderSearchInfo();
88 RecordedPI
.record(*PP
);
91 bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header
&H
) {
92 return llvm::any_of(IgnoreHeadersRegex
, [&H
](const llvm::Regex
&R
) {
94 case include_cleaner::Header::Standard
:
95 return R
.match(H
.standard().name());
96 case include_cleaner::Header::Verbatim
:
97 return R
.match(H
.verbatim());
98 case include_cleaner::Header::Physical
:
99 return R
.match(H
.physical()->tryGetRealPathName());
101 llvm_unreachable("Unknown Header kind.");
105 void IncludeCleanerCheck::check(const MatchFinder::MatchResult
&Result
) {
106 const SourceManager
*SM
= Result
.SourceManager
;
107 const FileEntry
*MainFile
= SM
->getFileEntryForID(SM
->getMainFileID());
108 llvm::DenseSet
<const include_cleaner::Include
*> Used
;
109 std::vector
<MissingIncludeInfo
> Missing
;
110 llvm::SmallVector
<Decl
*> MainFileDecls
;
111 for (Decl
*D
: Result
.Nodes
.getNodeAs
<TranslationUnitDecl
>("top")->decls()) {
112 if (!SM
->isWrittenInMainFile(SM
->getExpansionLoc(D
->getLocation())))
114 // FIXME: Filter out implicit template specializations.
115 MainFileDecls
.push_back(D
);
117 // FIXME: Find a way to have less code duplication between include-cleaner
118 // analysis implementation and the below code.
119 walkUsed(MainFileDecls
, RecordedPreprocessor
.MacroReferences
, &RecordedPI
,
121 [&](const include_cleaner::SymbolReference
&Ref
,
122 llvm::ArrayRef
<include_cleaner::Header
> Providers
) {
123 bool Satisfied
= false;
124 for (const include_cleaner::Header
&H
: Providers
) {
125 if (H
.kind() == include_cleaner::Header::Physical
&&
126 H
.physical() == MainFile
)
129 for (const include_cleaner::Include
*I
:
130 RecordedPreprocessor
.Includes
.match(H
)) {
135 if (!Satisfied
&& !Providers
.empty() &&
136 Ref
.RT
== include_cleaner::RefType::Explicit
&&
137 !shouldIgnore(Providers
.front()))
138 Missing
.push_back({Ref
, Providers
.front()});
141 std::vector
<const include_cleaner::Include
*> Unused
;
142 for (const include_cleaner::Include
&I
:
143 RecordedPreprocessor
.Includes
.all()) {
144 if (Used
.contains(&I
) || !I
.Resolved
)
146 if (RecordedPI
.shouldKeep(I
.Line
))
148 // Check if main file is the public interface for a private header. If so
149 // we shouldn't diagnose it as unused.
150 if (auto PHeader
= RecordedPI
.getPublic(I
.Resolved
); !PHeader
.empty()) {
151 PHeader
= PHeader
.trim("<>\"");
152 // Since most private -> public mappings happen in a verbatim way, we
153 // check textually here. This might go wrong in presence of symlinks or
154 // header mappings. But that's not different than rest of the places.
155 if (getCurrentMainFile().endswith(PHeader
))
159 if (llvm::none_of(IgnoreHeadersRegex
,
160 [Resolved
= I
.Resolved
->tryGetRealPathName()](
161 const llvm::Regex
&R
) { return R
.match(Resolved
); }))
162 Unused
.push_back(&I
);
165 llvm::StringRef Code
= SM
->getBufferData(SM
->getMainFileID());
167 format::getStyle(format::DefaultFormatStyle
, getCurrentMainFile(),
168 format::DefaultFallbackStyle
, Code
,
169 &SM
->getFileManager().getVirtualFileSystem());
171 FileStyle
= format::getLLVMStyle();
173 for (const auto *Inc
: Unused
) {
174 diag(Inc
->HashLocation
, "included header %0 is not used directly")
175 << llvm::sys::path::filename(Inc
->Spelled
,
176 llvm::sys::path::Style::posix
)
177 << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
178 SM
->translateLineCol(SM
->getMainFileID(), Inc
->Line
, 1),
179 SM
->translateLineCol(SM
->getMainFileID(), Inc
->Line
+ 1, 1)));
182 tooling::HeaderIncludes
HeaderIncludes(getCurrentMainFile(), Code
,
183 FileStyle
->IncludeStyle
);
184 for (const auto &Inc
: Missing
) {
185 std::string Spelling
=
186 include_cleaner::spellHeader({Inc
.Missing
, *HS
, MainFile
});
187 bool Angled
= llvm::StringRef
{Spelling
}.starts_with("<");
188 // We might suggest insertion of an existing include in edge cases, e.g.,
189 // include is present in a PP-disabled region, or spelling of the header
190 // turns out to be the same as one of the unresolved includes in the
192 if (auto Replacement
=
193 HeaderIncludes
.insert(llvm::StringRef
{Spelling
}.trim("\"<>"),
194 Angled
, tooling::IncludeDirective::Include
))
195 diag(SM
->getSpellingLoc(Inc
.SymRef
.RefLocation
),
196 "no header providing \"%0\" is directly included")
197 << Inc
.SymRef
.Target
.name()
198 << FixItHint::CreateInsertion(
199 SM
->getComposedLoc(SM
->getMainFileID(),
200 Replacement
->getOffset()),
201 Replacement
->getReplacementText());
205 } // namespace clang::tidy::misc