Will be used to allow checks to insert includes at the right position.
llvm-svn: 244586
ClangTidyModule.cpp
ClangTidyDiagnosticConsumer.cpp
ClangTidyOptions.cpp
+ IncludeInserter.cpp
+ IncludeSorter.cpp
DEPENDS
ClangSACheckers
--- /dev/null
+//===-------- 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
--- /dev/null
+//===---------- 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
--- /dev/null
+//===---------- 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
--- /dev/null
+//===------------ 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
add_extra_unittest(ClangTidyTests
ClangTidyDiagnosticConsumerTest.cpp
ClangTidyOptionsTest.cpp
+ IncludeInserterTest.cpp
GoogleModuleTest.cpp
LLVMModuleTest.cpp
MiscModuleTest.cpp
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Tooling.h"
+#include <map>
namespace clang {
namespace tidy {
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>(
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(
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 "";
--- /dev/null
+//===---- 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