IncludeOrderCheck.cpp 6.9 KB

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