1 //===--- IncludeOrderCheck.cpp - clang-tidy -------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
17 namespace clang::tidy::llvm_check
{
20 class IncludeOrderPPCallbacks
: public PPCallbacks
{
22 explicit IncludeOrderPPCallbacks(ClangTidyCheck
&Check
,
23 const SourceManager
&SM
)
24 : Check(Check
), SM(SM
) {}
26 void InclusionDirective(SourceLocation HashLoc
, const Token
&IncludeTok
,
27 StringRef FileName
, bool IsAngled
,
28 CharSourceRange FilenameRange
,
29 OptionalFileEntryRef File
, StringRef SearchPath
,
30 StringRef RelativePath
, const Module
*Imported
,
31 SrcMgr::CharacteristicKind FileType
) override
;
32 void EndOfMainFile() override
;
35 struct IncludeDirective
{
36 SourceLocation Loc
; ///< '#' location in the include directive
37 CharSourceRange Range
; ///< SourceRange for the file name
38 std::string Filename
; ///< Filename as a string
39 bool IsAngled
; ///< true if this was an include with angle brackets
40 bool IsMainModule
; ///< true if this was the first include in a file
43 using FileIncludes
= std::vector
<IncludeDirective
>;
44 std::map
<clang::FileID
, FileIncludes
> IncludeDirectives
;
45 bool LookForMainModule
= true;
47 ClangTidyCheck
&Check
;
48 const SourceManager
&SM
;
52 void IncludeOrderCheck::registerPPCallbacks(const SourceManager
&SM
,
54 Preprocessor
*ModuleExpanderPP
) {
55 PP
->addPPCallbacks(::std::make_unique
<IncludeOrderPPCallbacks
>(*this, SM
));
58 static int getPriority(StringRef Filename
, bool IsAngled
, bool IsMainModule
) {
59 // We leave the main module header at the top.
63 // LLVM and clang headers are in the penultimate position.
64 if (Filename
.startswith("llvm/") || Filename
.startswith("llvm-c/") ||
65 Filename
.startswith("clang/") || Filename
.startswith("clang-c/"))
68 // Put these between system and llvm headers to be consistent with LLVM
69 // clang-format style.
70 if (Filename
.startswith("gtest/") || Filename
.startswith("gmock/"))
73 // System headers are sorted to the end.
77 // Other headers are inserted between the main module header and LLVM headers.
81 void IncludeOrderPPCallbacks::InclusionDirective(
82 SourceLocation HashLoc
, const Token
&IncludeTok
, StringRef FileName
,
83 bool IsAngled
, CharSourceRange FilenameRange
, OptionalFileEntryRef File
,
84 StringRef SearchPath
, StringRef RelativePath
, const Module
*Imported
,
85 SrcMgr::CharacteristicKind FileType
) {
86 // We recognize the first include as a special main module header and want
87 // to leave it in the top position.
88 IncludeDirective ID
= {HashLoc
, FilenameRange
, std::string(FileName
),
90 if (LookForMainModule
&& !IsAngled
) {
91 ID
.IsMainModule
= true;
92 LookForMainModule
= false;
95 // Bucket the include directives by the id of the file they were declared in.
96 IncludeDirectives
[SM
.getFileID(HashLoc
)].push_back(std::move(ID
));
99 void IncludeOrderPPCallbacks::EndOfMainFile() {
100 LookForMainModule
= true;
101 if (IncludeDirectives
.empty())
104 // TODO: find duplicated includes.
106 // Form blocks of includes. We don't want to sort across blocks. This also
107 // implicitly makes us never reorder over #defines or #if directives.
108 // FIXME: We should be more careful about sorting below comments as we don't
109 // know if the comment refers to the next include or the whole block that
111 for (auto &Bucket
: IncludeDirectives
) {
112 auto &FileDirectives
= Bucket
.second
;
113 std::vector
<unsigned> Blocks(1, 0);
114 for (unsigned I
= 1, E
= FileDirectives
.size(); I
!= E
; ++I
)
115 if (SM
.getExpansionLineNumber(FileDirectives
[I
].Loc
) !=
116 SM
.getExpansionLineNumber(FileDirectives
[I
- 1].Loc
) + 1)
118 Blocks
.push_back(FileDirectives
.size()); // Sentinel value.
120 // Get a vector of indices.
121 std::vector
<unsigned> IncludeIndices
;
122 for (unsigned I
= 0, E
= FileDirectives
.size(); I
!= E
; ++I
)
123 IncludeIndices
.push_back(I
);
125 // Sort the includes. We first sort by priority, then lexicographically.
126 for (unsigned BI
= 0, BE
= Blocks
.size() - 1; BI
!= BE
; ++BI
)
127 llvm::sort(IncludeIndices
.begin() + Blocks
[BI
],
128 IncludeIndices
.begin() + Blocks
[BI
+ 1],
129 [&FileDirectives
](unsigned LHSI
, unsigned RHSI
) {
130 IncludeDirective
&LHS
= FileDirectives
[LHSI
];
131 IncludeDirective
&RHS
= FileDirectives
[RHSI
];
133 int PriorityLHS
= getPriority(LHS
.Filename
, LHS
.IsAngled
,
135 int PriorityRHS
= getPriority(RHS
.Filename
, RHS
.IsAngled
,
138 return std::tie(PriorityLHS
, LHS
.Filename
) <
139 std::tie(PriorityRHS
, RHS
.Filename
);
142 // Emit a warning for each block and fixits for all changes within that
144 for (unsigned BI
= 0, BE
= Blocks
.size() - 1; BI
!= BE
; ++BI
) {
145 // Find the first include that's not in the right position.
146 unsigned I
= 0, E
= 0;
147 for (I
= Blocks
[BI
], E
= Blocks
[BI
+ 1]; I
!= E
; ++I
)
148 if (IncludeIndices
[I
] != I
)
155 auto D
= Check
.diag(FileDirectives
[I
].Loc
,
156 "#includes are not sorted properly");
158 // Emit fix-its for all following includes in this block.
159 for (; I
!= E
; ++I
) {
160 if (IncludeIndices
[I
] == I
)
162 const IncludeDirective
&CopyFrom
= FileDirectives
[IncludeIndices
[I
]];
164 SourceLocation FromLoc
= CopyFrom
.Range
.getBegin();
165 const char *FromData
= SM
.getCharacterData(FromLoc
);
166 unsigned FromLen
= std::strcspn(FromData
, "\n");
168 StringRef
FixedName(FromData
, FromLen
);
170 SourceLocation ToLoc
= FileDirectives
[I
].Range
.getBegin();
171 const char *ToData
= SM
.getCharacterData(ToLoc
);
172 unsigned ToLen
= std::strcspn(ToData
, "\n");
174 CharSourceRange::getCharRange(ToLoc
, ToLoc
.getLocWithOffset(ToLen
));
176 D
<< FixItHint::CreateReplacement(ToRange
, FixedName
);
181 IncludeDirectives
.clear();
184 } // namespace clang::tidy::llvm_check