File: | build/source/clang-tools-extra/clangd/IncludeCleaner.cpp |
Warning: | line 204, column 12 Called C++ object pointer is null |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 "Protocol.h" | |||
15 | #include "SourceCode.h" | |||
16 | #include "URI.h" | |||
17 | #include "clang-include-cleaner/Analysis.h" | |||
18 | #include "clang-include-cleaner/Record.h" | |||
19 | #include "clang-include-cleaner/Types.h" | |||
20 | #include "support/Logger.h" | |||
21 | #include "support/Path.h" | |||
22 | #include "support/Trace.h" | |||
23 | #include "clang/AST/ASTContext.h" | |||
24 | #include "clang/AST/DeclCXX.h" | |||
25 | #include "clang/AST/Expr.h" | |||
26 | #include "clang/AST/ExprCXX.h" | |||
27 | #include "clang/AST/TemplateName.h" | |||
28 | #include "clang/AST/Type.h" | |||
29 | #include "clang/Basic/Diagnostic.h" | |||
30 | #include "clang/Basic/LLVM.h" | |||
31 | #include "clang/Basic/SourceLocation.h" | |||
32 | #include "clang/Basic/SourceManager.h" | |||
33 | #include "clang/Format/Format.h" | |||
34 | #include "clang/Lex/HeaderSearch.h" | |||
35 | #include "clang/Lex/Preprocessor.h" | |||
36 | #include "clang/Tooling/Core/Replacement.h" | |||
37 | #include "clang/Tooling/Inclusions/HeaderIncludes.h" | |||
38 | #include "clang/Tooling/Inclusions/StandardLibrary.h" | |||
39 | #include "clang/Tooling/Syntax/Tokens.h" | |||
40 | #include "llvm/ADT/ArrayRef.h" | |||
41 | #include "llvm/ADT/DenseSet.h" | |||
42 | #include "llvm/ADT/STLExtras.h" | |||
43 | #include "llvm/ADT/STLFunctionalExtras.h" | |||
44 | #include "llvm/ADT/SmallString.h" | |||
45 | #include "llvm/ADT/SmallVector.h" | |||
46 | #include "llvm/ADT/StringRef.h" | |||
47 | #include "llvm/ADT/StringSet.h" | |||
48 | #include "llvm/Support/Casting.h" | |||
49 | #include "llvm/Support/Error.h" | |||
50 | #include "llvm/Support/ErrorHandling.h" | |||
51 | #include "llvm/Support/FormatVariadic.h" | |||
52 | #include "llvm/Support/Path.h" | |||
53 | #include "llvm/Support/Regex.h" | |||
54 | #include <iterator> | |||
55 | #include <optional> | |||
56 | #include <string> | |||
57 | #include <vector> | |||
58 | ||||
59 | namespace clang { | |||
60 | namespace clangd { | |||
61 | ||||
62 | static bool AnalyzeStdlib = false; | |||
63 | void setIncludeCleanerAnalyzesStdlib(bool B) { AnalyzeStdlib = B; } | |||
64 | ||||
65 | namespace { | |||
66 | ||||
67 | // Returns the range starting at '#' and ending at EOL. Escaped newlines are not | |||
68 | // handled. | |||
69 | clangd::Range getDiagnosticRange(llvm::StringRef Code, unsigned HashOffset) { | |||
70 | clangd::Range Result; | |||
71 | Result.end = Result.start = offsetToPosition(Code, HashOffset); | |||
72 | ||||
73 | // Span the warning until the EOL or EOF. | |||
74 | Result.end.character += | |||
75 | lspLength(Code.drop_front(HashOffset).take_until([](char C) { | |||
76 | return C == '\n' || C == '\r'; | |||
77 | })); | |||
78 | return Result; | |||
79 | } | |||
80 | ||||
81 | ||||
82 | bool isFilteredByConfig(const Config &Cfg, llvm::StringRef HeaderPath) { | |||
83 | // Convert the path to Unix slashes and try to match against the filter. | |||
84 | llvm::SmallString<64> NormalizedPath(HeaderPath); | |||
85 | llvm::sys::path::native(NormalizedPath, llvm::sys::path::Style::posix); | |||
86 | for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) { | |||
87 | if (Filter(NormalizedPath)) | |||
88 | return true; | |||
89 | } | |||
90 | return false; | |||
91 | } | |||
92 | ||||
93 | static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, | |||
94 | const Config &Cfg, | |||
95 | const include_cleaner::PragmaIncludes *PI) { | |||
96 | if (PI && PI->shouldKeep(Inc.HashLine + 1)) | |||
97 | return false; | |||
98 | // FIXME(kirillbobyrev): We currently do not support the umbrella headers. | |||
99 | // System headers are likely to be standard library headers. | |||
100 | // Until we have good support for umbrella headers, don't warn about them. | |||
101 | if (Inc.Written.front() == '<') { | |||
102 | if (AnalyzeStdlib && tooling::stdlib::Header::named(Inc.Written)) | |||
103 | return true; | |||
104 | return false; | |||
105 | } | |||
106 | assert(Inc.HeaderID)(static_cast <bool> (Inc.HeaderID) ? void (0) : __assert_fail ("Inc.HeaderID", "clang-tools-extra/clangd/IncludeCleaner.cpp" , 106, __extension__ __PRETTY_FUNCTION__)); | |||
107 | auto HID = static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID); | |||
108 | auto FE = AST.getSourceManager().getFileManager().getFileRef( | |||
109 | AST.getIncludeStructure().getRealPath(HID)); | |||
110 | assert(FE)(static_cast <bool> (FE) ? void (0) : __assert_fail ("FE" , "clang-tools-extra/clangd/IncludeCleaner.cpp", 110, __extension__ __PRETTY_FUNCTION__)); | |||
111 | // Headers without include guards have side effects and are not | |||
112 | // self-contained, skip them. | |||
113 | if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded( | |||
114 | &FE->getFileEntry())) { | |||
115 | dlog("{0} doesn't have header guard and will not be considered unused",do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "{0} doesn't have header guard and will not be considered unused" , FE->getName()); } } while (false) | |||
116 | FE->getName())do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "{0} doesn't have header guard and will not be considered unused" , FE->getName()); } } while (false); | |||
117 | return false; | |||
118 | } | |||
119 | ||||
120 | if (isFilteredByConfig(Cfg, Inc.Resolved)) { | |||
121 | dlog("{0} header is filtered out by the configuration", FE->getName())do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "{0} header is filtered out by the configuration" , FE->getName()); } } while (false); | |||
122 | return false; | |||
123 | } | |||
124 | return true; | |||
125 | } | |||
126 | ||||
127 | include_cleaner::Includes | |||
128 | convertIncludes(const SourceManager &SM, | |||
129 | const llvm::ArrayRef<Inclusion> MainFileIncludes) { | |||
130 | include_cleaner::Includes Includes; | |||
131 | for (const Inclusion &Inc : MainFileIncludes) { | |||
132 | include_cleaner::Include TransformedInc; | |||
133 | llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written); | |||
134 | TransformedInc.Spelled = WrittenRef.trim("\"<>"); | |||
135 | TransformedInc.HashLocation = | |||
136 | SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); | |||
137 | TransformedInc.Line = Inc.HashLine + 1; | |||
138 | TransformedInc.Angled = WrittenRef.starts_with("<"); | |||
139 | auto FE = SM.getFileManager().getFile(Inc.Resolved); | |||
140 | if (!FE) { | |||
141 | elog("IncludeCleaner: Failed to get an entry for resolved path {0}: {1}", | |||
142 | Inc.Resolved, FE.getError().message()); | |||
143 | continue; | |||
144 | } | |||
145 | TransformedInc.Resolved = *FE; | |||
146 | Includes.add(std::move(TransformedInc)); | |||
147 | } | |||
148 | return Includes; | |||
149 | } | |||
150 | ||||
151 | std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile, | |||
152 | include_cleaner::Header Provider) { | |||
153 | if (Provider.kind() == include_cleaner::Header::Physical) { | |||
154 | if (auto CanonicalPath = | |||
155 | getCanonicalPath(Provider.physical(), AST.getSourceManager())) { | |||
156 | std::string SpelledHeader = | |||
157 | llvm::cantFail(URI::includeSpelling(URI::create(*CanonicalPath))); | |||
158 | if (!SpelledHeader.empty()) | |||
159 | return SpelledHeader; | |||
160 | } | |||
161 | } | |||
162 | return include_cleaner::spellHeader( | |||
163 | Provider, AST.getPreprocessor().getHeaderSearchInfo(), MainFile); | |||
164 | } | |||
165 | ||||
166 | std::vector<include_cleaner::SymbolReference> | |||
167 | collectMacroReferences(ParsedAST &AST) { | |||
168 | const auto &SM = AST.getSourceManager(); | |||
169 | // FIXME: !!this is a hacky way to collect macro references. | |||
170 | std::vector<include_cleaner::SymbolReference> Macros; | |||
171 | auto &PP = AST.getPreprocessor(); | |||
172 | for (const syntax::Token &Tok : | |||
173 | AST.getTokens().spelledTokens(SM.getMainFileID())) { | |||
174 | auto Macro = locateMacroAt(Tok, PP); | |||
175 | if (!Macro) | |||
176 | continue; | |||
177 | if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) | |||
178 | Macros.push_back( | |||
179 | {Tok.location(), | |||
180 | include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), | |||
181 | DefLoc}, | |||
182 | include_cleaner::RefType::Explicit}); | |||
183 | } | |||
184 | return Macros; | |||
185 | } | |||
186 | ||||
187 | llvm::StringRef getResolvedPath(const include_cleaner::Header &SymProvider) { | |||
188 | switch (SymProvider.kind()) { | |||
189 | case include_cleaner::Header::Physical: | |||
190 | return SymProvider.physical()->tryGetRealPathName(); | |||
191 | case include_cleaner::Header::Standard: | |||
192 | return SymProvider.standard().name().trim("<>\""); | |||
193 | case include_cleaner::Header::Verbatim: | |||
194 | return SymProvider.verbatim().trim("<>\""); | |||
195 | } | |||
196 | llvm_unreachable("Unknown header kind")::llvm::llvm_unreachable_internal("Unknown header kind", "clang-tools-extra/clangd/IncludeCleaner.cpp" , 196); | |||
197 | } | |||
198 | ||||
199 | std::string getSymbolName(const include_cleaner::Symbol &Sym) { | |||
200 | switch (Sym.kind()) { | |||
201 | case include_cleaner::Symbol::Macro: | |||
202 | return Sym.macro().Name->getName().str(); | |||
203 | case include_cleaner::Symbol::Declaration: | |||
204 | return llvm::dyn_cast<NamedDecl>(&Sym.declaration()) | |||
| ||||
205 | ->getQualifiedNameAsString(); | |||
206 | } | |||
207 | llvm_unreachable("Unknown symbol kind")::llvm::llvm_unreachable_internal("Unknown symbol kind", "clang-tools-extra/clangd/IncludeCleaner.cpp" , 207); | |||
208 | } | |||
209 | ||||
210 | std::vector<Diag> generateMissingIncludeDiagnostics( | |||
211 | ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes, | |||
212 | llvm::StringRef Code) { | |||
213 | std::vector<Diag> Result; | |||
214 | const Config &Cfg = Config::current(); | |||
215 | if (Cfg.Diagnostics.MissingIncludes != Config::IncludesPolicy::Strict || | |||
216 | Cfg.Diagnostics.SuppressAll || | |||
217 | Cfg.Diagnostics.Suppress.contains("missing-includes")) { | |||
218 | return Result; | |||
219 | } | |||
220 | ||||
221 | const SourceManager &SM = AST.getSourceManager(); | |||
222 | const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); | |||
223 | ||||
224 | auto FileStyle = format::getStyle( | |||
225 | format::DefaultFormatStyle, AST.tuPath(), format::DefaultFallbackStyle, | |||
226 | Code, &SM.getFileManager().getVirtualFileSystem()); | |||
227 | if (!FileStyle) { | |||
228 | elog("Couldn't infer style", FileStyle.takeError()); | |||
229 | FileStyle = format::getLLVMStyle(); | |||
230 | } | |||
231 | ||||
232 | tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code, | |||
233 | FileStyle->IncludeStyle); | |||
234 | for (const auto &SymbolWithMissingInclude : MissingIncludes) { | |||
235 | llvm::StringRef ResolvedPath = | |||
236 | getResolvedPath(SymbolWithMissingInclude.Providers.front()); | |||
237 | if (isFilteredByConfig(Cfg, ResolvedPath)) { | |||
238 | dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by "do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "IncludeCleaner: not diagnosing missing include {0}, filtered by " "config", ResolvedPath); } } while (false) | |||
239 | "config",do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "IncludeCleaner: not diagnosing missing include {0}, filtered by " "config", ResolvedPath); } } while (false) | |||
240 | ResolvedPath)do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "IncludeCleaner: not diagnosing missing include {0}, filtered by " "config", ResolvedPath); } } while (false); | |||
241 | continue; | |||
242 | } | |||
243 | ||||
244 | std::string Spelling = | |||
245 | spellHeader(AST, MainFile, SymbolWithMissingInclude.Providers.front()); | |||
246 | llvm::StringRef HeaderRef{Spelling}; | |||
247 | bool Angled = HeaderRef.starts_with("<"); | |||
248 | // We might suggest insertion of an existing include in edge cases, e.g., | |||
249 | // include is present in a PP-disabled region, or spelling of the header | |||
250 | // turns out to be the same as one of the unresolved includes in the | |||
251 | // main file. | |||
252 | std::optional<tooling::Replacement> Replacement = HeaderIncludes.insert( | |||
253 | HeaderRef.trim("\"<>"), Angled, tooling::IncludeDirective::Include); | |||
254 | if (!Replacement.has_value()) | |||
255 | continue; | |||
256 | ||||
257 | Diag &D = Result.emplace_back(); | |||
258 | D.Message = | |||
259 | llvm::formatv("No header providing \"{0}\" is directly included", | |||
260 | getSymbolName(SymbolWithMissingInclude.Symbol)); | |||
261 | D.Name = "missing-includes"; | |||
262 | D.Source = Diag::DiagSource::Clangd; | |||
263 | D.File = AST.tuPath(); | |||
264 | D.InsideMainFile = true; | |||
265 | D.Severity = DiagnosticsEngine::Warning; | |||
266 | D.Range = clangd::Range{ | |||
267 | offsetToPosition(Code, | |||
268 | SymbolWithMissingInclude.SymRefRange.beginOffset()), | |||
269 | offsetToPosition(Code, | |||
270 | SymbolWithMissingInclude.SymRefRange.endOffset())}; | |||
271 | auto &F = D.Fixes.emplace_back(); | |||
272 | F.Message = "#include " + Spelling; | |||
273 | TextEdit Edit = replacementToEdit(Code, *Replacement); | |||
274 | F.Edits.emplace_back(std::move(Edit)); | |||
275 | } | |||
276 | return Result; | |||
277 | } | |||
278 | ||||
279 | std::vector<Diag> generateUnusedIncludeDiagnostics( | |||
280 | PathRef FileName, llvm::ArrayRef<const Inclusion *> UnusedIncludes, | |||
281 | llvm::StringRef Code) { | |||
282 | std::vector<Diag> Result; | |||
283 | const Config &Cfg = Config::current(); | |||
284 | if (Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::None || | |||
285 | Cfg.Diagnostics.SuppressAll || | |||
286 | Cfg.Diagnostics.Suppress.contains("unused-includes")) { | |||
287 | return Result; | |||
288 | } | |||
289 | for (const auto *Inc : UnusedIncludes) { | |||
290 | Diag &D = Result.emplace_back(); | |||
291 | D.Message = | |||
292 | llvm::formatv("included header {0} is not used directly", | |||
293 | llvm::sys::path::filename( | |||
294 | Inc->Written.substr(1, Inc->Written.size() - 2), | |||
295 | llvm::sys::path::Style::posix)); | |||
296 | D.Name = "unused-includes"; | |||
297 | D.Source = Diag::DiagSource::Clangd; | |||
298 | D.File = FileName; | |||
299 | D.InsideMainFile = true; | |||
300 | D.Severity = DiagnosticsEngine::Warning; | |||
301 | D.Tags.push_back(Unnecessary); | |||
302 | D.Range = getDiagnosticRange(Code, Inc->HashOffset); | |||
303 | // FIXME(kirillbobyrev): Removing inclusion might break the code if the | |||
304 | // used headers are only reachable transitively through this one. Suggest | |||
305 | // including them directly instead. | |||
306 | // FIXME(kirillbobyrev): Add fix suggestion for adding IWYU pragmas | |||
307 | // (keep/export) remove the warning once we support IWYU pragmas. | |||
308 | auto &F = D.Fixes.emplace_back(); | |||
309 | F.Message = "remove #include directive"; | |||
310 | F.Edits.emplace_back(); | |||
311 | F.Edits.back().range.start.line = Inc->HashLine; | |||
312 | F.Edits.back().range.end.line = Inc->HashLine + 1; | |||
313 | } | |||
314 | return Result; | |||
315 | } | |||
316 | } // namespace | |||
317 | ||||
318 | ||||
319 | std::vector<const Inclusion *> | |||
320 | getUnused(ParsedAST &AST, | |||
321 | const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles, | |||
322 | const llvm::StringSet<> &ReferencedPublicHeaders) { | |||
323 | trace::Span Tracer("IncludeCleaner::getUnused"); | |||
324 | const Config &Cfg = Config::current(); | |||
325 | std::vector<const Inclusion *> Unused; | |||
326 | for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) { | |||
327 | if (!MFI.HeaderID) | |||
328 | continue; | |||
329 | if (ReferencedPublicHeaders.contains(MFI.Written)) | |||
330 | continue; | |||
331 | auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID); | |||
332 | bool Used = ReferencedFiles.contains(IncludeID); | |||
333 | if (!Used && !mayConsiderUnused(MFI, AST, Cfg, AST.getPragmaIncludes())) { | |||
334 | dlog("{0} was not used, but is not eligible to be diagnosed as unused",do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "{0} was not used, but is not eligible to be diagnosed as unused" , MFI.Written); } } while (false) | |||
335 | MFI.Written)do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "{0} was not used, but is not eligible to be diagnosed as unused" , MFI.Written); } } while (false); | |||
336 | continue; | |||
337 | } | |||
338 | if (!Used) | |||
339 | Unused.push_back(&MFI); | |||
340 | dlog("{0} is {1}", MFI.Written, Used ? "USED" : "UNUSED")do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType (::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp" ))) { ::clang::clangd::detail::log(Logger::Debug, "{0} is {1}" , MFI.Written, Used ? "USED" : "UNUSED"); } } while (false); | |||
341 | } | |||
342 | return Unused; | |||
343 | } | |||
344 | ||||
345 | IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST) { | |||
346 | const auto &SM = AST.getSourceManager(); | |||
347 | const auto &Includes = AST.getIncludeStructure(); | |||
348 | include_cleaner::Includes ConvertedIncludes = | |||
349 | convertIncludes(SM, Includes.MainFileIncludes); | |||
350 | const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); | |||
351 | ||||
352 | std::vector<include_cleaner::SymbolReference> Macros = | |||
353 | collectMacroReferences(AST); | |||
354 | std::vector<MissingIncludeDiagInfo> MissingIncludes; | |||
355 | llvm::DenseSet<IncludeStructure::HeaderID> Used; | |||
356 | trace::Span Tracer("include_cleaner::walkUsed"); | |||
357 | include_cleaner::walkUsed( | |||
358 | AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros, | |||
359 | AST.getPragmaIncludes(), SM, | |||
360 | [&](const include_cleaner::SymbolReference &Ref, | |||
361 | llvm::ArrayRef<include_cleaner::Header> Providers) { | |||
362 | bool Satisfied = false; | |||
363 | for (const auto &H : Providers) { | |||
364 | if (H.kind() == include_cleaner::Header::Physical && | |||
365 | H.physical() == MainFile) { | |||
366 | Satisfied = true; | |||
367 | continue; | |||
368 | } | |||
369 | for (auto *Inc : ConvertedIncludes.match(H)) { | |||
370 | Satisfied = true; | |||
371 | auto HeaderID = Includes.getID(Inc->Resolved); | |||
372 | assert(HeaderID.has_value() &&(static_cast <bool> (HeaderID.has_value() && "ConvertedIncludes only contains resolved includes." ) ? void (0) : __assert_fail ("HeaderID.has_value() && \"ConvertedIncludes only contains resolved includes.\"" , "clang-tools-extra/clangd/IncludeCleaner.cpp", 373, __extension__ __PRETTY_FUNCTION__)) | |||
373 | "ConvertedIncludes only contains resolved includes.")(static_cast <bool> (HeaderID.has_value() && "ConvertedIncludes only contains resolved includes." ) ? void (0) : __assert_fail ("HeaderID.has_value() && \"ConvertedIncludes only contains resolved includes.\"" , "clang-tools-extra/clangd/IncludeCleaner.cpp", 373, __extension__ __PRETTY_FUNCTION__)); | |||
374 | Used.insert(*HeaderID); | |||
375 | } | |||
376 | } | |||
377 | ||||
378 | if (Satisfied || Providers.empty() || | |||
379 | Ref.RT != include_cleaner::RefType::Explicit) | |||
380 | return; | |||
381 | ||||
382 | auto &Tokens = AST.getTokens(); | |||
383 | auto SpelledForExpanded = | |||
384 | Tokens.spelledForExpanded(Tokens.expandedTokens(Ref.RefLocation)); | |||
385 | if (!SpelledForExpanded) | |||
386 | return; | |||
387 | ||||
388 | auto Range = syntax::Token::range(SM, SpelledForExpanded->front(), | |||
389 | SpelledForExpanded->back()); | |||
390 | MissingIncludeDiagInfo DiagInfo{Ref.Target, Range, Providers}; | |||
391 | MissingIncludes.push_back(std::move(DiagInfo)); | |||
392 | }); | |||
393 | std::vector<const Inclusion *> UnusedIncludes = | |||
394 | getUnused(AST, Used, /*ReferencedPublicHeaders*/ {}); | |||
395 | return {std::move(UnusedIncludes), std::move(MissingIncludes)}; | |||
396 | } | |||
397 | ||||
398 | std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST, | |||
399 | llvm::StringRef Code) { | |||
400 | // Interaction is only polished for C/CPP. | |||
401 | if (AST.getLangOpts().ObjC) | |||
| ||||
402 | return {}; | |||
403 | ||||
404 | trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics"); | |||
405 | ||||
406 | const Config &Cfg = Config::current(); | |||
407 | IncludeCleanerFindings Findings; | |||
408 | if (Cfg.Diagnostics.MissingIncludes == Config::IncludesPolicy::Strict || | |||
409 | Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::Strict) { | |||
410 | // will need include-cleaner results, call it once | |||
411 | Findings = computeIncludeCleanerFindings(AST); | |||
412 | } | |||
413 | ||||
414 | std::vector<Diag> Result = generateUnusedIncludeDiagnostics( | |||
415 | AST.tuPath(), Findings.UnusedIncludes, Code); | |||
416 | llvm::move( | |||
417 | generateMissingIncludeDiagnostics(AST, Findings.MissingIncludes, Code), | |||
418 | std::back_inserter(Result)); | |||
419 | return Result; | |||
420 | } | |||
421 | ||||
422 | } // namespace clangd | |||
423 | } // namespace clang |