//===---------- IncludeSorter.cpp - clang-tidy ----------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "IncludeSorter.h" #include "clang/Lex/Lexer.h" namespace clang { namespace tidy { namespace utils { namespace { StringRef RemoveFirstSuffix(StringRef Str, ArrayRef 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"}); } 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 () 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) { std::pair 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; } } return IncludeSorter::IK_NonSystemInclude; } } // namespace IncludeSorter::IncludeSorter(const SourceManager *SourceMgr, const LangOptions *LangOpts, const FileID FileID, StringRef FileName, IncludeStyle Style) : SourceMgr(SourceMgr), LangOpts(LangOpts), 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 IncludeSorter::CreateIncludeInsertion(StringRef FileName, bool IsAngled) { std::string 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 (FileName < IncludeEntry) { const auto &Location = IncludeLocations[IncludeEntry][0]; return FixItHint::CreateInsertion(Location.getBegin(), IncludeStmt); } else 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(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); } std::vector IncludeSorter::GetEdits() { if (SourceLocations.empty()) return {}; typedef std::map> FileLineToSourceEditMap; FileLineToSourceEditMap Edits; auto SourceLocationIterator = SourceLocations.begin(); auto SourceLocationIteratorEnd = SourceLocations.end(); // Compute the Edits that need to be done to each line to add, replace, or // delete inclusions. for (int IncludeKind = 0; IncludeKind < IK_InvalidInclude; ++IncludeKind) { std::sort(IncludeBucket[IncludeKind].begin(), IncludeBucket[IncludeKind].end()); for (const auto &IncludeEntry : IncludeBucket[IncludeKind]) { auto &Location = IncludeLocations[IncludeEntry]; SourceRangeVector::iterator LocationIterator = Location.begin(); SourceRangeVector::iterator LocationIteratorEnd = Location.end(); SourceRange FirstLocation = *LocationIterator; // If the first occurrence of a particular include is on the current // source line we are examining, leave it alone. if (FirstLocation == *SourceLocationIterator) ++LocationIterator; // Add the deletion Edits for any (remaining) instances of this inclusion, // and remove their Locations from the source Locations to be processed. for (; LocationIterator != LocationIteratorEnd; ++LocationIterator) { int LineNumber = SourceMgr->getSpellingLineNumber(LocationIterator->getBegin()); Edits[LineNumber] = std::make_pair(*LocationIterator, ""); SourceLocationIteratorEnd = std::remove(SourceLocationIterator, SourceLocationIteratorEnd, *LocationIterator); } if (FirstLocation == *SourceLocationIterator) { // Do nothing except move to the next source Location (Location of an // inclusion in the original, unchanged source file). ++SourceLocationIterator; continue; } // Add (or append to) the replacement text for this line in source file. int LineNumber = SourceMgr->getSpellingLineNumber(SourceLocationIterator->getBegin()); if (Edits.find(LineNumber) == Edits.end()) { Edits[LineNumber].first = SourceRange(SourceLocationIterator->getBegin()); } StringRef SourceText = Lexer::getSourceText( CharSourceRange::getCharRange(FirstLocation), *SourceMgr, *LangOpts); Edits[LineNumber].second.append(SourceText.data(), SourceText.size()); } // Clear the bucket. IncludeBucket[IncludeKind].clear(); } // Go through the single-line Edits and combine them into blocks of Edits. int CurrentEndLine = 0; SourceRange CurrentRange; std::string CurrentText; std::vector Fixes; for (const auto &LineEdit : Edits) { // If the current edit is on the next line after the previous edit, add it // to the current block edit. if (LineEdit.first == CurrentEndLine + 1 && CurrentRange.getBegin() != CurrentRange.getEnd()) { SourceRange EditRange = LineEdit.second.first; if (EditRange.getBegin() != EditRange.getEnd()) { ++CurrentEndLine; CurrentRange.setEnd(EditRange.getEnd()); } CurrentText += LineEdit.second.second; // Otherwise report the current block edit and start a new block. } else { if (CurrentEndLine) { Fixes.push_back(FixItHint::CreateReplacement( CharSourceRange::getCharRange(CurrentRange), CurrentText)); } CurrentEndLine = LineEdit.first; CurrentRange = LineEdit.second.first; CurrentText = LineEdit.second.second; } } // Finally, report the current block edit if there is one. if (CurrentEndLine) { Fixes.push_back(FixItHint::CreateReplacement( CharSourceRange::getCharRange(CurrentRange), CurrentText)); } // Reset the remaining internal state. SourceLocations.clear(); IncludeLocations.clear(); return Fixes; } IncludeSorter::IncludeStyle IncludeSorter::parseIncludeStyle(const std::string &Value) { return Value == "llvm" ? IS_LLVM : IS_Google; } StringRef IncludeSorter::toString(IncludeStyle Style) { return Style == IS_LLVM ? "llvm" : "google"; } } // namespace utils } // namespace tidy } // namespace clang