forked from OSchip/llvm-project
Add an IncludeInserter to clang-tidy.
Will be used to allow checks to insert includes at the right position. llvm-svn: 244586
This commit is contained in:
parent
6e3ba33b07
commit
d00d6f1d43
|
@ -7,6 +7,8 @@ add_clang_library(clangTidy
|
|||
ClangTidyModule.cpp
|
||||
ClangTidyDiagnosticConsumer.cpp
|
||||
ClangTidyOptions.cpp
|
||||
IncludeInserter.cpp
|
||||
IncludeSorter.cpp
|
||||
|
||||
DEPENDS
|
||||
ClangSACheckers
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
//===-------- IncludeInserter.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 "IncludeInserter.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
|
||||
class IncludeInserterCallback : public PPCallbacks {
|
||||
public:
|
||||
explicit IncludeInserterCallback(IncludeInserter *IncludeInserter)
|
||||
: IncludeInserter(IncludeInserter) {}
|
||||
// Implements PPCallbacks::InclusionDerective(). Records the names and source
|
||||
// locations of the inclusions in the main source file being processed.
|
||||
void InclusionDirective(SourceLocation HashLocation,
|
||||
const Token & /*include_token*/,
|
||||
StringRef FileNameRef, bool IsAngled,
|
||||
CharSourceRange FileNameRange,
|
||||
const FileEntry * /*IncludedFile*/,
|
||||
StringRef /*SearchPath*/, StringRef /*RelativePath*/,
|
||||
const Module * /*ImportedModule*/) override {
|
||||
IncludeInserter->AddInclude(FileNameRef, IsAngled, HashLocation,
|
||||
FileNameRange.getEnd());
|
||||
}
|
||||
|
||||
private:
|
||||
IncludeInserter *IncludeInserter;
|
||||
};
|
||||
|
||||
IncludeInserter::IncludeInserter(const SourceManager &SourceMgr,
|
||||
const LangOptions &LangOpts,
|
||||
IncludeSorter::IncludeStyle Style)
|
||||
: SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style) {}
|
||||
|
||||
IncludeInserter::~IncludeInserter() {}
|
||||
|
||||
std::unique_ptr<PPCallbacks> IncludeInserter::CreatePPCallbacks() {
|
||||
return llvm::make_unique<IncludeInserterCallback>(this);
|
||||
}
|
||||
|
||||
llvm::Optional<FixItHint>
|
||||
IncludeInserter::CreateIncludeInsertion(FileID FileID, StringRef Header,
|
||||
bool IsAngled) {
|
||||
// We assume the same Header will never be included both angled and not
|
||||
// angled.
|
||||
if (!InsertedHeaders.insert(std::make_pair(FileID, std::set<std::string>()))
|
||||
.second) {
|
||||
return llvm::None;
|
||||
}
|
||||
if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) {
|
||||
return llvm::None;
|
||||
}
|
||||
return IncludeSorterByFile[FileID]->CreateIncludeInsertion(Header, IsAngled);
|
||||
}
|
||||
|
||||
void IncludeInserter::AddInclude(StringRef file_name, bool IsAngled,
|
||||
SourceLocation HashLocation,
|
||||
SourceLocation end_location) {
|
||||
FileID FileID = SourceMgr.getFileID(HashLocation);
|
||||
if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) {
|
||||
IncludeSorterByFile.insert(std::make_pair(
|
||||
FileID, llvm::make_unique<IncludeSorter>(
|
||||
&SourceMgr, &LangOpts, FileID,
|
||||
SourceMgr.getFilename(HashLocation), Style)));
|
||||
}
|
||||
IncludeSorterByFile[FileID]->AddInclude(file_name, IsAngled, HashLocation,
|
||||
end_location);
|
||||
}
|
||||
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
|
@ -0,0 +1,75 @@
|
|||
//===---------- IncludeInserter.h - clang-tidy ----------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H
|
||||
|
||||
#include "IncludeSorter.h"
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/LangOptions.h"
|
||||
#include "clang/Basic/SourceManager.h"
|
||||
#include "clang/Lex/PPCallbacks.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
|
||||
// IncludeInserter can be used by ClangTidyChecks in the following fashion:
|
||||
// class MyCheck : public ClangTidyCheck {
|
||||
// public:
|
||||
// void registerPPCallbacks(CompilerInstance& Compiler) override {
|
||||
// Inserter.reset(new IncludeInserter(&Compiler.getSourceManager(),
|
||||
// &Compiler.getLangOpts()));
|
||||
// Compiler.getPreprocessor().addPPCallbacks(
|
||||
// Inserter->CreatePPCallback());
|
||||
// }
|
||||
//
|
||||
// void registerMatchers(ast_matchers::MatchFinder* Finder) override { ... }
|
||||
//
|
||||
// void check(const ast_matchers::MatchFinder::MatchResult& Result) override {
|
||||
// ...
|
||||
// Inserter->CreateIncludeInsertion(
|
||||
// Result.SourceManager->getMainFileID(), "path/to/Header.h",
|
||||
// /*IsAngled=*/false);
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// private:
|
||||
// std::unique_ptr<IncludeInserter> Inserter;
|
||||
// };
|
||||
class IncludeInserter {
|
||||
public:
|
||||
IncludeInserter(const SourceManager &SourceMgr, const LangOptions &LangOpts,
|
||||
IncludeSorter::IncludeStyle Style);
|
||||
~IncludeInserter();
|
||||
|
||||
// Create PPCallbacks for registration with the compiler's preprocessor.
|
||||
std::unique_ptr<PPCallbacks> CreatePPCallbacks();
|
||||
|
||||
// Creates a Header inclusion directive fixit. Returns None on error or
|
||||
// if inclusion directive already exists.
|
||||
llvm::Optional<FixItHint>
|
||||
CreateIncludeInsertion(FileID FileID, llvm::StringRef Header, bool IsAngled);
|
||||
|
||||
private:
|
||||
void AddInclude(StringRef file_name, bool IsAngled,
|
||||
SourceLocation hash_location, SourceLocation end_location);
|
||||
|
||||
llvm::DenseMap<FileID, std::unique_ptr<IncludeSorter>> IncludeSorterByFile;
|
||||
llvm::DenseMap<FileID, std::set<std::string>> InsertedHeaders;
|
||||
const SourceManager &SourceMgr;
|
||||
const LangOptions &LangOpts;
|
||||
const IncludeSorter::IncludeStyle Style;
|
||||
friend class IncludeInserterCallback;
|
||||
};
|
||||
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H
|
|
@ -0,0 +1,267 @@
|
|||
//===---------- 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 {
|
||||
|
||||
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"});
|
||||
}
|
||||
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.equals(CanonicalInclude)) {
|
||||
return IncludeSorter::IK_MainTUInclude;
|
||||
}
|
||||
if (Style == IncludeSorter::IS_Google) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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<FixItHint> IncludeSorter::CreateIncludeInsertion(StringRef FileName,
|
||||
bool IsAngled) {
|
||||
if (SourceLocations.empty()) {
|
||||
return None;
|
||||
}
|
||||
std::string IncludeStmt =
|
||||
IsAngled ? llvm::Twine("#include <" + FileName + ">\n").str()
|
||||
: llvm::Twine("#include \"" + FileName + "\"\n").str();
|
||||
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);
|
||||
}
|
||||
IncludeKinds NonEmptyKind = IK_InvalidInclude;
|
||||
for (int i = IncludeKind - 1; i >= 0; --i) {
|
||||
if (!IncludeBucket[i].empty()) {
|
||||
NonEmptyKind = static_cast<IncludeKinds>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (NonEmptyKind == IK_InvalidInclude) {
|
||||
return llvm::None;
|
||||
}
|
||||
const std::string &LastInclude = IncludeBucket[NonEmptyKind].back();
|
||||
SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back();
|
||||
IncludeStmt.append("\n");
|
||||
return FixItHint::CreateInsertion(
|
||||
LastIncludeLocation.getEnd().getLocWithOffset(1), IncludeStmt);
|
||||
}
|
||||
|
||||
std::vector<FixItHint> IncludeSorter::GetEdits() {
|
||||
if (SourceLocations.empty())
|
||||
return {};
|
||||
|
||||
typedef std::map<int, std::pair<SourceRange, std::string>>
|
||||
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(),
|
||||
[](const std::string &Left, const std::string &Right) {
|
||||
return llvm::StringRef(Left).compare_lower(Right) < 0;
|
||||
});
|
||||
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<FixItHint> Fixes;
|
||||
for (const auto &LineEdit : Edits) {
|
||||
const SourceRange &EditRange = LineEdit.second.first;
|
||||
// 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()) {
|
||||
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(CreateFixIt(CurrentRange, CurrentText));
|
||||
}
|
||||
|
||||
CurrentEndLine = LineEdit.first;
|
||||
CurrentRange = EditRange;
|
||||
CurrentText = LineEdit.second.second;
|
||||
}
|
||||
}
|
||||
// Finally, report the current block edit if there is one.
|
||||
if (CurrentEndLine) {
|
||||
Fixes.push_back(CreateFixIt(CurrentRange, CurrentText));
|
||||
}
|
||||
|
||||
// Reset the remaining internal state.
|
||||
SourceLocations.clear();
|
||||
IncludeLocations.clear();
|
||||
return Fixes;
|
||||
}
|
||||
|
||||
// Creates a fix-it for the given replacements.
|
||||
// Takes the the source location that will be replaced, and the new text.
|
||||
FixItHint IncludeSorter::CreateFixIt(SourceRange EditRange,
|
||||
const std::string &NewText) {
|
||||
FixItHint Fix;
|
||||
Fix.RemoveRange = CharSourceRange::getCharRange(EditRange);
|
||||
Fix.CodeToInsert = NewText;
|
||||
return Fix;
|
||||
}
|
||||
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
|
@ -0,0 +1,87 @@
|
|||
//===------------ IncludeSorter.h - clang-tidy ----------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H
|
||||
|
||||
#include "ClangTidy.h"
|
||||
#include <string>
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
|
||||
// Class used by IncludeSorterCallback and IncludeInserterCallback to record the
|
||||
// names of the inclusions in a given source file being processed and generate
|
||||
// the necessary commands to sort the inclusions according to the precedence
|
||||
// enocded in IncludeKinds.
|
||||
class IncludeSorter {
|
||||
public:
|
||||
// Supported include styles.
|
||||
enum IncludeStyle {
|
||||
IS_LLVM = 0,
|
||||
IS_Google = 1
|
||||
};
|
||||
|
||||
// The classifications of inclusions, in the order they should be sorted.
|
||||
enum IncludeKinds {
|
||||
IK_MainTUInclude = 0, // e.g. #include "foo.h" when editing foo.cc
|
||||
IK_CSystemInclude = 1, // e.g. #include <stdio.h>
|
||||
IK_CXXSystemInclude = 2, // e.g. #include <vector>
|
||||
IK_NonSystemInclude = 3, // e.g. #include "bar.h"
|
||||
IK_InvalidInclude = 4 // total number of valid IncludeKinds
|
||||
};
|
||||
|
||||
// IncludeSorter constructor; takes the FileID and name of the file to be
|
||||
// processed by the sorter.
|
||||
IncludeSorter(const SourceManager* SourceMgr,
|
||||
const LangOptions* LangOpts, const FileID FileID,
|
||||
StringRef FileName, IncludeStyle Style);
|
||||
|
||||
// Returns the SourceManager-specific file ID for the file being handled by
|
||||
// the sorter.
|
||||
const FileID current_FileID() const { return CurrentFileID; }
|
||||
|
||||
// Adds the given #include to the sorter.
|
||||
void AddInclude(StringRef FileName, bool IsAngled,
|
||||
SourceLocation HashLocation, SourceLocation EndLocation);
|
||||
|
||||
// Returns the edits needed to sort the current set of includes and reset the
|
||||
// internal state (so that different blocks of includes are sorted separately
|
||||
// within the same file).
|
||||
std::vector<FixItHint> GetEdits();
|
||||
|
||||
// Creates a quoted inclusion directive in the right sort order. Returns None
|
||||
// on error or if header inclusion directive for header already exists.
|
||||
Optional<FixItHint> CreateIncludeInsertion(StringRef FileName,
|
||||
bool IsAngled);
|
||||
|
||||
private:
|
||||
typedef SmallVector<SourceRange, 1> SourceRangeVector;
|
||||
|
||||
// Creates a fix-it for the given replacements.
|
||||
// Takes the the source location that will be replaced, and the new text.
|
||||
FixItHint CreateFixIt(SourceRange EditRange, const std::string& NewText);
|
||||
|
||||
const SourceManager* SourceMgr;
|
||||
const LangOptions* LangOpts;
|
||||
const IncludeStyle Style;
|
||||
FileID CurrentFileID;
|
||||
// The file name stripped of common suffixes.
|
||||
StringRef CanonicalFile;
|
||||
// Locations of visited include directives.
|
||||
SourceRangeVector SourceLocations;
|
||||
// Mapping from file name to #include locations.
|
||||
llvm::StringMap<SourceRangeVector> IncludeLocations;
|
||||
// Includes sorted into buckets.
|
||||
SmallVector<std::string, 1> IncludeBucket[IK_InvalidInclude];
|
||||
};
|
||||
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H
|
|
@ -9,6 +9,7 @@ include_directories(${CLANG_LINT_SOURCE_DIR})
|
|||
add_extra_unittest(ClangTidyTests
|
||||
ClangTidyDiagnosticConsumerTest.cpp
|
||||
ClangTidyOptionsTest.cpp
|
||||
IncludeInserterTest.cpp
|
||||
GoogleModuleTest.cpp
|
||||
LLVMModuleTest.cpp
|
||||
MiscModuleTest.cpp
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "clang/Frontend/FrontendActions.h"
|
||||
#include "clang/Tooling/Refactoring.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include <map>
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
|
@ -46,7 +47,8 @@ std::string
|
|||
runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
|
||||
const Twine &Filename = "input.cc",
|
||||
ArrayRef<std::string> ExtraArgs = None,
|
||||
const ClangTidyOptions &ExtraOptions = ClangTidyOptions()) {
|
||||
const ClangTidyOptions &ExtraOptions = ClangTidyOptions(),
|
||||
std::map<StringRef, StringRef> PathsToContent = {}) {
|
||||
ClangTidyOptions Options = ExtraOptions;
|
||||
Options.Checks = "*";
|
||||
ClangTidyContext Context(llvm::make_unique<DefaultOptionsProvider>(
|
||||
|
@ -60,6 +62,7 @@ runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
|
|||
std::vector<std::string> ArgCXX11(1, "clang-tidy");
|
||||
ArgCXX11.push_back("-fsyntax-only");
|
||||
ArgCXX11.push_back("-std=c++11");
|
||||
ArgCXX11.push_back("-Iinclude");
|
||||
ArgCXX11.insert(ArgCXX11.end(), ExtraArgs.begin(), ExtraArgs.end());
|
||||
ArgCXX11.push_back(Filename.str());
|
||||
llvm::IntrusiveRefCntPtr<FileManager> Files(
|
||||
|
@ -67,6 +70,10 @@ runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
|
|||
tooling::ToolInvocation Invocation(
|
||||
ArgCXX11, new TestClangTidyAction(Check, Finder, Context), Files.get());
|
||||
Invocation.mapVirtualFile(Filename.str(), Code);
|
||||
for (const auto & FileContent : PathsToContent) {
|
||||
Invocation.mapVirtualFile(Twine("include/" + FileContent.first).str(),
|
||||
FileContent.second);
|
||||
}
|
||||
Invocation.setDiagnosticConsumer(&DiagConsumer);
|
||||
if (!Invocation.run())
|
||||
return "";
|
||||
|
|
|
@ -0,0 +1,407 @@
|
|||
//===---- IncludeInserterTest.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 "../clang-tidy/IncludeInserter.h"
|
||||
#include "clang/Lex/Preprocessor.h"
|
||||
#include "clang/Frontend/CompilerInstance.h"
|
||||
#include "ClangTidyTest.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tidy {
|
||||
namespace {
|
||||
|
||||
class IncludeInserterCheckBase : public ClangTidyCheck {
|
||||
public:
|
||||
using ClangTidyCheck::ClangTidyCheck;
|
||||
void registerPPCallbacks(CompilerInstance &Compiler) override {
|
||||
Inserter.reset(new IncludeInserter(Compiler.getSourceManager(),
|
||||
Compiler.getLangOpts(),
|
||||
IncludeSorter::IS_Google));
|
||||
Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks());
|
||||
}
|
||||
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override {
|
||||
Finder->addMatcher(ast_matchers::declStmt().bind("stmt"), this);
|
||||
}
|
||||
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
|
||||
auto Fixit =
|
||||
Inserter->CreateIncludeInsertion(Result.SourceManager->getMainFileID(),
|
||||
HeaderToInclude(), IsAngledInclude());
|
||||
if (Fixit) {
|
||||
diag(Result.Nodes.getStmtAs<DeclStmt>("stmt")->getLocStart(), "foo, bar")
|
||||
<< *Fixit;
|
||||
}
|
||||
// Second include should yield no Fixit.
|
||||
Fixit =
|
||||
Inserter->CreateIncludeInsertion(Result.SourceManager->getMainFileID(),
|
||||
HeaderToInclude(), IsAngledInclude());
|
||||
EXPECT_FALSE(Fixit);
|
||||
}
|
||||
|
||||
virtual StringRef HeaderToInclude() const = 0;
|
||||
virtual bool IsAngledInclude() const = 0;
|
||||
|
||||
std::unique_ptr<IncludeInserter> Inserter;
|
||||
};
|
||||
|
||||
class NonSystemHeaderInserterCheck : public IncludeInserterCheckBase {
|
||||
public:
|
||||
using IncludeInserterCheckBase::IncludeInserterCheckBase;
|
||||
StringRef HeaderToInclude() const override { return "path/to/header.h"; }
|
||||
bool IsAngledInclude() const override { return false; }
|
||||
};
|
||||
|
||||
class CXXSystemIncludeInserterCheck : public IncludeInserterCheckBase {
|
||||
public:
|
||||
using IncludeInserterCheckBase::IncludeInserterCheckBase;
|
||||
StringRef HeaderToInclude() const override { return "set"; }
|
||||
bool IsAngledInclude() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename Check>
|
||||
std::string runCheckOnCode(StringRef Code, StringRef Filename,
|
||||
size_t NumWarningsExpected) {
|
||||
std::vector<ClangTidyError> Errors;
|
||||
return test::runCheckOnCode<Check>(Code, &Errors, Filename, None,
|
||||
ClangTidyOptions(),
|
||||
{// Main file include
|
||||
{"devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.h",
|
||||
"\n"},
|
||||
// Non system headers
|
||||
{"path/to/a/header.h", "\n"},
|
||||
{"path/to/z/header.h", "\n"},
|
||||
{"path/to/header.h", "\n"},
|
||||
// Fake system headers.
|
||||
{"stdlib.h", "\n"},
|
||||
{"unistd.h", "\n"},
|
||||
{"list", "\n"},
|
||||
{"map", "\n"},
|
||||
{"set", "\n"},
|
||||
{"vector", "\n"}});
|
||||
EXPECT_EQ(NumWarningsExpected, Errors.size());
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertAfterLastNonSystemInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
#include "path/to/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_input2.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertBeforeFirstNonSystemInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/z/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/header.h"
|
||||
#include "path/to/z/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_input2.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertBetweenNonSystemIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
#include "path/to/z/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
#include "path/to/header.h"
|
||||
#include "path/to/z/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_input2.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, NonSystemIncludeAlreadyIncluded) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
#include "path/to/header.h"
|
||||
#include "path/to/z/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
EXPECT_EQ(PreCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_input2.cc",
|
||||
0));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertNonSystemIncludeAfterLastCXXSystemInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertNonSystemIncludeAfterMainFileInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include "path/to/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterLastCXXSystemInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertCXXSystemIncludeBeforeFirstCXXSystemInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertCXXSystemIncludeBetweenCXXSystemIncludes) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterMainFileInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterCSystemInclude) {
|
||||
const char *PreCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
const char *PostCode = R"(
|
||||
#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "path/to/a/header.h"
|
||||
|
||||
void foo() {
|
||||
int a = 0;
|
||||
})";
|
||||
|
||||
EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
|
||||
PreCode, "devtools/cymbal/clang_tidy/tests/"
|
||||
"insert_includes_test_header.cc",
|
||||
1));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tidy
|
||||
} // namespace clang
|
Loading…
Reference in New Issue