1 //===--- HeaderIncludes.h - Insert/Delete #includes for C++ code--*- 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 #ifndef LLVM_CLANG_TOOLING_INCLUSIONS_HEADERINCLUDES_H
10 #define LLVM_CLANG_TOOLING_INCLUSIONS_HEADERINCLUDES_H
11 
12 #include "clang/Basic/SourceManager.h"
13 #include "clang/Tooling/Core/Replacement.h"
14 #include "clang/Tooling/Inclusions/IncludeStyle.h"
15 #include "llvm/Support/Path.h"
16 #include "llvm/Support/Regex.h"
17 #include <list>
18 #include <optional>
19 #include <unordered_map>
20 
21 namespace clang {
22 namespace tooling {
23 
24 /// This class manages priorities of C++ #include categories and calculates
25 /// priorities for headers.
26 /// FIXME(ioeric): move this class into implementation file when clang-format's
27 /// include sorting functions are also moved here.
28 class IncludeCategoryManager {
29 public:
30   IncludeCategoryManager(const IncludeStyle &Style, StringRef FileName);
31 
32   /// Returns the priority of the category which \p IncludeName belongs to.
33   /// If \p CheckMainHeader is true and \p IncludeName is a main header, returns
34   /// 0. Otherwise, returns the priority of the matching category or INT_MAX.
35   /// NOTE: this API is not thread-safe!
36   int getIncludePriority(StringRef IncludeName, bool CheckMainHeader) const;
37   int getSortIncludePriority(StringRef IncludeName, bool CheckMainHeader) const;
38 
39 private:
40   bool isMainHeader(StringRef IncludeName) const;
41 
42   const IncludeStyle Style;
43   bool IsMainFile;
44   std::string FileName;
45   SmallVector<llvm::Regex, 4> CategoryRegexs;
46 };
47 
48 enum class IncludeDirective { Include, Import };
49 
50 /// Generates replacements for inserting or deleting #include directives in a
51 /// file.
52 class HeaderIncludes {
53 public:
54   HeaderIncludes(llvm::StringRef FileName, llvm::StringRef Code,
55                  const IncludeStyle &Style);
56 
57   /// Inserts an #include or #import directive of \p Header into the code.
58   /// If \p IsAngled is true, \p Header will be quoted with <> in the directive;
59   /// otherwise, it will be quoted with "".
60   ///
61   /// When searching for points to insert new header, this ignores #include's
62   /// after the #include block(s) in the beginning of a file to avoid inserting
63   /// headers into code sections where new #include's should not be added by
64   /// default. These code sections include:
65   ///   - raw string literals (containing #include).
66   ///   - #if blocks.
67   ///   - Special #include's among declarations (e.g. functions).
68   ///
69   /// Returns a replacement that inserts the new header into a suitable #include
70   /// block of the same category. This respects the order of the existing
71   /// #includes in the block; if the existing #includes are not already sorted,
72   /// this will simply insert the #include in front of the first #include of the
73   /// same category in the code that should be sorted after \p IncludeName. If
74   /// \p IncludeName already exists (with exactly the same spelling), this
75   /// returns std::nullopt.
76   std::optional<tooling::Replacement> insert(llvm::StringRef Header,
77                                              bool IsAngled,
78                                              IncludeDirective Directive) const;
79 
80   /// Removes all existing #includes and #imports of \p Header quoted with <> if
81   /// \p IsAngled is true or "" if \p IsAngled is false.
82   /// This doesn't resolve the header file path; it only deletes #includes and
83   /// #imports with exactly the same spelling.
84   tooling::Replacements remove(llvm::StringRef Header, bool IsAngled) const;
85 
86   // Matches a whole #include directive.
87   static const llvm::Regex IncludeRegex;
88 
89 private:
90   struct Include {
91     Include(StringRef Name, tooling::Range R, IncludeDirective D)
92         : Name(Name), R(R), Directive(D) {}
93 
94     // An include header quoted with either <> or "".
95     std::string Name;
96     // The range of the whole line of include directive including any leading
97     // whitespaces and trailing comment.
98     tooling::Range R;
99     // Either #include or #import.
100     IncludeDirective Directive;
101   };
102 
103   void addExistingInclude(Include IncludeToAdd, unsigned NextLineOffset);
104 
105   std::string FileName;
106   std::string Code;
107 
108   // Map from include name (quotation trimmed) to a list of existing includes
109   // (in case there are more than one) with the name in the current file. <x>
110   // and "x" will be treated as the same header when deleting #includes.
111   // std::list is used for pointers stability (see IncludesByPriority)
112   llvm::StringMap<std::list<Include>> ExistingIncludes;
113 
114   /// Map from priorities of #include categories to all #includes in the same
115   /// category. This is used to find #includes of the same category when
116   /// inserting new #includes. #includes in the same categories are sorted in
117   /// in the order they appear in the source file.
118   /// See comment for "FormatStyle::IncludeCategories" for details about include
119   /// priorities.
120   std::unordered_map<int, llvm::SmallVector<const Include *, 8>>
121       IncludesByPriority;
122 
123   int FirstIncludeOffset;
124   // All new headers should be inserted after this offset (e.g. after header
125   // guards, file comment).
126   unsigned MinInsertOffset;
127   // Max insertion offset in the original code. For example, we want to avoid
128   // inserting new #includes into the actual code section (e.g. after a
129   // declaration).
130   unsigned MaxInsertOffset;
131   // True if we find the main-file header in the Code.
132   bool MainIncludeFound;
133   IncludeCategoryManager Categories;
134   // Record the offset of the end of the last include in each category.
135   std::unordered_map<int, int> CategoryEndOffsets;
136 
137   // All possible priorities.
138   std::set<int> Priorities;
139 };
140 
141 } // namespace tooling
142 } // namespace clang
143 
144 #endif // LLVM_CLANG_TOOLING_INCLUSIONS_HEADERINCLUDES_H
145