forked from OSchip/llvm-project
238 lines
9.4 KiB
C++
238 lines
9.4 KiB
C++
//===---------- IncludeSorter.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 "IncludeSorter.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include <algorithm>
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace utils {
|
|
|
|
namespace {
|
|
|
|
StringRef removeFirstSuffix(StringRef Str, ArrayRef<const char *> Suffixes) {
|
|
for (StringRef Suffix : Suffixes) {
|
|
if (Str.endswith(Suffix)) {
|
|
return Str.substr(0, Str.size() - Suffix.size());
|
|
}
|
|
}
|
|
return Str;
|
|
}
|
|
|
|
StringRef makeCanonicalName(StringRef Str, IncludeSorter::IncludeStyle Style) {
|
|
// The list of suffixes to remove from source file names to get the
|
|
// "canonical" file names.
|
|
// E.g. tools/sort_includes.cc and tools/sort_includes_test.cc
|
|
// would both canonicalize to tools/sort_includes and tools/sort_includes.h
|
|
// (once canonicalized) will match as being the main include file associated
|
|
// with the source files.
|
|
if (Style == IncludeSorter::IS_LLVM) {
|
|
return removeFirstSuffix(
|
|
removeFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), {"Test"});
|
|
}
|
|
if (Style == IncludeSorter::IS_Google_ObjC) {
|
|
StringRef Canonical =
|
|
removeFirstSuffix(removeFirstSuffix(Str, {".cc", ".cpp", ".c", ".h",
|
|
".hpp", ".mm", ".m"}),
|
|
{"_unittest", "_regtest", "_test", "Test"});
|
|
|
|
// Objective-C categories have a `+suffix` format, but should be grouped
|
|
// with the file they are a category of.
|
|
size_t StartIndex = Canonical.find_last_of('/');
|
|
if (StartIndex == StringRef::npos) {
|
|
StartIndex = 0;
|
|
}
|
|
return Canonical.substr(
|
|
0, Canonical.find_first_of('+', StartIndex));
|
|
}
|
|
return removeFirstSuffix(
|
|
removeFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}),
|
|
{"_unittest", "_regtest", "_test"});
|
|
}
|
|
|
|
// Scan to the end of the line and return the offset of the next line.
|
|
size_t findNextLine(const char *Text) {
|
|
size_t EOLIndex = std::strcspn(Text, "\n");
|
|
return Text[EOLIndex] == '\0' ? EOLIndex : EOLIndex + 1;
|
|
}
|
|
|
|
IncludeSorter::IncludeKinds
|
|
determineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile,
|
|
bool IsAngled, IncludeSorter::IncludeStyle Style) {
|
|
// Compute the two "canonical" forms of the include's filename sans extension.
|
|
// The first form is the include's filename without ".h" or "-inl.h" at the
|
|
// end. The second form is the first form with "/public/" in the file path
|
|
// replaced by "/internal/".
|
|
if (IsAngled) {
|
|
// If the system include (<foo>) ends with ".h", then it is a normal C-style
|
|
// include. Otherwise assume it is a C++-style extensionless include.
|
|
return IncludeFile.endswith(".h") ? IncludeSorter::IK_CSystemInclude
|
|
: IncludeSorter::IK_CXXSystemInclude;
|
|
}
|
|
StringRef CanonicalInclude = makeCanonicalName(IncludeFile, Style);
|
|
if (CanonicalFile.endswith(CanonicalInclude)
|
|
|| CanonicalInclude.endswith(CanonicalFile)) {
|
|
return IncludeSorter::IK_MainTUInclude;
|
|
}
|
|
if ((Style == IncludeSorter::IS_Google) ||
|
|
(Style == IncludeSorter::IS_Google_ObjC)) {
|
|
std::pair<StringRef, StringRef> Parts = CanonicalInclude.split("/public/");
|
|
std::string AltCanonicalInclude =
|
|
Parts.first.str() + "/internal/" + Parts.second.str();
|
|
std::string ProtoCanonicalInclude =
|
|
Parts.first.str() + "/proto/" + Parts.second.str();
|
|
|
|
// Determine the kind of this inclusion.
|
|
if (CanonicalFile.equals(AltCanonicalInclude) ||
|
|
CanonicalFile.equals(ProtoCanonicalInclude)) {
|
|
return IncludeSorter::IK_MainTUInclude;
|
|
}
|
|
}
|
|
if (Style == IncludeSorter::IS_Google_ObjC) {
|
|
if (IncludeFile.endswith(".generated.h") ||
|
|
IncludeFile.endswith(".proto.h") || IncludeFile.endswith(".pbobjc.h")) {
|
|
return IncludeSorter::IK_GeneratedInclude;
|
|
}
|
|
}
|
|
return IncludeSorter::IK_NonSystemInclude;
|
|
}
|
|
|
|
int compareHeaders(StringRef LHS, StringRef RHS,
|
|
IncludeSorter::IncludeStyle Style) {
|
|
if (Style == IncludeSorter::IncludeStyle::IS_Google_ObjC) {
|
|
const std::pair<const char *, const char *> &Mismatch =
|
|
std::mismatch(LHS.begin(), LHS.end(), RHS.begin());
|
|
if ((Mismatch.first != LHS.end()) && (Mismatch.second != RHS.end())) {
|
|
if ((*Mismatch.first == '.') && (*Mismatch.second == '+')) {
|
|
return -1;
|
|
}
|
|
if ((*Mismatch.first == '+') && (*Mismatch.second == '.')) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return LHS.compare(RHS);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
IncludeSorter::IncludeSorter(const SourceManager *SourceMgr,
|
|
const FileID FileID, StringRef FileName,
|
|
IncludeStyle Style)
|
|
: SourceMgr(SourceMgr), Style(Style), CurrentFileID(FileID),
|
|
CanonicalFile(makeCanonicalName(FileName, Style)) {}
|
|
|
|
void IncludeSorter::AddInclude(StringRef FileName, bool IsAngled,
|
|
SourceLocation HashLocation,
|
|
SourceLocation EndLocation) {
|
|
int Offset = findNextLine(SourceMgr->getCharacterData(EndLocation));
|
|
|
|
// Record the relevant location information for this inclusion directive.
|
|
IncludeLocations[FileName].push_back(
|
|
SourceRange(HashLocation, EndLocation.getLocWithOffset(Offset)));
|
|
SourceLocations.push_back(IncludeLocations[FileName].back());
|
|
|
|
// Stop if this inclusion is a duplicate.
|
|
if (IncludeLocations[FileName].size() > 1)
|
|
return;
|
|
|
|
// Add the included file's name to the appropriate bucket.
|
|
IncludeKinds Kind =
|
|
determineIncludeKind(CanonicalFile, FileName, IsAngled, Style);
|
|
if (Kind != IK_InvalidInclude)
|
|
IncludeBucket[Kind].push_back(FileName.str());
|
|
}
|
|
|
|
Optional<FixItHint> IncludeSorter::CreateIncludeInsertion(StringRef FileName,
|
|
bool IsAngled) {
|
|
std::string IncludeStmt;
|
|
if (Style == IncludeStyle::IS_Google_ObjC) {
|
|
IncludeStmt = IsAngled
|
|
? llvm::Twine("#import <" + FileName + ">\n").str()
|
|
: llvm::Twine("#import \"" + FileName + "\"\n").str();
|
|
} else {
|
|
IncludeStmt = IsAngled
|
|
? llvm::Twine("#include <" + FileName + ">\n").str()
|
|
: llvm::Twine("#include \"" + FileName + "\"\n").str();
|
|
}
|
|
if (SourceLocations.empty()) {
|
|
// If there are no includes in this file, add it in the first line.
|
|
// FIXME: insert after the file comment or the header guard, if present.
|
|
IncludeStmt.append("\n");
|
|
return FixItHint::CreateInsertion(
|
|
SourceMgr->getLocForStartOfFile(CurrentFileID), IncludeStmt);
|
|
}
|
|
|
|
auto IncludeKind =
|
|
determineIncludeKind(CanonicalFile, FileName, IsAngled, Style);
|
|
|
|
if (!IncludeBucket[IncludeKind].empty()) {
|
|
for (const std::string &IncludeEntry : IncludeBucket[IncludeKind]) {
|
|
if (compareHeaders(FileName, IncludeEntry, Style) < 0) {
|
|
const auto &Location = IncludeLocations[IncludeEntry][0];
|
|
return FixItHint::CreateInsertion(Location.getBegin(), IncludeStmt);
|
|
}
|
|
if (FileName == IncludeEntry) {
|
|
return llvm::None;
|
|
}
|
|
}
|
|
// FileName comes after all include entries in bucket, insert it after
|
|
// last.
|
|
const std::string &LastInclude = IncludeBucket[IncludeKind].back();
|
|
SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back();
|
|
return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(),
|
|
IncludeStmt);
|
|
}
|
|
// Find the non-empty include bucket to be sorted directly above
|
|
// 'IncludeKind'. If such a bucket exists, we'll want to sort the include
|
|
// after that bucket. If no such bucket exists, find the first non-empty
|
|
// include bucket in the file. In that case, we'll want to sort the include
|
|
// before that bucket.
|
|
IncludeKinds NonEmptyKind = IK_InvalidInclude;
|
|
for (int I = IK_InvalidInclude - 1; I >= 0; --I) {
|
|
if (!IncludeBucket[I].empty()) {
|
|
NonEmptyKind = static_cast<IncludeKinds>(I);
|
|
if (NonEmptyKind < IncludeKind)
|
|
break;
|
|
}
|
|
}
|
|
if (NonEmptyKind == IK_InvalidInclude) {
|
|
return llvm::None;
|
|
}
|
|
|
|
if (NonEmptyKind < IncludeKind) {
|
|
// Create a block after.
|
|
const std::string &LastInclude = IncludeBucket[NonEmptyKind].back();
|
|
SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back();
|
|
IncludeStmt = '\n' + IncludeStmt;
|
|
return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(),
|
|
IncludeStmt);
|
|
}
|
|
// Create a block before.
|
|
const std::string &FirstInclude = IncludeBucket[NonEmptyKind][0];
|
|
SourceRange FirstIncludeLocation = IncludeLocations[FirstInclude].back();
|
|
IncludeStmt.append("\n");
|
|
return FixItHint::CreateInsertion(FirstIncludeLocation.getBegin(),
|
|
IncludeStmt);
|
|
}
|
|
|
|
} // namespace utils
|
|
|
|
llvm::ArrayRef<std::pair<utils::IncludeSorter::IncludeStyle, StringRef>>
|
|
OptionEnumMapping<utils::IncludeSorter::IncludeStyle>::getEnumMapping() {
|
|
static constexpr std::pair<utils::IncludeSorter::IncludeStyle, StringRef>
|
|
Mapping[] = {{utils::IncludeSorter::IS_LLVM, "llvm"},
|
|
{utils::IncludeSorter::IS_Google, "google"},
|
|
{utils::IncludeSorter::IS_Google_ObjC, "google-objc"}};
|
|
return makeArrayRef(Mapping);
|
|
}
|
|
} // namespace tidy
|
|
} // namespace clang
|