clang-tools 17.0.0git
IncludeCleaner.cpp
Go to the documentation of this file.
1//===--- IncludeCleaner.cpp - Unused/Missing Headers Analysis ---*- 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//===----------------------------------------------------------------------===//
8
9#include "IncludeCleaner.h"
10#include "Config.h"
11#include "Diagnostics.h"
12#include "Headers.h"
13#include "ParsedAST.h"
14#include "Preamble.h"
15#include "Protocol.h"
16#include "SourceCode.h"
17#include "URI.h"
18#include "clang-include-cleaner/Analysis.h"
19#include "clang-include-cleaner/IncludeSpeller.h"
20#include "clang-include-cleaner/Record.h"
21#include "clang-include-cleaner/Types.h"
22#include "support/Logger.h"
23#include "support/Path.h"
24#include "support/Trace.h"
25#include "clang/AST/ASTContext.h"
26#include "clang/AST/DeclCXX.h"
27#include "clang/AST/Expr.h"
28#include "clang/AST/ExprCXX.h"
29#include "clang/AST/TemplateName.h"
30#include "clang/AST/Type.h"
31#include "clang/Basic/Diagnostic.h"
32#include "clang/Basic/LLVM.h"
33#include "clang/Basic/SourceLocation.h"
34#include "clang/Basic/SourceManager.h"
35#include "clang/Format/Format.h"
36#include "clang/Lex/HeaderSearch.h"
37#include "clang/Lex/Preprocessor.h"
38#include "clang/Tooling/Core/Replacement.h"
39#include "clang/Tooling/Inclusions/HeaderIncludes.h"
40#include "clang/Tooling/Inclusions/StandardLibrary.h"
41#include "clang/Tooling/Syntax/Tokens.h"
42#include "llvm/ADT/ArrayRef.h"
43#include "llvm/ADT/DenseSet.h"
44#include "llvm/ADT/GenericUniformityImpl.h"
45#include "llvm/ADT/STLExtras.h"
46#include "llvm/ADT/STLFunctionalExtras.h"
47#include "llvm/ADT/SmallString.h"
48#include "llvm/ADT/SmallVector.h"
49#include "llvm/ADT/StringMap.h"
50#include "llvm/ADT/StringRef.h"
51#include "llvm/ADT/StringSet.h"
52#include "llvm/Support/Casting.h"
53#include "llvm/Support/Error.h"
54#include "llvm/Support/ErrorHandling.h"
55#include "llvm/Support/FormatVariadic.h"
56#include "llvm/Support/Path.h"
57#include "llvm/Support/Regex.h"
58#include <cassert>
59#include <iterator>
60#include <optional>
61#include <string>
62#include <utility>
63#include <vector>
64
65namespace clang {
66namespace clangd {
67
68static bool AnalyzeStdlib = false;
70
71namespace {
72
73// Returns the range starting at '#' and ending at EOL. Escaped newlines are not
74// handled.
75clangd::Range getDiagnosticRange(llvm::StringRef Code, unsigned HashOffset) {
76 clangd::Range Result;
77 Result.end = Result.start = offsetToPosition(Code, HashOffset);
78
79 // Span the warning until the EOL or EOF.
80 Result.end.character +=
81 lspLength(Code.drop_front(HashOffset).take_until([](char C) {
82 return C == '\n' || C == '\r';
83 }));
84 return Result;
85}
86
87bool isFilteredByConfig(const Config &Cfg, llvm::StringRef HeaderPath) {
88 // Convert the path to Unix slashes and try to match against the filter.
89 llvm::SmallString<64> NormalizedPath(HeaderPath);
90 llvm::sys::path::native(NormalizedPath, llvm::sys::path::Style::posix);
91 for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) {
92 if (Filter(NormalizedPath))
93 return true;
94 }
95 return false;
96}
97
98static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,
99 const Config &Cfg,
100 const include_cleaner::PragmaIncludes *PI) {
101 // FIXME(kirillbobyrev): We currently do not support the umbrella headers.
102 // System headers are likely to be standard library headers.
103 // Until we have good support for umbrella headers, don't warn about them.
104 if (Inc.Written.front() == '<') {
105 if (AnalyzeStdlib && tooling::stdlib::Header::named(Inc.Written))
106 return true;
107 return false;
108 }
109 assert(Inc.HeaderID);
110 auto HID = static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID);
111 auto FE = AST.getSourceManager().getFileManager().getFileRef(
112 AST.getIncludeStructure().getRealPath(HID));
113 assert(FE);
114 if (PI) {
115 if (PI->shouldKeep(Inc.HashLine + 1))
116 return false;
117 // Check if main file is the public interface for a private header. If so we
118 // shouldn't diagnose it as unused.
119 if (auto PHeader = PI->getPublic(*FE); !PHeader.empty()) {
120 PHeader = PHeader.trim("<>\"");
121 // Since most private -> public mappings happen in a verbatim way, we
122 // check textually here. This might go wrong in presence of symlinks or
123 // header mappings. But that's not different than rest of the places.
124 if (AST.tuPath().endswith(PHeader))
125 return false;
126 }
127 }
128 // Headers without include guards have side effects and are not
129 // self-contained, skip them.
130 if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded(
131 &FE->getFileEntry())) {
132 dlog("{0} doesn't have header guard and will not be considered unused",
133 FE->getName());
134 return false;
135 }
136
137 if (isFilteredByConfig(Cfg, Inc.Resolved)) {
138 dlog("{0} header is filtered out by the configuration", FE->getName());
139 return false;
140 }
141 return true;
142}
143
144llvm::StringRef getResolvedPath(const include_cleaner::Header &SymProvider) {
145 switch (SymProvider.kind()) {
146 case include_cleaner::Header::Physical:
147 return SymProvider.physical()->tryGetRealPathName();
148 case include_cleaner::Header::Standard:
149 return SymProvider.standard().name().trim("<>\"");
150 case include_cleaner::Header::Verbatim:
151 return SymProvider.verbatim().trim("<>\"");
152 }
153 llvm_unreachable("Unknown header kind");
154}
155
156std::string getSymbolName(const include_cleaner::Symbol &Sym) {
157 switch (Sym.kind()) {
158 case include_cleaner::Symbol::Macro:
159 return Sym.macro().Name->getName().str();
160 case include_cleaner::Symbol::Declaration:
161 return llvm::dyn_cast<NamedDecl>(&Sym.declaration())
162 ->getQualifiedNameAsString();
163 }
164 llvm_unreachable("Unknown symbol kind");
165}
166
167std::vector<Diag> generateMissingIncludeDiagnostics(
168 ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes,
169 llvm::StringRef Code) {
170 std::vector<Diag> Result;
171 const Config &Cfg = Config::current();
174 Cfg.Diagnostics.Suppress.contains("missing-includes")) {
175 return Result;
176 }
177
178 const SourceManager &SM = AST.getSourceManager();
179 const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
180
181 auto FileStyle = format::getStyle(
182 format::DefaultFormatStyle, AST.tuPath(), format::DefaultFallbackStyle,
183 Code, &SM.getFileManager().getVirtualFileSystem());
184 if (!FileStyle) {
185 elog("Couldn't infer style", FileStyle.takeError());
186 FileStyle = format::getLLVMStyle();
187 }
188
189 tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code,
190 FileStyle->IncludeStyle);
191 for (const auto &SymbolWithMissingInclude : MissingIncludes) {
192 llvm::StringRef ResolvedPath =
193 getResolvedPath(SymbolWithMissingInclude.Providers.front());
194 if (isFilteredByConfig(Cfg, ResolvedPath)) {
195 dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by "
196 "config",
197 ResolvedPath);
198 continue;
199 }
200
201 std::string Spelling =
202 spellHeader(AST, MainFile, SymbolWithMissingInclude.Providers.front());
203
204 llvm::StringRef HeaderRef{Spelling};
205 bool Angled = HeaderRef.starts_with("<");
206 // We might suggest insertion of an existing include in edge cases, e.g.,
207 // include is present in a PP-disabled region, or spelling of the header
208 // turns out to be the same as one of the unresolved includes in the
209 // main file.
210 std::optional<tooling::Replacement> Replacement = HeaderIncludes.insert(
211 HeaderRef.trim("\"<>"), Angled, tooling::IncludeDirective::Include);
212 if (!Replacement.has_value())
213 continue;
214
215 Diag &D = Result.emplace_back();
216 D.Message =
217 llvm::formatv("No header providing \"{0}\" is directly included",
218 getSymbolName(SymbolWithMissingInclude.Symbol));
219 D.Name = "missing-includes";
221 D.File = AST.tuPath();
222 D.InsideMainFile = true;
223 // We avoid the "warning" severity here in favor of LSP's "information".
224 //
225 // Users treat most warnings on code being edited as high-priority.
226 // They don't think of include cleanups the same way: they want to edit
227 // lines with existing violations without fixing them.
228 // Diagnostics at the same level tend to be visually indistinguishable,
229 // and a few missing includes can cause many diagnostics.
230 // Marking these as "information" leaves them visible, but less intrusive.
231 //
232 // (These concerns don't apply to unused #include warnings: these are fewer,
233 // they appear on infrequently-edited lines with few other warnings, and
234 // the 'Unneccesary' tag often result in a different rendering)
235 //
236 // Usually clang's "note" severity usually has special semantics, being
237 // translated into LSP RelatedInformation of a parent diagnostic.
238 // But not here: these aren't processed by clangd's DiagnosticConsumer.
239 D.Severity = DiagnosticsEngine::Note;
240 D.Range = clangd::Range{
242 SymbolWithMissingInclude.SymRefRange.beginOffset()),
244 SymbolWithMissingInclude.SymRefRange.endOffset())};
245 auto &F = D.Fixes.emplace_back();
246 F.Message = "#include " + Spelling;
247 TextEdit Edit = replacementToEdit(Code, *Replacement);
248 F.Edits.emplace_back(std::move(Edit));
249 }
250 return Result;
251}
252
253std::vector<Diag> generateUnusedIncludeDiagnostics(
254 PathRef FileName, llvm::ArrayRef<const Inclusion *> UnusedIncludes,
255 llvm::StringRef Code) {
256 std::vector<Diag> Result;
257 const Config &Cfg = Config::current();
260 Cfg.Diagnostics.Suppress.contains("unused-includes")) {
261 return Result;
262 }
263 for (const auto *Inc : UnusedIncludes) {
264 Diag &D = Result.emplace_back();
265 D.Message =
266 llvm::formatv("included header {0} is not used directly",
267 llvm::sys::path::filename(
268 Inc->Written.substr(1, Inc->Written.size() - 2),
269 llvm::sys::path::Style::posix));
270 D.Name = "unused-includes";
272 D.File = FileName;
273 D.InsideMainFile = true;
274 D.Severity = DiagnosticsEngine::Warning;
275 D.Tags.push_back(Unnecessary);
276 D.Range = getDiagnosticRange(Code, Inc->HashOffset);
277 // FIXME(kirillbobyrev): Removing inclusion might break the code if the
278 // used headers are only reachable transitively through this one. Suggest
279 // including them directly instead.
280 // FIXME(kirillbobyrev): Add fix suggestion for adding IWYU pragmas
281 // (keep/export) remove the warning once we support IWYU pragmas.
282 auto &F = D.Fixes.emplace_back();
283 F.Message = "remove #include directive";
284 F.Edits.emplace_back();
285 F.Edits.back().range.start.line = Inc->HashLine;
286 F.Edits.back().range.end.line = Inc->HashLine + 1;
287 }
288 return Result;
289}
290} // namespace
291
292std::vector<include_cleaner::SymbolReference>
294 const auto &SM = AST.getSourceManager();
295 // FIXME: !!this is a hacky way to collect macro references.
296 std::vector<include_cleaner::SymbolReference> Macros;
297 auto &PP = AST.getPreprocessor();
298 for (const syntax::Token &Tok :
299 AST.getTokens().spelledTokens(SM.getMainFileID())) {
300 auto Macro = locateMacroAt(Tok, PP);
301 if (!Macro)
302 continue;
303 if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid())
304 Macros.push_back(
305 {Tok.location(),
306 include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)),
307 DefLoc},
308 include_cleaner::RefType::Explicit});
309 }
310 return Macros;
311}
312
313include_cleaner::Includes
314convertIncludes(const SourceManager &SM,
315 const llvm::ArrayRef<Inclusion> Includes) {
316 include_cleaner::Includes ConvertedIncludes;
317 for (const Inclusion &Inc : Includes) {
318 include_cleaner::Include TransformedInc;
319 llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written);
320 TransformedInc.Spelled = WrittenRef.trim("\"<>");
321 TransformedInc.HashLocation =
322 SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset);
323 TransformedInc.Line = Inc.HashLine + 1;
324 TransformedInc.Angled = WrittenRef.starts_with("<");
325 auto FE = SM.getFileManager().getFile(Inc.Resolved);
326 if (!FE) {
327 elog("IncludeCleaner: Failed to get an entry for resolved path {0}: {1}",
328 Inc.Resolved, FE.getError().message());
329 continue;
330 }
331 TransformedInc.Resolved = *FE;
332 ConvertedIncludes.add(std::move(TransformedInc));
333 }
334 return ConvertedIncludes;
335}
336
337std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile,
338 include_cleaner::Header Provider) {
339 if (Provider.kind() == include_cleaner::Header::Physical) {
340 if (auto CanonicalPath =
341 getCanonicalPath(Provider.physical()->getLastRef(),
342 AST.getSourceManager().getFileManager())) {
343 std::string SpelledHeader =
344 llvm::cantFail(URI::includeSpelling(URI::create(*CanonicalPath)));
345 if (!SpelledHeader.empty())
346 return SpelledHeader;
347 }
348 }
349 return include_cleaner::spellHeader(
350 {Provider, AST.getPreprocessor().getHeaderSearchInfo(), MainFile});
351}
352
353std::vector<const Inclusion *>
355 const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles,
356 const llvm::StringSet<> &ReferencedPublicHeaders) {
357 trace::Span Tracer("IncludeCleaner::getUnused");
358 const Config &Cfg = Config::current();
359 std::vector<const Inclusion *> Unused;
360 for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) {
361 if (!MFI.HeaderID)
362 continue;
363 if (ReferencedPublicHeaders.contains(MFI.Written))
364 continue;
365 auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID);
366 bool Used = ReferencedFiles.contains(IncludeID);
367 if (!Used && !mayConsiderUnused(MFI, AST, Cfg, AST.getPragmaIncludes())) {
368 dlog("{0} was not used, but is not eligible to be diagnosed as unused",
369 MFI.Written);
370 continue;
371 }
372 if (!Used)
373 Unused.push_back(&MFI);
374 dlog("{0} is {1}", MFI.Written, Used ? "USED" : "UNUSED");
375 }
376 return Unused;
377}
378
380 const auto &SM = AST.getSourceManager();
381 const auto &Includes = AST.getIncludeStructure();
382 include_cleaner::Includes ConvertedIncludes =
383 convertIncludes(SM, Includes.MainFileIncludes);
384 const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
385 auto *PreamblePatch = PreamblePatch::getPatchEntry(AST.tuPath(), SM);
386
387 std::vector<include_cleaner::SymbolReference> Macros =
389 std::vector<MissingIncludeDiagInfo> MissingIncludes;
390 llvm::DenseSet<IncludeStructure::HeaderID> Used;
391 trace::Span Tracer("include_cleaner::walkUsed");
392 include_cleaner::walkUsed(
393 AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros,
394 AST.getPragmaIncludes(), SM,
395 [&](const include_cleaner::SymbolReference &Ref,
396 llvm::ArrayRef<include_cleaner::Header> Providers) {
397 bool Satisfied = false;
398 for (const auto &H : Providers) {
399 if (H.kind() == include_cleaner::Header::Physical &&
400 (H.physical() == MainFile || H.physical() == PreamblePatch)) {
401 Satisfied = true;
402 continue;
403 }
404 for (auto *Inc : ConvertedIncludes.match(H)) {
405 Satisfied = true;
406 auto HeaderID = Includes.getID(Inc->Resolved);
407 assert(HeaderID.has_value() &&
408 "ConvertedIncludes only contains resolved includes.");
409 Used.insert(*HeaderID);
410 }
411 }
412
413 if (Satisfied || Providers.empty() ||
414 Ref.RT != include_cleaner::RefType::Explicit)
415 return;
416
417 // We actually always want to map usages to their spellings, but
418 // spelling locations can point into preamble section. Using these
419 // offsets could lead into crashes in presence of stale preambles. Hence
420 // we use "getFileLoc" instead to make sure it always points into main
421 // file.
422 // FIXME: Use presumed locations to map such usages back to patched
423 // locations safely.
424 auto Loc = SM.getFileLoc(Ref.RefLocation);
425 // File locations can be outside of the main file if macro is expanded
426 // through an #include.
427 while (SM.getFileID(Loc) != SM.getMainFileID())
428 Loc = SM.getIncludeLoc(SM.getFileID(Loc));
429 auto TouchingTokens =
430 syntax::spelledTokensTouching(Loc, AST.getTokens());
431 assert(!TouchingTokens.empty());
432 // Loc points to the start offset of the ref token, here we use the last
433 // element of the TouchingTokens, e.g. avoid getting the "::" for
434 // "ns::^abc".
435 MissingIncludeDiagInfo DiagInfo{
436 Ref.Target, TouchingTokens.back().range(SM), Providers};
437 MissingIncludes.push_back(std::move(DiagInfo));
438 });
439 // Put possibly equal diagnostics together for deduplication.
440 // The duplicates might be from macro arguments that get expanded multiple
441 // times.
442 llvm::stable_sort(MissingIncludes, [](const MissingIncludeDiagInfo &LHS,
443 const MissingIncludeDiagInfo &RHS) {
444 // First sort by reference location.
445 if (LHS.SymRefRange != RHS.SymRefRange) {
446 // We can get away just by comparing the offsets as all the ranges are in
447 // main file.
448 return LHS.SymRefRange.beginOffset() < RHS.SymRefRange.beginOffset();
449 }
450 // For the same location, break ties using the symbol. Note that this won't
451 // be stable across runs.
452 using MapInfo = llvm::DenseMapInfo<include_cleaner::Symbol>;
453 return MapInfo::getHashValue(LHS.Symbol) <
454 MapInfo::getHashValue(RHS.Symbol);
455 });
456 MissingIncludes.erase(llvm::unique(MissingIncludes), MissingIncludes.end());
457 std::vector<const Inclusion *> UnusedIncludes =
458 getUnused(AST, Used, /*ReferencedPublicHeaders*/ {});
459 return {std::move(UnusedIncludes), std::move(MissingIncludes)};
460}
461
462std::optional<Fix> removeAllUnusedIncludes(llvm::ArrayRef<Diag> UnusedIncludes) {
463 if (UnusedIncludes.empty())
464 return std::nullopt;
465
466 Fix RemoveAll;
467 RemoveAll.Message = "remove all unused includes";
468 for (const auto &Diag : UnusedIncludes) {
469 assert(Diag.Fixes.size() == 1 && "Expected exactly one fix.");
470 RemoveAll.Edits.insert(RemoveAll.Edits.end(),
471 Diag.Fixes.front().Edits.begin(),
472 Diag.Fixes.front().Edits.end());
473 }
474
475 // TODO(hokein): emit a suitable text for the label.
476 ChangeAnnotation Annotation = {/*label=*/"",
477 /*needsConfirmation=*/true,
478 /*description=*/""};
479 static const ChangeAnnotationIdentifier RemoveAllUnusedID =
480 "RemoveAllUnusedIncludes";
481 for (unsigned I = 0; I < RemoveAll.Edits.size(); ++I) {
482 ChangeAnnotationIdentifier ID = RemoveAllUnusedID + std::to_string(I);
483 RemoveAll.Edits[I].annotationId = ID;
484 RemoveAll.Annotations.push_back({ID, Annotation});
485 }
486 return RemoveAll;
487}
488std::optional<Fix>
489addAllMissingIncludes(llvm::ArrayRef<Diag> MissingIncludeDiags) {
490 if (MissingIncludeDiags.empty())
491 return std::nullopt;
492
493 Fix AddAllMissing;
494 AddAllMissing.Message = "add all missing includes";
495 // A map to deduplicate the edits with the same new text.
496 // newText (#include "my_missing_header.h") -> TextEdit.
497 llvm::StringMap<TextEdit> Edits;
498 for (const auto &Diag : MissingIncludeDiags) {
499 assert(Diag.Fixes.size() == 1 && "Expected exactly one fix.");
500 for (const auto& Edit : Diag.Fixes.front().Edits) {
501 Edits.try_emplace(Edit.newText, Edit);
502 }
503 }
504 // FIXME(hokein): emit used symbol reference in the annotation.
505 ChangeAnnotation Annotation = {/*label=*/"",
506 /*needsConfirmation=*/true,
507 /*description=*/""};
508 static const ChangeAnnotationIdentifier AddAllMissingID =
509 "AddAllMissingIncludes";
510 unsigned I = 0;
511 for (auto &It : Edits) {
512 ChangeAnnotationIdentifier ID = AddAllMissingID + std::to_string(I++);
513 AddAllMissing.Edits.push_back(std::move(It.getValue()));
514 AddAllMissing.Edits.back().annotationId = ID;
515
516 AddAllMissing.Annotations.push_back({ID, Annotation});
517 }
518 return AddAllMissing;
519}
520Fix fixAll(const Fix& RemoveAllUnused, const Fix& AddAllMissing) {
521 Fix FixAll;
522 FixAll.Message = "fix all includes";
523
524 for (const auto &F : RemoveAllUnused.Edits)
525 FixAll.Edits.push_back(F);
526 for (const auto &F : AddAllMissing.Edits)
527 FixAll.Edits.push_back(F);
528
529 for (const auto& A : RemoveAllUnused.Annotations)
530 FixAll.Annotations.push_back(A);
531 for (const auto& A : AddAllMissing.Annotations)
532 FixAll.Annotations.push_back(A);
533 return FixAll;
534}
535
537 ParsedAST &AST, const IncludeCleanerFindings &Findings,
538 llvm::StringRef Code) {
539 std::vector<Diag> UnusedIncludes = generateUnusedIncludeDiagnostics(
540 AST.tuPath(), Findings.UnusedIncludes, Code);
541 std::optional<Fix> RemoveAllUnused = removeAllUnusedIncludes(UnusedIncludes);
542
543 std::vector<Diag> MissingIncludeDiags = generateMissingIncludeDiagnostics(
544 AST, Findings.MissingIncludes, Code);
545 std::optional<Fix> AddAllMissing = addAllMissingIncludes(MissingIncludeDiags);
546
547 std::optional<Fix> FixAll;
548 if (RemoveAllUnused && AddAllMissing)
549 FixAll = fixAll(*RemoveAllUnused, *AddAllMissing);
550
551 auto AddBatchFix = [](const std::optional<Fix> &F, clang::clangd::Diag *Out) {
552 if (!F) return;
553 Out->Fixes.push_back(*F);
554 };
555 for (auto &Diag : MissingIncludeDiags) {
556 AddBatchFix(MissingIncludeDiags.size() > 1
557 ? AddAllMissing
558 : std::nullopt,
559 &Diag);
560 AddBatchFix(FixAll, &Diag);
561 }
562 for (auto &Diag : UnusedIncludes) {
563 AddBatchFix(UnusedIncludes.size() > 1 ? RemoveAllUnused
564 : std::nullopt,
565 &Diag);
566 AddBatchFix(FixAll, &Diag);
567 }
568
569 auto Result = std::move(MissingIncludeDiags);
570 llvm::move(UnusedIncludes,
571 std::back_inserter(Result));
572 return Result;
573}
574
576 llvm::StringRef Code) {
577 // Interaction is only polished for C/CPP.
578 if (AST.getLangOpts().ObjC)
579 return {};
580
581 trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics");
582
583 const Config &Cfg = Config::current();
584 IncludeCleanerFindings Findings;
585 if (Cfg.Diagnostics.MissingIncludes == Config::IncludesPolicy::Strict ||
586 Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::Strict) {
587 // will need include-cleaner results, call it once
588 Findings = computeIncludeCleanerFindings(AST);
589 }
590 return generateIncludeCleanerDiagnostic(AST, Findings, Code);
591}
592
593std::optional<include_cleaner::Header>
594firstMatchedProvider(const include_cleaner::Includes &Includes,
595 llvm::ArrayRef<include_cleaner::Header> Providers) {
596 for (const auto &H : Providers) {
597 if (!Includes.match(H).empty())
598 return H;
599 }
600 // No match for this provider in the includes list.
601 return std::nullopt;
602}
603} // namespace clangd
604} // namespace clang
CompiledFragmentImpl & Out
std::string Code
const Criteria C
std::string MainFile
Include Cleaner is clangd functionality for providing diagnostics for misuse of transitive headers an...
StringRef FileName
SourceLocation Loc
#define dlog(...)
Definition: Logger.h:101
size_t HashOffset
std::vector< Inclusion > MainFileIncludes
Definition: Headers.h:174
Stores and provides access to parsed AST.
Definition: ParsedAST.h:47
Stores information required to parse a TU using a (possibly stale) Baseline preamble.
Definition: Preamble.h:127
static const FileEntry * getPatchEntry(llvm::StringRef MainFilePath, const SourceManager &SM)
Returns the FileEntry for the preamble patch of MainFilePath in SM, if any.
Definition: Preamble.cpp:929
static llvm::Expected< std::string > includeSpelling(const URI &U)
Gets the preferred spelling of this file for #include, if there is one, e.g.
Definition: URI.cpp:273
static llvm::Expected< URI > create(llvm::StringRef AbsolutePath, llvm::StringRef Scheme)
Creates a URI for a file in the given scheme.
Definition: URI.cpp:209
Records an event whose duration is the lifetime of the Span object.
Definition: Trace.h:143
std::vector< Diag > issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code)
std::optional< Fix > addAllMissingIncludes(llvm::ArrayRef< Diag > MissingIncludeDiags)
Position offsetToPosition(llvm::StringRef Code, size_t Offset)
Turn an offset in Code into a [line, column] pair.
Definition: SourceCode.cpp:202
size_t lspLength(llvm::StringRef Code)
Definition: SourceCode.cpp:149
Fix fixAll(const Fix &RemoveAllUnused, const Fix &AddAllMissing)
IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST)
std::optional< Fix > removeAllUnusedIncludes(llvm::ArrayRef< Diag > UnusedIncludes)
std::vector< include_cleaner::SymbolReference > collectMacroReferences(ParsedAST &AST)
void setIncludeCleanerAnalyzesStdlib(bool B)
Affects whether standard library includes should be considered for removal.
std::vector< Diag > generateIncludeCleanerDiagnostic(ParsedAST &AST, const IncludeCleanerFindings &Findings, llvm::StringRef Code)
std::optional< DefinedMacro > locateMacroAt(const syntax::Token &SpelledTok, Preprocessor &PP)
Gets the macro referenced by SpelledTok.
Definition: SourceCode.cpp:981
static bool AnalyzeStdlib
std::optional< std::string > getCanonicalPath(const FileEntryRef F, FileManager &FileMgr)
Get the canonical path of F.
Definition: SourceCode.cpp:516
include_cleaner::Includes convertIncludes(const SourceManager &SM, const llvm::ArrayRef< Inclusion > Includes)
Converts the clangd include representation to include-cleaner include representation.
std::string ChangeAnnotationIdentifier
Definition: Protocol.h:241
std::optional< include_cleaner::Header > firstMatchedProvider(const include_cleaner::Includes &Includes, llvm::ArrayRef< include_cleaner::Header > Providers)
Find the first provider in the list that is matched by the includes.
std::vector< const Inclusion * > getUnused(ParsedAST &AST, const llvm::DenseSet< IncludeStructure::HeaderID > &ReferencedFiles, const llvm::StringSet<> &ReferencedPublicHeaders)
Retrieves headers that are referenced from the main file but not used.
@ Unnecessary
Unused or unnecessary code.
Definition: Protocol.h:901
std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile, include_cleaner::Header Provider)
Determines the header spelling of an include-cleaner header representation.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition: Path.h:29
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:61
TextEdit replacementToEdit(llvm::StringRef Code, const tooling::Replacement &R)
Definition: SourceCode.cpp:500
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Settings that express user/project preferences and control clangd behavior.
Definition: Config.h:44
struct clang::clangd::Config::@4::@13 Includes
IncludeCleaner will not diagnose usages of these headers matched by these regexes.
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
Definition: Config.cpp:17
@ Strict
Diagnose missing and unused includes.
llvm::StringSet Suppress
Definition: Config.h:99
std::vector< std::function< bool(llvm::StringRef)> > IgnoreHeader
Definition: Config.h:117
IncludesPolicy UnusedIncludes
Definition: Config.h:111
IncludesPolicy MissingIncludes
Definition: Config.h:112
struct clang::clangd::Config::@4 Diagnostics
Controls warnings and errors when parsing code.
A top-level diagnostic that may have Notes and Fixes.
Definition: Diagnostics.h:98
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
Definition: Diagnostics.h:111
A set of edits generated for a single file.
Definition: SourceCode.h:185
Represents a single fix-it that editor can apply to fix the error.
Definition: Diagnostics.h:81
llvm::SmallVector< std::pair< ChangeAnnotationIdentifier, ChangeAnnotation > > Annotations
Annotations for the Edits.
Definition: Diagnostics.h:89
std::string Message
Message for the fix-it.
Definition: Diagnostics.h:83
llvm::SmallVector< TextEdit, 1 > Edits
TextEdits from clang's fix-its. Must be non-empty.
Definition: Diagnostics.h:85
std::vector< const Inclusion * > UnusedIncludes
std::vector< MissingIncludeDiagInfo > MissingIncludes
Represents a symbol occurrence in the source file.
Definition: Ref.h:85