clang-tools 23.0.0git
IncludeOrderCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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 "IncludeOrderCheck.h"
10#include "clang/Frontend/CompilerInstance.h"
11#include "clang/Lex/PPCallbacks.h"
12#include "clang/Lex/Preprocessor.h"
13#include "llvm/ADT/STLExtras.h"
14
16
17namespace {
18class IncludeOrderPPCallbacks : public PPCallbacks {
19public:
20 explicit IncludeOrderPPCallbacks(ClangTidyCheck &Check,
21 const SourceManager &SM)
22 : Check(Check), SM(SM) {}
23
24 void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
25 StringRef FileName, bool IsAngled,
26 CharSourceRange FilenameRange,
27 OptionalFileEntryRef File, StringRef SearchPath,
28 StringRef RelativePath, const Module *SuggestedModule,
29 bool ModuleImported,
30 SrcMgr::CharacteristicKind FileType) override;
31 void EndOfMainFile() override;
32
33private:
34 struct IncludeDirective {
35 SourceLocation Loc; ///< '#' location in the include directive
36 CharSourceRange Range; ///< SourceRange for the file name
37 std::string Filename; ///< Filename as a string
38 bool IsAngled; ///< true if this was an include with angle brackets
39 bool IsMainModule; ///< true if this was the first include in a file
40 };
41
42 using FileIncludes = std::vector<IncludeDirective>;
43 llvm::DenseMap<FileID, FileIncludes> IncludeDirectives;
44 bool LookForMainModule = true;
45
46 ClangTidyCheck &Check;
47 const SourceManager &SM;
48};
49} // namespace
50
51void IncludeOrderCheck::registerPPCallbacks(const SourceManager &SM,
52 Preprocessor *PP,
53 Preprocessor *ModuleExpanderPP) {
54 PP->addPPCallbacks(::std::make_unique<IncludeOrderPPCallbacks>(*this, SM));
55}
56
57static int getPriority(StringRef Filename, bool IsAngled, bool IsMainModule) {
58 // We leave the main module header at the top.
59 if (IsMainModule)
60 return 0;
61
62 // LLVM and clang headers are in the penultimate position.
63 if (Filename.starts_with("llvm/") || Filename.starts_with("llvm-c/") ||
64 Filename.starts_with("clang/") || Filename.starts_with("clang-c/"))
65 return 2;
66
67 // Put these between system and llvm headers to be consistent with LLVM
68 // clang-format style.
69 if (Filename.starts_with("gtest/") || Filename.starts_with("gmock/"))
70 return 3;
71
72 // System headers are sorted to the end.
73 if (IsAngled)
74 return 4;
75
76 // Other headers are inserted between the main module header and LLVM headers.
77 return 1;
78}
79
80void IncludeOrderPPCallbacks::InclusionDirective(
81 SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,
82 bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File,
83 StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule,
84 bool ModuleImported, SrcMgr::CharacteristicKind FileType) {
85 // We recognize the first include as a special main module header and want
86 // to leave it in the top position.
87 IncludeDirective ID = {HashLoc, FilenameRange, std::string(FileName),
88 IsAngled, false};
89 if (LookForMainModule && !IsAngled) {
90 ID.IsMainModule = true;
91 LookForMainModule = false;
92 }
93
94 // Bucket the include directives by the id of the file they were declared in.
95 IncludeDirectives[SM.getFileID(HashLoc)].push_back(std::move(ID));
96}
97
98void IncludeOrderPPCallbacks::EndOfMainFile() {
99 LookForMainModule = true;
100 if (IncludeDirectives.empty())
101 return;
102
103 // TODO: find duplicated includes.
104
105 // Form blocks of includes. We don't want to sort across blocks. This also
106 // implicitly makes us never reorder over #defines or #if directives.
107 // FIXME: We should be more careful about sorting below comments as we don't
108 // know if the comment refers to the next include or the whole block that
109 // follows.
110 for (auto &Bucket : IncludeDirectives) {
111 auto &FileDirectives = Bucket.second;
112 std::vector<unsigned> Blocks(1, 0);
113 for (unsigned I = 1, E = FileDirectives.size(); I != E; ++I)
114 if (SM.getExpansionLineNumber(FileDirectives[I].Loc) !=
115 SM.getExpansionLineNumber(FileDirectives[I - 1].Loc) + 1)
116 Blocks.push_back(I);
117 Blocks.push_back(FileDirectives.size()); // Sentinel value.
118
119 // Get a vector of indices.
120 std::vector<unsigned> IncludeIndices;
121 for (unsigned I = 0, E = FileDirectives.size(); I != E; ++I)
122 IncludeIndices.push_back(I);
123
124 // Sort the includes. We first sort by priority, then lexicographically.
125 for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI)
126 llvm::sort(IncludeIndices.begin() + Blocks[BI],
127 IncludeIndices.begin() + Blocks[BI + 1],
128 [&FileDirectives](unsigned LHSI, unsigned RHSI) {
129 IncludeDirective &LHS = FileDirectives[LHSI];
130 IncludeDirective &RHS = FileDirectives[RHSI];
131
132 int PriorityLHS = getPriority(LHS.Filename, LHS.IsAngled,
133 LHS.IsMainModule);
134 int PriorityRHS = getPriority(RHS.Filename, RHS.IsAngled,
135 RHS.IsMainModule);
136
137 return std::tie(PriorityLHS, LHS.Filename) <
138 std::tie(PriorityRHS, RHS.Filename);
139 });
140
141 // Emit a warning for each block and fixits for all changes within that
142 // block.
143 for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) {
144 // Find the first include that's not in the right position.
145 unsigned I = 0, E = 0;
146 for (I = Blocks[BI], E = Blocks[BI + 1]; I != E; ++I)
147 if (IncludeIndices[I] != I)
148 break;
149
150 if (I == E)
151 continue;
152
153 // Emit a warning.
154 auto D = Check.diag(FileDirectives[I].Loc,
155 "#includes are not sorted properly");
156
157 // Emit fix-its for all following includes in this block.
158 for (; I != E; ++I) {
159 if (IncludeIndices[I] == I)
160 continue;
161 const IncludeDirective &CopyFrom = FileDirectives[IncludeIndices[I]];
162
163 const SourceLocation FromLoc = CopyFrom.Range.getBegin();
164 const char *FromData = SM.getCharacterData(FromLoc);
165 const unsigned FromLen = std::strcspn(FromData, "\n");
166
167 const StringRef FixedName(FromData, FromLen);
168
169 const SourceLocation ToLoc = FileDirectives[I].Range.getBegin();
170 const char *ToData = SM.getCharacterData(ToLoc);
171 const unsigned ToLen = std::strcspn(ToData, "\n");
172 auto ToRange =
173 CharSourceRange::getCharRange(ToLoc, ToLoc.getLocWithOffset(ToLen));
174
175 D << FixItHint::CreateReplacement(ToRange, FixedName);
176 }
177 }
178 }
179
180 IncludeDirectives.clear();
181}
182
183} // namespace clang::tidy::llvm_check
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
static int getPriority(StringRef Filename, bool IsAngled, bool IsMainModule)