//===--- 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 "llvm/ADT/STLExtras.h" #include 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, Optional 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 FileIncludes; std::map IncludeDirectives; bool LookForMainModule; ClangTidyCheck &Check; const SourceManager &SM; }; } // namespace void IncludeOrderCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { PP->addPPCallbacks(::std::make_unique(*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; // Put these between system and llvm headers to be consistent with LLVM // clang-format style. if (Filename.startswith("gtest/") || Filename.startswith("gmock/")) return 3; // System headers are sorted to the end. if (IsAngled) return 4; // 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, Optional 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 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 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) llvm::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