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