forked from OSchip/llvm-project
184 lines
6.8 KiB
C++
184 lines
6.8 KiB
C++
//===--- 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
|