1//===--- HeaderIncludes.cpp - Insert/Delete #includes --*- 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 "clang/Tooling/Inclusions/HeaderIncludes.h"
10#include "clang/Basic/LLVM.h"
11#include "clang/Basic/SourceManager.h"
12#include "clang/Basic/TokenKinds.h"
13#include "clang/Lex/Lexer.h"
14#include "clang/Lex/Token.h"
15#include "clang/Tooling/Core/Replacement.h"
16#include "clang/Tooling/Inclusions/IncludeStyle.h"
17#include "llvm/ADT/STLFunctionalExtras.h"
18#include "llvm/ADT/StringRef.h"
19#include "llvm/Support/Error.h"
20#include "llvm/Support/ErrorHandling.h"
21#include "llvm/Support/FormatVariadic.h"
22#include "llvm/Support/Path.h"
23#include "llvm/Support/Regex.h"
24#include <algorithm>
25#include <cassert>
26#include <climits>
27#include <functional>
28#include <optional>
29#include <string>
30#include <type_traits>
31#include <utility>
32
33namespace clang {
34namespace tooling {
35namespace {
36
37LangOptions createLangOpts() {
38 LangOptions LangOpts;
39 LangOpts.CPlusPlus = 1;
40 LangOpts.CPlusPlus11 = 1;
41 LangOpts.CPlusPlus14 = 1;
42 LangOpts.LineComment = 1;
43 LangOpts.CXXOperatorNames = 1;
44 LangOpts.Bool = 1;
45 LangOpts.ObjC = 1;
46 LangOpts.MicrosoftExt = 1; // To get kw___try, kw___finally.
47 LangOpts.DeclSpecKeyword = 1; // To get __declspec.
48 LangOpts.WChar = 1; // To get wchar_t
49 return LangOpts;
50}
51
52// Create a new lexer on the given \p Code and calls \p Callback with the
53// created source manager and lexer. \p Callback must be a callable object that
54// could be invoked with (const SourceManager &, Lexer &). This function returns
55// whatever \p Callback returns.
56template <typename F>
57auto withLexer(StringRef FileName, StringRef Code, const IncludeStyle &Style,
58 F &&Callback)
59 -> std::invoke_result_t<F, const SourceManager &, Lexer &> {
60 SourceManagerForFile VirtualSM(FileName, Code);
61 SourceManager &SM = VirtualSM.get();
62 LangOptions LangOpts = createLangOpts();
63 Lexer Lex(SM.getMainFileID(), SM.getBufferOrFake(FID: SM.getMainFileID()), SM,
64 LangOpts);
65 return std::invoke(std::forward<F>(Callback), std::as_const(t&: SM), Lex);
66}
67
68// Returns the offset after skipping a sequence of tokens, matched by \p
69// GetOffsetAfterSequence, from the start of the code.
70// \p GetOffsetAfterSequence should be a function that matches a sequence of
71// tokens and returns an offset after the sequence.
72unsigned getOffsetAfterTokenSequence(
73 StringRef FileName, StringRef Code, const IncludeStyle &Style,
74 llvm::function_ref<unsigned(const SourceManager &, Lexer &, Token &)>
75 GetOffsetAfterSequence) {
76 return withLexer(FileName, Code, Style,
77 Callback: [&](const SourceManager &SM, Lexer &Lex) {
78 Token Tok;
79 // Get the first token.
80 Lex.LexFromRawLexer(Result&: Tok);
81 return GetOffsetAfterSequence(SM, Lex, Tok);
82 });
83}
84
85// Check if a sequence of tokens is like "#<Name> <raw_identifier>". If it is,
86// \p Tok will be the token after this directive; otherwise, it can be any token
87// after the given \p Tok (including \p Tok). If \p RawIDName is provided, the
88// (second) raw_identifier name is checked.
89bool checkAndConsumeDirectiveWithName(
90 Lexer &Lex, StringRef Name, Token &Tok,
91 std::optional<StringRef> RawIDName = std::nullopt) {
92 bool Matched = Tok.is(K: tok::hash) && !Lex.LexFromRawLexer(Result&: Tok) &&
93 Tok.is(K: tok::raw_identifier) &&
94 Tok.getRawIdentifier() == Name && !Lex.LexFromRawLexer(Result&: Tok) &&
95 Tok.is(K: tok::raw_identifier) &&
96 (!RawIDName || Tok.getRawIdentifier() == *RawIDName);
97 if (Matched)
98 Lex.LexFromRawLexer(Result&: Tok);
99 return Matched;
100}
101
102void skipComments(Lexer &Lex, Token &Tok) {
103 while (Tok.is(K: tok::comment))
104 if (Lex.LexFromRawLexer(Result&: Tok))
105 return;
106}
107
108bool checkAndConsumeModuleDecl(const SourceManager &SM, Lexer &Lex,
109 Token &Tok) {
110 bool Matched = Tok.is(K: tok::raw_identifier) &&
111 Tok.getRawIdentifier() == "module" &&
112 !Lex.LexFromRawLexer(Result&: Tok) && Tok.is(K: tok::semi) &&
113 !Lex.LexFromRawLexer(Result&: Tok);
114 return Matched;
115}
116
117// Determines the minimum offset into the file where we want to insert header
118// includes. This will be put (when available):
119// - after `#pragma once`
120// - after header guards (`#ifdef` and `#define`)
121// - after opening global module (`module;`)
122// - after any comments at the start of the file or immediately following one of
123// the above constructs
124unsigned getMinHeaderInsertionOffset(StringRef FileName, StringRef Code,
125 const IncludeStyle &Style) {
126 // \p Consume returns location after header guard or 0 if no header guard is
127 // found.
128 auto ConsumeHeaderGuardAndComment =
129 [&](std::function<unsigned(const SourceManager &SM, Lexer &Lex,
130 Token Tok)>
131 Consume) {
132 return getOffsetAfterTokenSequence(
133 FileName, Code, Style,
134 GetOffsetAfterSequence: [&Consume](const SourceManager &SM, Lexer &Lex, Token Tok) {
135 skipComments(Lex, Tok);
136 unsigned InitialOffset = SM.getFileOffset(SpellingLoc: Tok.getLocation());
137 return std::max(a: InitialOffset, b: Consume(SM, Lex, Tok));
138 });
139 };
140
141 auto ModuleDecl = ConsumeHeaderGuardAndComment(
142 [](const SourceManager &SM, Lexer &Lex, Token Tok) -> unsigned {
143 if (checkAndConsumeModuleDecl(SM, Lex, Tok)) {
144 skipComments(Lex, Tok);
145 return SM.getFileOffset(SpellingLoc: Tok.getLocation());
146 }
147 return 0;
148 });
149
150 auto HeaderAndPPOffset = std::max(
151 // #ifndef/#define
152 a: ConsumeHeaderGuardAndComment(
153 [](const SourceManager &SM, Lexer &Lex, Token Tok) -> unsigned {
154 if (checkAndConsumeDirectiveWithName(Lex, Name: "ifndef", Tok)) {
155 skipComments(Lex, Tok);
156 if (checkAndConsumeDirectiveWithName(Lex, Name: "define", Tok) &&
157 Tok.isAtStartOfLine())
158 return SM.getFileOffset(SpellingLoc: Tok.getLocation());
159 }
160 return 0;
161 }),
162 // #pragma once
163 b: ConsumeHeaderGuardAndComment(
164 [](const SourceManager &SM, Lexer &Lex, Token Tok) -> unsigned {
165 if (checkAndConsumeDirectiveWithName(Lex, Name: "pragma", Tok,
166 RawIDName: StringRef("once")))
167 return SM.getFileOffset(SpellingLoc: Tok.getLocation());
168 return 0;
169 }));
170 return std::max(a: HeaderAndPPOffset, b: ModuleDecl);
171}
172
173// Check if a sequence of tokens is like
174// "#(include | import) ("header.h" | <header.h>)".
175// If it is, \p Tok will be the token after this directive; otherwise, it can be
176// any token after the given \p Tok (including \p Tok).
177bool checkAndConsumeInclusiveDirective(Lexer &Lex, Token &Tok) {
178 auto Matched = [&]() {
179 Lex.LexFromRawLexer(Result&: Tok);
180 return true;
181 };
182 if (Tok.is(K: tok::hash) && !Lex.LexFromRawLexer(Result&: Tok) &&
183 Tok.is(K: tok::raw_identifier) &&
184 (Tok.getRawIdentifier() == "include" ||
185 Tok.getRawIdentifier() == "import")) {
186 if (Lex.LexFromRawLexer(Result&: Tok))
187 return false;
188 if (Tok.is(K: tok::string_literal))
189 return Matched();
190 if (Tok.is(K: tok::less)) {
191 while (!Lex.LexFromRawLexer(Result&: Tok) && Tok.isNot(K: tok::greater)) {
192 }
193 if (Tok.is(K: tok::greater))
194 return Matched();
195 }
196 }
197 return false;
198}
199
200// Returns the offset of the last #include directive after which a new
201// #include can be inserted. This ignores #include's after the #include block(s)
202// in the beginning of a file to avoid inserting headers into code sections
203// where new #include's should not be added by default.
204// These code sections include:
205// - raw string literals (containing #include).
206// - #if blocks.
207// - Special #include's among declarations (e.g. functions).
208//
209// If no #include after which a new #include can be inserted, this returns the
210// offset after skipping all comments from the start of the code.
211// Inserting after an #include is not allowed if it comes after code that is not
212// #include (e.g. pre-processing directive that is not #include, declarations).
213unsigned getMaxHeaderInsertionOffset(StringRef FileName, StringRef Code,
214 const IncludeStyle &Style) {
215 return getOffsetAfterTokenSequence(
216 FileName, Code, Style,
217 GetOffsetAfterSequence: [](const SourceManager &SM, Lexer &Lex, Token Tok) {
218 skipComments(Lex, Tok);
219 unsigned MaxOffset = SM.getFileOffset(SpellingLoc: Tok.getLocation());
220 while (checkAndConsumeInclusiveDirective(Lex, Tok))
221 MaxOffset = SM.getFileOffset(SpellingLoc: Tok.getLocation());
222 return MaxOffset;
223 });
224}
225
226// Check whether the first declaration in the code is a C++20 module
227// declaration, and it is not preceded by any preprocessor directives.
228bool isFirstDeclModuleDecl(StringRef FileName, StringRef Code,
229 const IncludeStyle &Style) {
230 return withLexer(
231 FileName, Code, Style, Callback: [](const SourceManager &SM, Lexer &Lex) {
232 // Let the lexer skip any comments and whitespaces for us.
233 Lex.SetKeepWhitespaceMode(false);
234 Lex.SetCommentRetentionState(false);
235
236 Token tok;
237 if (Lex.LexFromRawLexer(Result&: tok))
238 return false;
239
240 // A module declaration is made up of the following token sequence:
241 // export? module <ident> ('.' <ident>)* <partition> <attr> ;
242 //
243 // For convenience, we don't actually lex the whole declaration -- it's
244 // enough to distinguish a module declaration to just ensure an <ident>
245 // is following the "module" keyword.
246
247 // Lex the optional "export" keyword.
248 if (tok.is(K: tok::raw_identifier) && tok.getRawIdentifier() == "export") {
249 if (Lex.LexFromRawLexer(Result&: tok))
250 return false;
251 }
252
253 // Lex the "module" keyword.
254 if (!tok.is(K: tok::raw_identifier) ||
255 tok.getRawIdentifier() != "module" || Lex.LexFromRawLexer(Result&: tok))
256 return false;
257
258 // Make sure an identifier follows the "module" keyword.
259 return tok.is(K: tok::raw_identifier);
260 });
261}
262
263inline StringRef trimInclude(StringRef IncludeName) {
264 return IncludeName.trim(Chars: "\"<>");
265}
266
267const char IncludeRegexPattern[] =
268 "^[\t ]*#[\t ]*(import|include)[^\"<]*([\"<][^\">]*[\">])";
269
270// The filename of Path excluding extension.
271// Used to match implementation with headers, this differs from sys::path::stem:
272// - in names with multiple dots (foo.cu.cc) it terminates at the *first*
273// - an empty stem is never returned: /foo/.bar.x => .bar
274// - we don't bother to handle . and .. specially
275StringRef matchingStem(llvm::StringRef Path) {
276 StringRef Name = llvm::sys::path::filename(path: Path);
277 return Name.substr(Start: 0, N: Name.find(C: '.', From: 1));
278}
279
280} // anonymous namespace
281
282IncludeCategoryManager::IncludeCategoryManager(const IncludeStyle &Style,
283 StringRef FileName)
284 : Style(Style), FileName(FileName) {
285 for (const auto &Category : Style.IncludeCategories) {
286 CategoryRegexs.emplace_back(Args: Category.Regex, Args: Category.RegexIsCaseSensitive
287 ? llvm::Regex::NoFlags
288 : llvm::Regex::IgnoreCase);
289 }
290 IsMainFile = FileName.ends_with(Suffix: ".c") || FileName.ends_with(Suffix: ".cc") ||
291 FileName.ends_with(Suffix: ".cpp") || FileName.ends_with(Suffix: ".c++") ||
292 FileName.ends_with(Suffix: ".cxx") || FileName.ends_with(Suffix: ".m") ||
293 FileName.ends_with(Suffix: ".mm");
294 if (!Style.IncludeIsMainSourceRegex.empty()) {
295 llvm::Regex MainFileRegex(Style.IncludeIsMainSourceRegex);
296 IsMainFile |= MainFileRegex.match(String: FileName);
297 }
298}
299
300int IncludeCategoryManager::getIncludePriority(StringRef IncludeName,
301 bool CheckMainHeader) const {
302 int Ret = INT_MAX;
303 for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i)
304 if (CategoryRegexs[i].match(String: IncludeName)) {
305 Ret = Style.IncludeCategories[i].Priority;
306 break;
307 }
308 if (CheckMainHeader && IsMainFile && Ret > 0 && isMainHeader(IncludeName))
309 Ret = 0;
310 return Ret;
311}
312
313int IncludeCategoryManager::getSortIncludePriority(StringRef IncludeName,
314 bool CheckMainHeader) const {
315 int Ret = INT_MAX;
316 for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i)
317 if (CategoryRegexs[i].match(String: IncludeName)) {
318 Ret = Style.IncludeCategories[i].SortPriority;
319 if (Ret == 0)
320 Ret = Style.IncludeCategories[i].Priority;
321 break;
322 }
323 if (CheckMainHeader && IsMainFile && Ret > 0 && isMainHeader(IncludeName))
324 Ret = 0;
325 return Ret;
326}
327bool IncludeCategoryManager::isMainHeader(StringRef IncludeName) const {
328 switch (Style.MainIncludeChar) {
329 case IncludeStyle::MICD_Quote:
330 if (!IncludeName.starts_with(Prefix: "\""))
331 return false;
332 break;
333 case IncludeStyle::MICD_AngleBracket:
334 if (!IncludeName.starts_with(Prefix: "<"))
335 return false;
336 break;
337 case IncludeStyle::MICD_Any:
338 break;
339 }
340
341 IncludeName =
342 IncludeName.drop_front(N: 1).drop_back(N: 1); // remove the surrounding "" or <>
343 // Not matchingStem: implementation files may have compound extensions but
344 // headers may not.
345 StringRef HeaderStem = llvm::sys::path::stem(path: IncludeName);
346 StringRef FileStem = llvm::sys::path::stem(path: FileName); // foo.cu for foo.cu.cc
347 StringRef MatchingFileStem = matchingStem(Path: FileName); // foo for foo.cu.cc
348 // main-header examples:
349 // 1) foo.h => foo.cc
350 // 2) foo.h => foo.cu.cc
351 // 3) foo.proto.h => foo.proto.cc
352 //
353 // non-main-header examples:
354 // 1) foo.h => bar.cc
355 // 2) foo.proto.h => foo.cc
356 StringRef Matching;
357 if (MatchingFileStem.starts_with_insensitive(Prefix: HeaderStem))
358 Matching = MatchingFileStem; // example 1), 2)
359 else if (FileStem.equals_insensitive(RHS: HeaderStem))
360 Matching = FileStem; // example 3)
361 if (!Matching.empty()) {
362 llvm::Regex MainIncludeRegex(llvm::Regex::escape(String: HeaderStem) +
363 Style.IncludeIsMainRegex,
364 llvm::Regex::IgnoreCase);
365 if (MainIncludeRegex.match(String: Matching))
366 return true;
367 }
368 return false;
369}
370
371const llvm::Regex HeaderIncludes::IncludeRegex(IncludeRegexPattern);
372
373HeaderIncludes::HeaderIncludes(StringRef FileName, StringRef Code,
374 const IncludeStyle &Style)
375 : FileName(FileName), Code(Code), FirstIncludeOffset(-1),
376 MinInsertOffset(getMinHeaderInsertionOffset(FileName, Code, Style)),
377 MaxInsertOffset(MinInsertOffset +
378 getMaxHeaderInsertionOffset(
379 FileName, Code: Code.drop_front(N: MinInsertOffset), Style)),
380 MainIncludeFound(false),
381 ShouldInsertGlobalModuleFragmentDecl(
382 isFirstDeclModuleDecl(FileName, Code, Style)),
383 Categories(Style, FileName) {
384 // Add 0 for main header and INT_MAX for headers that are not in any
385 // category.
386 Priorities = {0, INT_MAX};
387 for (const auto &Category : Style.IncludeCategories)
388 Priorities.insert(x: Category.Priority);
389 SmallVector<StringRef, 32> Lines;
390 Code.drop_front(N: MinInsertOffset).split(A&: Lines, Separator: "\n");
391
392 unsigned Offset = MinInsertOffset;
393 unsigned NextLineOffset;
394 SmallVector<StringRef, 4> Matches;
395 for (auto Line : Lines) {
396 NextLineOffset = std::min(a: Code.size(), b: Offset + Line.size() + 1);
397 if (IncludeRegex.match(String: Line, Matches: &Matches)) {
398 // If this is the last line without trailing newline, we need to make
399 // sure we don't delete across the file boundary.
400 addExistingInclude(
401 IncludeToAdd: Include(Matches[2],
402 tooling::Range(
403 Offset, std::min(a: Line.size() + 1, b: Code.size() - Offset)),
404 Matches[1] == "import" ? tooling::IncludeDirective::Import
405 : tooling::IncludeDirective::Include),
406 NextLineOffset);
407 }
408 Offset = NextLineOffset;
409 }
410
411 // Populate CategoryEndOfssets:
412 // - Ensure that CategoryEndOffset[Highest] is always populated.
413 // - If CategoryEndOffset[Priority] isn't set, use the next higher value
414 // that is set, up to CategoryEndOffset[Highest].
415 auto Highest = Priorities.begin();
416 auto [It, Inserted] = CategoryEndOffsets.try_emplace(k: *Highest);
417 if (Inserted)
418 It->second = FirstIncludeOffset >= 0 ? FirstIncludeOffset : MinInsertOffset;
419 // By this point, CategoryEndOffset[Highest] is always set appropriately:
420 // - to an appropriate location before/after existing #includes, or
421 // - to right after the header guard, or
422 // - to the beginning of the file.
423 for (auto I = ++Priorities.begin(), E = Priorities.end(); I != E; ++I)
424 if (CategoryEndOffsets.find(x: *I) == CategoryEndOffsets.end())
425 CategoryEndOffsets[*I] = CategoryEndOffsets[*std::prev(x: I)];
426}
427
428// \p Offset: the start of the line following this include directive.
429void HeaderIncludes::addExistingInclude(Include IncludeToAdd,
430 unsigned NextLineOffset) {
431 auto &Incs = ExistingIncludes[trimInclude(IncludeName: IncludeToAdd.Name)];
432 Incs.push_back(x: std::move(IncludeToAdd));
433 auto &CurInclude = Incs.back();
434 // The header name with quotes or angle brackets.
435 // Only record the offset of current #include if we can insert after it.
436 if (CurInclude.R.getOffset() <= MaxInsertOffset) {
437 int Priority = Categories.getIncludePriority(
438 IncludeName: CurInclude.Name, /*CheckMainHeader=*/!MainIncludeFound);
439 if (Priority == 0)
440 MainIncludeFound = true;
441 CategoryEndOffsets[Priority] = NextLineOffset;
442 IncludesByPriority[Priority].push_back(Elt: &CurInclude);
443 if (FirstIncludeOffset < 0)
444 FirstIncludeOffset = CurInclude.R.getOffset();
445 }
446}
447
448std::optional<tooling::Replacement>
449HeaderIncludes::insert(llvm::StringRef Header, bool IsAngled,
450 IncludeDirective Directive) const {
451 assert(Header == trimInclude(Header));
452 // If a <header> ("header") already exists in code, "header" (<header>) with
453 // different quotation will still be inserted.
454 // FIXME: figure out if this is the best behavior.
455 auto It = ExistingIncludes.find(Key: Header);
456 if (It != ExistingIncludes.end()) {
457 for (const auto &Inc : It->second) {
458 bool SameQuotation = (IsAngled && StringRef(Inc.Name).starts_with(Prefix: "<")) ||
459 (!IsAngled && StringRef(Inc.Name).starts_with(Prefix: "\""));
460 if (SameQuotation) {
461 // If the directive is the same, or if the directive is an include and
462 // the existing directive is an import, then we don't need to insert
463 // the header.
464 if ((Inc.Directive == Directive) ||
465 (Inc.Directive == IncludeDirective::Import &&
466 Directive == IncludeDirective::Include)) {
467 return std::nullopt;
468 }
469
470 // "import" outranks "include" with the assumption that includes are
471 // designed to handle multiple inclusions while import is not.
472 char Open = IsAngled ? '<' : '"';
473 char Close = IsAngled ? '>' : '"';
474 std::string NewInclude =
475 llvm::formatv(Fmt: "#import {0}{1}{2}\n", Vals&: Open, Vals&: Header, Vals&: Close);
476
477 return tooling::Replacement(FileName, Inc.R.getOffset(),
478 Inc.R.getLength(), NewInclude);
479 }
480 }
481 }
482 std::string Quoted =
483 std::string(llvm::formatv(Fmt: IsAngled ? "<{0}>" : "\"{0}\"", Vals&: Header));
484 StringRef QuotedName = Quoted;
485 int Priority = Categories.getIncludePriority(
486 IncludeName: QuotedName, /*CheckMainHeader=*/!MainIncludeFound);
487 auto CatOffset = CategoryEndOffsets.find(x: Priority);
488 assert(CatOffset != CategoryEndOffsets.end());
489 unsigned InsertOffset = CatOffset->second; // Fall back offset
490 auto Iter = IncludesByPriority.find(x: Priority);
491 if (Iter != IncludesByPriority.end()) {
492 for (const auto *Inc : Iter->second) {
493 if (QuotedName < Inc->Name) {
494 InsertOffset = Inc->R.getOffset();
495 break;
496 }
497 }
498 }
499 assert(InsertOffset <= Code.size());
500 llvm::StringRef DirectiveSpelling =
501 Directive == IncludeDirective::Include ? "include" : "import";
502 std::string NewInclude =
503 llvm::formatv(Fmt: "#{0} {1}\n", Vals&: DirectiveSpelling, Vals&: QuotedName);
504 // When inserting headers at end of the code, also append '\n' to the code
505 // if it does not end with '\n'.
506 // FIXME: when inserting multiple #includes at the end of code, only one
507 // newline should be added.
508 if (InsertOffset == Code.size() && (!Code.empty() && Code.back() != '\n'))
509 NewInclude = "\n" + NewInclude;
510 if (ShouldInsertGlobalModuleFragmentDecl)
511 NewInclude = "module;\n" + NewInclude;
512 return tooling::Replacement(FileName, InsertOffset, 0, NewInclude);
513}
514
515tooling::Replacements HeaderIncludes::remove(llvm::StringRef IncludeName,
516 bool IsAngled) const {
517 assert(IncludeName == trimInclude(IncludeName));
518 tooling::Replacements Result;
519 auto Iter = ExistingIncludes.find(Key: IncludeName);
520 if (Iter == ExistingIncludes.end())
521 return Result;
522 for (const auto &Inc : Iter->second) {
523 if ((IsAngled && StringRef(Inc.Name).starts_with(Prefix: "\"")) ||
524 (!IsAngled && StringRef(Inc.Name).starts_with(Prefix: "<")))
525 continue;
526 llvm::Error Err = Result.add(R: tooling::Replacement(
527 FileName, Inc.R.getOffset(), Inc.R.getLength(), ""));
528 if (Err) {
529 auto ErrMsg = "Unexpected conflicts in #include deletions: " +
530 llvm::toString(E: std::move(Err));
531 llvm_unreachable(ErrMsg.c_str());
532 }
533 }
534 return Result;
535}
536
537} // namespace tooling
538} // namespace clang
539