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
*SuggestedModule
,
32 SrcMgr::CharacteristicKind FileType
) override
;
33 void EndOfMainFile() override
;
36 struct IncludeDirective
{
37 SourceLocation Loc
; ///< '#' location in the include directive
38 CharSourceRange Range
; ///< SourceRange for the file name
39 std::string Filename
; ///< Filename as a string
40 bool IsAngled
; ///< true if this was an include with angle brackets
41 bool IsMainModule
; ///< true if this was the first include in a file
44 using FileIncludes
= std::vector
<IncludeDirective
>;
45 std::map
<clang::FileID
, FileIncludes
> IncludeDirectives
;
46 bool LookForMainModule
= true;
48 ClangTidyCheck
&Check
;
49 const SourceManager
&SM
;
53 void IncludeOrderCheck::registerPPCallbacks(const SourceManager
&SM
,
55 Preprocessor
*ModuleExpanderPP
) {
56 PP
->addPPCallbacks(::std::make_unique
<IncludeOrderPPCallbacks
>(*this, SM
));
59 static int getPriority(StringRef Filename
, bool IsAngled
, bool IsMainModule
) {
60 // We leave the main module header at the top.
64 // LLVM and clang headers are in the penultimate position.
65 if (Filename
.starts_with("llvm/") || Filename
.starts_with("llvm-c/") ||
66 Filename
.starts_with("clang/") || Filename
.starts_with("clang-c/"))
69 // Put these between system and llvm headers to be consistent with LLVM
70 // clang-format style.
71 if (Filename
.starts_with("gtest/") || Filename
.starts_with("gmock/"))
74 // System headers are sorted to the end.
78 // Other headers are inserted between the main module header and LLVM headers.
82 void IncludeOrderPPCallbacks::InclusionDirective(
83 SourceLocation HashLoc
, const Token
&IncludeTok
, StringRef FileName
,
84 bool IsAngled
, CharSourceRange FilenameRange
, OptionalFileEntryRef File
,
85 StringRef SearchPath
, StringRef RelativePath
, const Module
*SuggestedModule
,
86 bool ModuleImported
, SrcMgr::CharacteristicKind FileType
) {
87 // We recognize the first include as a special main module header and want
88 // to leave it in the top position.
89 IncludeDirective ID
= {HashLoc
, FilenameRange
, std::string(FileName
),
91 if (LookForMainModule
&& !IsAngled
) {
92 ID
.IsMainModule
= true;
93 LookForMainModule
= false;
96 // Bucket the include directives by the id of the file they were declared in.
97 IncludeDirectives
[SM
.getFileID(HashLoc
)].push_back(std::move(ID
));
100 void IncludeOrderPPCallbacks::EndOfMainFile() {
101 LookForMainModule
= true;
102 if (IncludeDirectives
.empty())
105 // TODO: find duplicated includes.
107 // Form blocks of includes. We don't want to sort across blocks. This also
108 // implicitly makes us never reorder over #defines or #if directives.
109 // FIXME: We should be more careful about sorting below comments as we don't
110 // know if the comment refers to the next include or the whole block that
112 for (auto &Bucket
: IncludeDirectives
) {
113 auto &FileDirectives
= Bucket
.second
;
114 std::vector
<unsigned> Blocks(1, 0);
115 for (unsigned I
= 1, E
= FileDirectives
.size(); I
!= E
; ++I
)
116 if (SM
.getExpansionLineNumber(FileDirectives
[I
].Loc
) !=
117 SM
.getExpansionLineNumber(FileDirectives
[I
- 1].Loc
) + 1)
119 Blocks
.push_back(FileDirectives
.size()); // Sentinel value.
121 // Get a vector of indices.
122 std::vector
<unsigned> IncludeIndices
;
123 for (unsigned I
= 0, E
= FileDirectives
.size(); I
!= E
; ++I
)
124 IncludeIndices
.push_back(I
);
126 // Sort the includes. We first sort by priority, then lexicographically.
127 for (unsigned BI
= 0, BE
= Blocks
.size() - 1; BI
!= BE
; ++BI
)
128 llvm::sort(IncludeIndices
.begin() + Blocks
[BI
],
129 IncludeIndices
.begin() + Blocks
[BI
+ 1],
130 [&FileDirectives
](unsigned LHSI
, unsigned RHSI
) {
131 IncludeDirective
&LHS
= FileDirectives
[LHSI
];
132 IncludeDirective
&RHS
= FileDirectives
[RHSI
];
134 int PriorityLHS
= getPriority(LHS
.Filename
, LHS
.IsAngled
,
136 int PriorityRHS
= getPriority(RHS
.Filename
, RHS
.IsAngled
,
139 return std::tie(PriorityLHS
, LHS
.Filename
) <
140 std::tie(PriorityRHS
, RHS
.Filename
);
143 // Emit a warning for each block and fixits for all changes within that
145 for (unsigned BI
= 0, BE
= Blocks
.size() - 1; BI
!= BE
; ++BI
) {
146 // Find the first include that's not in the right position.
147 unsigned I
= 0, E
= 0;
148 for (I
= Blocks
[BI
], E
= Blocks
[BI
+ 1]; I
!= E
; ++I
)
149 if (IncludeIndices
[I
] != I
)
156 auto D
= Check
.diag(FileDirectives
[I
].Loc
,
157 "#includes are not sorted properly");
159 // Emit fix-its for all following includes in this block.
160 for (; I
!= E
; ++I
) {
161 if (IncludeIndices
[I
] == I
)
163 const IncludeDirective
&CopyFrom
= FileDirectives
[IncludeIndices
[I
]];
165 SourceLocation FromLoc
= CopyFrom
.Range
.getBegin();
166 const char *FromData
= SM
.getCharacterData(FromLoc
);
167 unsigned FromLen
= std::strcspn(FromData
, "\n");
169 StringRef
FixedName(FromData
, FromLen
);
171 SourceLocation ToLoc
= FileDirectives
[I
].Range
.getBegin();
172 const char *ToData
= SM
.getCharacterData(ToLoc
);
173 unsigned ToLen
= std::strcspn(ToData
, "\n");
175 CharSourceRange::getCharRange(ToLoc
, ToLoc
.getLocWithOffset(ToLen
));
177 D
<< FixItHint::CreateReplacement(ToRange
, FixedName
);
182 IncludeDirectives
.clear();
185 } // namespace clang::tidy::llvm_check