| //===--- IncludeOrderCheck.cpp - clang-tidy -------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "IncludeOrderCheck.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Lex/PPCallbacks.h" |
| #include "clang/Lex/Preprocessor.h" |
| |
| #include <map> |
| |
| namespace clang { |
| namespace tidy { |
| namespace llvm_check { |
| |
| namespace { |
| class IncludeOrderPPCallbacks : public PPCallbacks { |
| public: |
| explicit IncludeOrderPPCallbacks(ClangTidyCheck &Check, |
| const SourceManager &SM) |
| : LookForMainModule(true), Check(Check), SM(SM) {} |
| |
| void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, |
| StringRef FileName, bool IsAngled, |
| CharSourceRange FilenameRange, const FileEntry *File, |
| StringRef SearchPath, StringRef RelativePath, |
| const Module *Imported, |
| SrcMgr::CharacteristicKind FileType) override; |
| void EndOfMainFile() override; |
| |
| private: |
| struct IncludeDirective { |
| SourceLocation Loc; ///< '#' location in the include directive |
| CharSourceRange Range; ///< SourceRange for the file name |
| std::string Filename; ///< Filename as a string |
| bool IsAngled; ///< true if this was an include with angle brackets |
| bool IsMainModule; ///< true if this was the first include in a file |
| }; |
| |
| typedef std::vector<IncludeDirective> FileIncludes; |
| std::map<clang::FileID, FileIncludes> IncludeDirectives; |
| bool LookForMainModule; |
| |
| ClangTidyCheck &Check; |
| const SourceManager &SM; |
| }; |
| } // namespace |
| |
| void IncludeOrderCheck::registerPPCallbacks(const SourceManager &SM, |
| Preprocessor *PP, |
| Preprocessor *ModuleExpanderPP) { |
| PP->addPPCallbacks(::std::make_unique<IncludeOrderPPCallbacks>(*this, SM)); |
| } |
| |
| static int getPriority(StringRef Filename, bool IsAngled, bool IsMainModule) { |
| // We leave the main module header at the top. |
| if (IsMainModule) |
| return 0; |
| |
| // LLVM and clang headers are in the penultimate position. |
| if (Filename.startswith("llvm/") || Filename.startswith("llvm-c/") || |
| Filename.startswith("clang/") || Filename.startswith("clang-c/")) |
| return 2; |
| |
| // System headers are sorted to the end. |
| if (IsAngled || Filename.startswith("gtest/") || |
| Filename.startswith("gmock/")) |
| return 3; |
| |
| // Other headers are inserted between the main module header and LLVM headers. |
| return 1; |
| } |
| |
| void IncludeOrderPPCallbacks::InclusionDirective( |
| SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, |
| bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, |
| StringRef SearchPath, StringRef RelativePath, const Module *Imported, |
| SrcMgr::CharacteristicKind FileType) { |
| // We recognize the first include as a special main module header and want |
| // to leave it in the top position. |
| IncludeDirective ID = {HashLoc, FilenameRange, std::string(FileName), |
| IsAngled, false}; |
| if (LookForMainModule && !IsAngled) { |
| ID.IsMainModule = true; |
| LookForMainModule = false; |
| } |
| |
| // Bucket the include directives by the id of the file they were declared in. |
| IncludeDirectives[SM.getFileID(HashLoc)].push_back(std::move(ID)); |
| } |
| |
| void IncludeOrderPPCallbacks::EndOfMainFile() { |
| LookForMainModule = true; |
| if (IncludeDirectives.empty()) |
| return; |
| |
| // TODO: find duplicated includes. |
| |
| // Form blocks of includes. We don't want to sort across blocks. This also |
| // implicitly makes us never reorder over #defines or #if directives. |
| // FIXME: We should be more careful about sorting below comments as we don't |
| // know if the comment refers to the next include or the whole block that |
| // follows. |
| for (auto &Bucket : IncludeDirectives) { |
| auto &FileDirectives = Bucket.second; |
| std::vector<unsigned> Blocks(1, 0); |
| for (unsigned I = 1, E = FileDirectives.size(); I != E; ++I) |
| if (SM.getExpansionLineNumber(FileDirectives[I].Loc) != |
| SM.getExpansionLineNumber(FileDirectives[I - 1].Loc) + 1) |
| Blocks.push_back(I); |
| Blocks.push_back(FileDirectives.size()); // Sentinel value. |
| |
| // Get a vector of indices. |
| std::vector<unsigned> IncludeIndices; |
| for (unsigned I = 0, E = FileDirectives.size(); I != E; ++I) |
| IncludeIndices.push_back(I); |
| |
| // Sort the includes. We first sort by priority, then lexicographically. |
| for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) |
| std::sort(IncludeIndices.begin() + Blocks[BI], |
| IncludeIndices.begin() + Blocks[BI + 1], |
| [&FileDirectives](unsigned LHSI, unsigned RHSI) { |
| IncludeDirective &LHS = FileDirectives[LHSI]; |
| IncludeDirective &RHS = FileDirectives[RHSI]; |
| |
| int PriorityLHS = |
| getPriority(LHS.Filename, LHS.IsAngled, LHS.IsMainModule); |
| int PriorityRHS = |
| getPriority(RHS.Filename, RHS.IsAngled, RHS.IsMainModule); |
| |
| return std::tie(PriorityLHS, LHS.Filename) < |
| std::tie(PriorityRHS, RHS.Filename); |
| }); |
| |
| // Emit a warning for each block and fixits for all changes within that |
| // block. |
| for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) { |
| // Find the first include that's not in the right position. |
| unsigned I, E; |
| for (I = Blocks[BI], E = Blocks[BI + 1]; I != E; ++I) |
| if (IncludeIndices[I] != I) |
| break; |
| |
| if (I == E) |
| continue; |
| |
| // Emit a warning. |
| auto D = Check.diag(FileDirectives[I].Loc, |
| "#includes are not sorted properly"); |
| |
| // Emit fix-its for all following includes in this block. |
| for (; I != E; ++I) { |
| if (IncludeIndices[I] == I) |
| continue; |
| const IncludeDirective &CopyFrom = FileDirectives[IncludeIndices[I]]; |
| |
| SourceLocation FromLoc = CopyFrom.Range.getBegin(); |
| const char *FromData = SM.getCharacterData(FromLoc); |
| unsigned FromLen = std::strcspn(FromData, "\n"); |
| |
| StringRef FixedName(FromData, FromLen); |
| |
| SourceLocation ToLoc = FileDirectives[I].Range.getBegin(); |
| const char *ToData = SM.getCharacterData(ToLoc); |
| unsigned ToLen = std::strcspn(ToData, "\n"); |
| auto ToRange = |
| CharSourceRange::getCharRange(ToLoc, ToLoc.getLocWithOffset(ToLen)); |
| |
| D << FixItHint::CreateReplacement(ToRange, FixedName); |
| } |
| } |
| } |
| |
| IncludeDirectives.clear(); |
| } |
| |
| } // namespace llvm_check |
| } // namespace tidy |
| } // namespace clang |