From: Manuel Klimek Date: Tue, 11 Aug 2015 11:37:48 +0000 (+0000) Subject: Add an IncludeInserter to clang-tidy. X-Git-Tag: studio-1.4~339 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=d00d6f1d43a0d0b820943b6c9e553cf0514a1407;p=platform%2Fupstream%2Fllvm.git Add an IncludeInserter to clang-tidy. Will be used to allow checks to insert includes at the right position. llvm-svn: 244586 --- diff --git a/clang-tools-extra/clang-tidy/CMakeLists.txt b/clang-tools-extra/clang-tidy/CMakeLists.txt index c9dd786..2b4a5b6 100644 --- a/clang-tools-extra/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/CMakeLists.txt @@ -7,6 +7,8 @@ add_clang_library(clangTidy ClangTidyModule.cpp ClangTidyDiagnosticConsumer.cpp ClangTidyOptions.cpp + IncludeInserter.cpp + IncludeSorter.cpp DEPENDS ClangSACheckers diff --git a/clang-tools-extra/clang-tidy/IncludeInserter.cpp b/clang-tools-extra/clang-tidy/IncludeInserter.cpp new file mode 100644 index 0000000..74c181a --- /dev/null +++ b/clang-tools-extra/clang-tidy/IncludeInserter.cpp @@ -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 IncludeInserter::CreatePPCallbacks() { + return llvm::make_unique(this); +} + +llvm::Optional +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())) + .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( + &SourceMgr, &LangOpts, FileID, + SourceMgr.getFilename(HashLocation), Style))); + } + IncludeSorterByFile[FileID]->AddInclude(file_name, IsAngled, HashLocation, + end_location); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/IncludeInserter.h b/clang-tools-extra/clang-tidy/IncludeInserter.h new file mode 100644 index 0000000..31f57d8 --- /dev/null +++ b/clang-tools-extra/clang-tidy/IncludeInserter.h @@ -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 +#include + +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 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 CreatePPCallbacks(); + + // Creates a Header inclusion directive fixit. Returns None on error or + // if inclusion directive already exists. + llvm::Optional + CreateIncludeInsertion(FileID FileID, llvm::StringRef Header, bool IsAngled); + +private: + void AddInclude(StringRef file_name, bool IsAngled, + SourceLocation hash_location, SourceLocation end_location); + + llvm::DenseMap> IncludeSorterByFile; + llvm::DenseMap> 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 diff --git a/clang-tools-extra/clang-tidy/IncludeSorter.cpp b/clang-tools-extra/clang-tidy/IncludeSorter.cpp new file mode 100644 index 0000000..ff6fb8f --- /dev/null +++ b/clang-tools-extra/clang-tidy/IncludeSorter.cpp @@ -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 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.equals(CanonicalInclude)) { + 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) { + 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(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 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(), + [](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 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 diff --git a/clang-tools-extra/clang-tidy/IncludeSorter.h b/clang-tools-extra/clang-tidy/IncludeSorter.h new file mode 100644 index 0000000..a587575 --- /dev/null +++ b/clang-tools-extra/clang-tidy/IncludeSorter.h @@ -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 + +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 + IK_CXXSystemInclude = 2, // e.g. #include + 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 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 CreateIncludeInsertion(StringRef FileName, + bool IsAngled); + + private: + typedef SmallVector 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 IncludeLocations; + // Includes sorted into buckets. + SmallVector IncludeBucket[IK_InvalidInclude]; +}; + +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt index 3759484..a756c45 100644 --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -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 diff --git a/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h b/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h index 1bc01d3..2d1ba7d 100644 --- a/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h +++ b/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h @@ -17,6 +17,7 @@ #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Tooling.h" +#include namespace clang { namespace tidy { @@ -46,7 +47,8 @@ std::string runCheckOnCode(StringRef Code, std::vector *Errors = nullptr, const Twine &Filename = "input.cc", ArrayRef ExtraArgs = None, - const ClangTidyOptions &ExtraOptions = ClangTidyOptions()) { + const ClangTidyOptions &ExtraOptions = ClangTidyOptions(), + std::map PathsToContent = {}) { ClangTidyOptions Options = ExtraOptions; Options.Checks = "*"; ClangTidyContext Context(llvm::make_unique( @@ -60,6 +62,7 @@ runCheckOnCode(StringRef Code, std::vector *Errors = nullptr, std::vector 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 Files( @@ -67,6 +70,10 @@ runCheckOnCode(StringRef Code, std::vector *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 ""; diff --git a/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp b/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp new file mode 100644 index 0000000..72b5fc3 --- /dev/null +++ b/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp @@ -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("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 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 +std::string runCheckOnCode(StringRef Code, StringRef Filename, + size_t NumWarningsExpected) { + std::vector Errors; + return test::runCheckOnCode(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 +#include + +#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 +#include + +#include "path/to/a/header.h" +#include "path/to/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + 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 +#include + +#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 +#include + +#include "path/to/header.h" +#include "path/to/z/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + 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 +#include + +#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 +#include + +#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( + 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 +#include + +#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( + 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 +#include + +void foo() { + int a = 0; +})"; + const char *PostCode = R"( +#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h" + +#include +#include + +#include "path/to/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + 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( + 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 +#include + +#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 +#include +#include + +#include "path/to/a/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + 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 + +#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 +#include + +#include "path/to/a/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + 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 +#include + +#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 +#include +#include + +#include "path/to/a/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + 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 + +#include "path/to/a/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + 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 + +#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 + +#include + +#include "path/to/a/header.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, runCheckOnCode( + PreCode, "devtools/cymbal/clang_tidy/tests/" + "insert_includes_test_header.cc", + 1)); +} + +} // namespace +} // namespace tidy +} // namespace clang