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/HeaderSearchOptions.h"
30#include "clang/Lex/Preprocessor.h"
31#include "clang/Tooling/Core/Replacement.h"
32#include "clang/Tooling/Inclusions/HeaderIncludes.h"
33#include "clang/Tooling/Inclusions/StandardLibrary.h"
34#include "llvm/ADT/DenseSet.h"
35#include "llvm/ADT/STLExtras.h"
36#include "llvm/ADT/SmallVector.h"
37#include "llvm/ADT/StringRef.h"
38#include "llvm/ADT/StringSet.h"
39#include "llvm/Support/ErrorHandling.h"
40#include "llvm/Support/Path.h"
41#include "llvm/Support/Regex.h"
51struct MissingIncludeInfo {
52 include_cleaner::SymbolReference
SymRef;
60 IgnoreHeaders(utils::options::parseStringList(
61 Options.getLocalOrGlobal(
"IgnoreHeaders",
""))),
63 Options.getLocalOrGlobal(
"DeduplicateFindings", true)) {
64 for (
const auto &Header : IgnoreHeaders) {
65 if (!llvm::Regex{Header}.isValid())
67 std::string HeaderSuffix{Header.str()};
68 if (!Header.ends_with(
"$"))
70 IgnoreHeadersRegex.emplace_back(HeaderSuffix);
77 Options.
store(Opts,
"DeduplicateFindings", DeduplicateFindings);
81 const LangOptions &LangOpts)
const {
82 return !LangOpts.ObjC;
86 Finder->addMatcher(translationUnitDecl().bind(
"top"),
this);
91 Preprocessor *ModuleExpanderPP) {
92 PP->addPPCallbacks(RecordedPreprocessor.record(*PP));
94 RecordedPI.record(*PP);
97bool IncludeCleanerCheck::shouldIgnore(
const include_cleaner::Header &H) {
98 return llvm::any_of(IgnoreHeadersRegex, [&H](
const llvm::Regex &R) {
100 case include_cleaner::Header::Standard:
104 return R.match(H.standard().name());
105 case include_cleaner::Header::Verbatim:
106 return R.match(H.verbatim().trim(
"<>\""));
107 case include_cleaner::Header::Physical:
108 return R.match(H.physical().getFileEntry().tryGetRealPathName());
110 llvm_unreachable(
"Unknown Header kind.");
115 const SourceManager *SM = Result.SourceManager;
116 const FileEntry *
MainFile = SM->getFileEntryForID(SM->getMainFileID());
117 llvm::DenseSet<const include_cleaner::Include *> Used;
118 std::vector<MissingIncludeInfo>
Missing;
119 llvm::SmallVector<Decl *> MainFileDecls;
120 for (
Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>(
"top")->decls()) {
121 if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation())))
124 MainFileDecls.push_back(D);
126 llvm::DenseSet<include_cleaner::Symbol> SeenSymbols;
127 OptionalDirectoryEntryRef ResourceDir =
128 PP->getHeaderSearchInfo().getModuleMap().getBuiltinDir();
131 walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI,
133 [&](
const include_cleaner::SymbolReference &Ref,
134 llvm::ArrayRef<include_cleaner::Header> Providers) {
146 if (DeduplicateFindings && !SeenSymbols.insert(Ref.Target).second)
148 bool Satisfied = false;
149 for (const include_cleaner::Header &H : Providers) {
150 if (H.kind() == include_cleaner::Header::Physical &&
151 (H.physical() == MainFile ||
152 H.physical().getDir() == ResourceDir)) {
157 for (const include_cleaner::Include *I :
158 RecordedPreprocessor.Includes.match(H)) {
163 if (!Satisfied && !Providers.empty() &&
164 Ref.RT == include_cleaner::RefType::Explicit &&
165 !shouldIgnore(Providers.front()))
166 Missing.push_back({Ref, Providers.front()});
169 std::vector<const include_cleaner::Include *> Unused;
170 for (
const include_cleaner::Include &I :
171 RecordedPreprocessor.Includes.all()) {
172 if (Used.contains(&I) || !I.Resolved || I.Resolved->getDir() == ResourceDir)
174 if (RecordedPI.shouldKeep(*I.Resolved))
178 if (
auto PHeader = RecordedPI.getPublic(*I.Resolved); !PHeader.empty()) {
179 PHeader = PHeader.trim(
"<>\"");
183 if (getCurrentMainFile().ends_with(PHeader))
186 auto StdHeader = tooling::stdlib::Header::named(
187 I.quote(), PP->getLangOpts().CPlusPlus ? tooling::stdlib::Lang::CXX
188 : tooling::stdlib::Lang::C);
189 if (StdHeader && shouldIgnore(*StdHeader))
191 if (shouldIgnore(*I.Resolved))
193 Unused.push_back(&I);
196 llvm::StringRef
Code = SM->getBufferData(SM->getMainFileID());
198 format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(),
199 format::DefaultFallbackStyle,
Code,
200 &SM->getFileManager().getVirtualFileSystem());
202 FileStyle = format::getLLVMStyle();
204 for (
const auto *Inc : Unused) {
205 diag(Inc->HashLocation,
"included header %0 is not used directly")
206 << llvm::sys::path::filename(Inc->Spelled,
207 llvm::sys::path::Style::posix)
208 << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
209 SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
210 SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
213 tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(),
Code,
214 FileStyle->IncludeStyle);
216 llvm::StringSet<> InsertedHeaders{};
217 for (
const auto &Inc :
Missing) {
218 std::string Spelling = include_cleaner::spellHeader(
219 {Inc.Missing, PP->getHeaderSearchInfo(),
MainFile});
220 bool Angled = llvm::StringRef{Spelling}.starts_with(
"<");
225 if (
auto Replacement =
226 HeaderIncludes.insert(llvm::StringRef{Spelling}.trim(
"\"<>"),
227 Angled, tooling::IncludeDirective::Include)) {
228 DiagnosticBuilder DB =
229 diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),
230 "no header providing \"%0\" is directly included")
231 << Inc.SymRef.Target.name();
232 if (areDiagsSelfContained() ||
233 InsertedHeaders.insert(Replacement->getReplacementText()).second) {
234 DB << FixItHint::CreateInsertion(
235 SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()),
236 Replacement->getReplacementText());
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
include_cleaner::Header Missing
include_cleaner::SymbolReference SymRef
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
Override this to disable registering matchers and PP callbacks if an invalid language version is bein...
IncludeCleanerCheck(StringRef Name, ClangTidyContext *Context)
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap