diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.h b/clang-tools-extra/clang-tidy/ClangTidyCheck.h --- a/clang-tools-extra/clang-tidy/ClangTidyCheck.h +++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.h @@ -511,6 +511,9 @@ StringRef getCurrentMainFile() const { return Context->getCurrentFile(); } /// Returns the language options from the context. const LangOptions &getLangOpts() const { return Context->getLangOpts(); } + /// Returns true when the check is ran in a use case when only 1 fix will be + /// applied at a time. + bool isSingleFixMode() const { return Context->isSingleFixMode(); } }; /// Read a named option from the ``Context`` and parse it as a bool. diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -175,6 +175,10 @@ return AllowEnablingAnalyzerAlphaCheckers; } + void setSingleFixMode(bool Value = true) { SingleFixMode = Value; } + + bool isSingleFixMode() const { return SingleFixMode; } + using DiagLevelAndFormatString = std::pair; DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID, SourceLocation Loc) { @@ -210,6 +214,7 @@ std::string ProfilePrefix; bool AllowEnablingAnalyzerAlphaCheckers; + bool SingleFixMode; }; /// Check whether a given diagnostic should be suppressed due to the presence diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -157,7 +157,8 @@ bool AllowEnablingAnalyzerAlphaCheckers) : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), Profile(false), - AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) { + AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers), + SingleFixMode(false) { // Before the first translation unit we can get errors related to command-line // parsing, use empty string for the file name in this case. setCurrentFile(""); diff --git a/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp b/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp --- a/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp @@ -27,7 +27,8 @@ StringLikeClasses(utils::options::parseStringList( Options.get("StringLikeClasses", "::std::basic_string"))), IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)), + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()), AbseilStringsMatchHeader( Options.get("AbseilStringsMatchHeader", "absl/strings/match.h")) {} diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/InitVariablesCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/InitVariablesCheck.cpp --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/InitVariablesCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/InitVariablesCheck.cpp @@ -27,7 +27,8 @@ ClangTidyContext *Context) : ClangTidyCheck(Name, Context), IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)), + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()), MathHeader(Options.get("MathHeader", "")) {} void InitVariablesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp @@ -181,7 +181,8 @@ << Field; bool AddComma = false; - if (!Ctor->getNumCtorInitializers() && FirstToCtorInits) { + if (!Ctor->getNumCtorInitializers() && + (FirstToCtorInits || isSingleFixMode())) { SourceLocation BodyPos = Ctor->getBody()->getBeginLoc(); SourceLocation NextPos = Ctor->getBeginLoc(); do { diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp @@ -22,7 +22,8 @@ StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), GslHeader(Options.get("GslHeader", "")), Inserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)) {} + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()) {} void ProBoundsConstantArrayIndexCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -486,7 +486,8 @@ MinConfidence(Options.get("MinConfidence", Confidence::CL_Reasonable)), NamingStyle(Options.get("NamingStyle", VariableNamer::NS_CamelCase)), Inserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)), + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()), UseCxx20IfAvailable(Options.get("UseCxx20ReverseRanges", true)), ReverseFunction(Options.get("MakeReverseRangeFunction", "")), ReverseHeader(Options.get("MakeReverseRangeHeader", "")) { diff --git a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp --- a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -44,7 +44,8 @@ StringRef MakeSmartPtrFunctionName) : ClangTidyCheck(Name, Context), Inserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)), + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()), MakeSmartPtrFunctionHeader( Options.get("MakeSmartPtrFunctionHeader", "")), MakeSmartPtrFunctionName( diff --git a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp --- a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp @@ -121,7 +121,8 @@ PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), Inserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)), + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()), ValuesOnly(Options.get("ValuesOnly", false)) {} void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { diff --git a/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp --- a/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -41,7 +41,8 @@ ClangTidyContext *Context) : ClangTidyCheck(Name, Context), Inserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)) {} + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()) {} void ReplaceAutoPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "IncludeStyle", Inserter.getStyle()); diff --git a/clang-tools-extra/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp b/clang-tools-extra/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp --- a/clang-tools-extra/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp @@ -24,8 +24,8 @@ ClangTidyContext *Context) : ClangTidyCheck(Name, Context), IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)) { -} + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()) {} void ReplaceRandomShuffleCheck::registerMatchers(MatchFinder *Finder) { const auto Begin = hasArgument(0, expr()); diff --git a/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp b/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp @@ -24,7 +24,8 @@ ClangTidyContext *Context) : ClangTidyCheck(Name, Context), Inserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)) {} + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()) {} void MoveConstructorInitCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( diff --git a/clang-tools-extra/clang-tidy/performance/TypePromotionInMathFnCheck.cpp b/clang-tools-extra/clang-tidy/performance/TypePromotionInMathFnCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/TypePromotionInMathFnCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/TypePromotionInMathFnCheck.cpp @@ -32,8 +32,8 @@ StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)) { -} + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()) {} void TypePromotionInMathFnCheck::registerPPCallbacks( const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -69,7 +69,8 @@ StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), Inserter(Options.getLocalOrGlobal("IncludeStyle", - utils::IncludeSorter::IS_LLVM)), + utils::IncludeSorter::IS_LLVM), + isSingleFixMode()), AllowedTypes( utils::options::parseStringList(Options.get("AllowedTypes", ""))) {} diff --git a/clang-tools-extra/clang-tidy/utils/IncludeInserter.h b/clang-tools-extra/clang-tidy/utils/IncludeInserter.h --- a/clang-tools-extra/clang-tidy/utils/IncludeInserter.h +++ b/clang-tools-extra/clang-tidy/utils/IncludeInserter.h @@ -59,7 +59,8 @@ /// using \code /// Options.getLocalOrGlobal("IncludeStyle", ) /// \endcode - explicit IncludeInserter(IncludeSorter::IncludeStyle Style); + explicit IncludeInserter(IncludeSorter::IncludeStyle Style, + bool SingleFixMode); /// Registers this with the Preprocessor \p PP, must be called before this /// class is used. @@ -93,6 +94,7 @@ llvm::DenseMap> InsertedHeaders; const SourceManager *SourceMgr{nullptr}; const IncludeSorter::IncludeStyle Style; + const bool SingleFixMode; friend class IncludeInserterCallback; }; diff --git a/clang-tools-extra/clang-tidy/utils/IncludeInserter.cpp b/clang-tools-extra/clang-tidy/utils/IncludeInserter.cpp --- a/clang-tools-extra/clang-tidy/utils/IncludeInserter.cpp +++ b/clang-tools-extra/clang-tidy/utils/IncludeInserter.cpp @@ -36,8 +36,9 @@ IncludeInserter *Inserter; }; -IncludeInserter::IncludeInserter(IncludeSorter::IncludeStyle Style) - : Style(Style) {} +IncludeInserter::IncludeInserter(IncludeSorter::IncludeStyle Style, + bool SingleFixMode) + : Style(Style), SingleFixMode(SingleFixMode) {} void IncludeInserter::registerPreprocessor(Preprocessor *PP) { assert(PP && "PP shouldn't be null"); @@ -73,7 +74,12 @@ return llvm::None; // We assume the same Header will never be included both angled and not // angled. - if (!InsertedHeaders[FileID].insert(Header).second) + // In SingleFixMode inserting a header once in a translation unit should not + // prevent us from inserting it again. + if (SingleFixMode) { + if (InsertedHeaders[FileID].contains(Header)) + return llvm::None; + } else if (!InsertedHeaders[FileID].insert(Header).second) return llvm::None; return getOrCreate(FileID).CreateIncludeInsertion(Header, IsAngled); diff --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp --- a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp +++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp @@ -30,8 +30,8 @@ TransformerClangTidyCheck::TransformerClangTidyCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), - Inserter( - Options.getLocalOrGlobal("IncludeStyle", IncludeSorter::IS_LLVM)) {} + Inserter(Options.getLocalOrGlobal("IncludeStyle", IncludeSorter::IS_LLVM), + isSingleFixMode()) {} // This constructor cannot dispatch to the simpler one (below), because, in // order to get meaningful results from `getLangOpts` and `Options`, we need the diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -306,6 +306,7 @@ CTContext->setDiagnosticsEngine(&Clang->getDiagnostics()); CTContext->setASTContext(&Clang->getASTContext()); CTContext->setCurrentFile(Filename); + CTContext->setSingleFixMode(); CTChecks = CTFactories.createChecks(CTContext.getPointer()); llvm::erase_if(CTChecks, [&](const auto &Check) { return !Check->isLanguageVersionSupported(CTContext->getLangOpts()); diff --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp --- a/clang-tools-extra/clangd/TidyProvider.cpp +++ b/clang-tools-extra/clangd/TidyProvider.cpp @@ -76,12 +76,24 @@ } }; +llvm::SmallString<256> pathAppend(PathRef Path, llvm::StringRef Tail) { + llvm::SmallString<256> ConfigPath(Path); + llvm::sys::path::append(ConfigPath, Tail); + return ConfigPath; +} + // Access to combined config from .clang-tidy files governing a source file. // Each config file is cached and the caches are shared for affected sources. // // FIXME: largely duplicates config::Provider::fromAncestorRelativeYAMLFiles. // Potentially useful for compile_commands.json too. Extract? class DotClangTidyTree { + struct DirectoryNode { + DirectoryNode(PathRef File) : Cache(File), Parent(nullptr) {} + DotClangTidyCache Cache; + DirectoryNode *Parent; + }; + const ThreadsafeFS &FS; std::string RelPath; std::chrono::steady_clock::duration MaxStaleness; @@ -90,56 +102,92 @@ // Keys are the ancestor directory, not the actual config path within it. // We only insert into this map, so pointers to values are stable forever. // Mutex guards the map itself, not the values (which are threadsafe). - mutable llvm::StringMap Cache; - -public: - DotClangTidyTree(const ThreadsafeFS &FS) - : FS(FS), RelPath(".clang-tidy"), MaxStaleness(std::chrono::seconds(5)) {} - - void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) { + // We store values as linked lists pointing to their parent directories nodes, + // this saves quering the map for each component of a path. + mutable llvm::StringMap Cache; + + /// Ensures there is a FileCache for each .clang-tidy file in the directory + /// tree up to \p AbsPath and returns a linked list structure pointing to the + /// first item. + /// \pre \p AbsPath is an absolute path to a file. + DirectoryNode *getNode(PathRef AbsPath) { namespace path = llvm::sys::path; assert(path::is_absolute(AbsPath)); // Compute absolute paths to all ancestors (substrings of P.Path). // Ensure cache entries for each ancestor exist in the map. + + // AbsPath should be pointing to a file, get the directory. llvm::StringRef Parent = path::parent_path(AbsPath); - llvm::SmallVector Caches; - { - std::lock_guard Lock(Mu); - for (auto I = path::rbegin(Parent), E = path::rend(Parent); I != E; ++I) { - assert(I->end() >= Parent.begin() && I->end() <= Parent.end() && - "Canonical path components should be substrings"); - llvm::StringRef Ancestor(Parent.begin(), I->end() - Parent.begin()); + auto I = path::rbegin(Parent), E = path::rend(Parent); + assert(I != E && "There should be at least 1 path component to traverse"); + llvm::StringRef Ancestor(Parent.begin(), I->end() - Parent.begin()); + + std::lock_guard Lock(Mu); + + auto It = Cache.find(Ancestor); + // This is the hot path. After invoking this method on a given path once, + // The path will stay in the map for the life of this object, saving any + // further lookup. + if (LLVM_LIKELY(It != Cache.end())) + return &It->getValue(); + + // Build the FileCache for this item and store a pointer to its parent node, + // this will be filled in when we process the next component in the path. + DirectoryNode *Result = + &Cache.try_emplace(Ancestor, pathAppend(Ancestor, RelPath).str()) + .first->getValue(); + DirectoryNode **ParentPtr = &Result->Parent; + + // Skip the first item as we have already processed it. + ++I; + for (; I != E; ++I) { + assert(I->end() >= Parent.begin() && I->end() <= Parent.end() && + "Canonical path components should be substrings"); + llvm::StringRef Ancestor(Parent.begin(), I->end() - Parent.begin()); #ifdef _WIN32 - // C:\ is an ancestor, but skip its (relative!) parent C:. - if (Ancestor.size() == 2 && Ancestor.back() == ':') - continue; + // C:\ is an ancestor, but skip its (relative!) parent C:. + if (Ancestor.size() == 2 && Ancestor.back() == ':') + break; #endif - assert(path::is_absolute(Ancestor)); - - auto It = Cache.find(Ancestor); - - // Assemble the actual config file path only if needed. - if (It == Cache.end()) { - llvm::SmallString<256> ConfigPath = Ancestor; - path::append(ConfigPath, RelPath); - It = Cache.try_emplace(Ancestor, ConfigPath.str()).first; - } - Caches.push_back(&It->second); + assert(path::is_absolute(Ancestor)); + + It = Cache.find(Ancestor); + if (It != Cache.end()) { + // We have found a relative parent already in the map. Update the childs + // parent pointer to this, then stop. This items Parent should contain + // the tree below it already. + *ParentPtr = &It->getValue(); + break; } + + // The item isn't in the cache, so assemble it. + auto *Node = + &Cache.try_emplace(Ancestor, pathAppend(Ancestor, RelPath).str()) + .first->getValue(); + *ParentPtr = Node; + // Keep track of this nodes parent pointer for the next iteration. + ParentPtr = &Node->Parent; } - // Finally query each individual file. - // This will take a (per-file) lock for each file that actually exists. + return Result; + } + +public: + DotClangTidyTree(const ThreadsafeFS &FS) + : FS(FS), RelPath(".clang-tidy"), MaxStaleness(std::chrono::seconds(5)) {} + + void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) { std::chrono::steady_clock::time_point FreshTime = std::chrono::steady_clock::now() - MaxStaleness; llvm::SmallVector> OptionStack; - for (const DotClangTidyCache *Cache : Caches) - if (auto Config = Cache->get(FS, FreshTime)) { + for (auto *DirNode = getNode(AbsPath); DirNode; DirNode = DirNode->Parent) { + if (auto Config = DirNode->Cache.get(FS, FreshTime)) { OptionStack.push_back(std::move(Config)); if (!OptionStack.back()->InheritParentConfig.getValueOr(false)) break; } + } unsigned Order = 1u; for (auto &Option : llvm::reverse(OptionStack)) Result.mergeWith(*Option, Order++); @@ -314,3 +362,12 @@ } } // namespace clangd } // namespace clang + +struct Foo { + int A, B, C; + Foo(int D, int E, int F) { + A = D; + B = E; + C = F; + } +}; \ No newline at end of file diff --git a/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp b/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp --- a/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/IncludeInserterTest.cpp @@ -30,8 +30,9 @@ public: IncludeInserterCheckBase(StringRef CheckName, ClangTidyContext *Context, utils::IncludeSorter::IncludeStyle Style = - utils::IncludeSorter::IS_Google) - : ClangTidyCheck(CheckName, Context), Inserter(Style) {} + utils::IncludeSorter::IS_Google, + bool SingleFixMode = false) + : ClangTidyCheck(CheckName, Context), Inserter(Style, SingleFixMode) {} void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override { @@ -85,6 +86,18 @@ } }; +class MultipleHeaderSingleInserterCheck : public IncludeInserterCheckBase { +public: + MultipleHeaderSingleInserterCheck(StringRef CheckName, + ClangTidyContext *Context) + : IncludeInserterCheckBase(CheckName, Context, + utils::IncludeSorter::IS_Google, true) {} + + std::vector headersToInclude() const override { + return {"path/to/header.h", "path/to/header2.h", "path/to/header.h"}; + } +}; + class CSystemIncludeInserterCheck : public IncludeInserterCheckBase { public: CSystemIncludeInserterCheck(StringRef CheckName, ClangTidyContext *Context) @@ -246,6 +259,37 @@ PreCode, "clang_tidy/tests/insert_includes_test_input2.cc")); } +TEST(IncludeInserterTest, InsertMultipleIncludesNoDeduplicate) { + const char *PreCode = R"( +#include "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 "clang_tidy/tests/insert_includes_test_header.h" + +#include +#include + +#include "path/to/a/header.h" +#include "path/to/header.h" +#include "path/to/header2.h" + +void foo() { + int a = 0; +})"; + + EXPECT_EQ(PostCode, + runCheckOnCode( + PreCode, "clang_tidy/tests/insert_includes_test_input2.cc")); +} + TEST(IncludeInserterTest, InsertBeforeFirstNonSystemInclude) { const char *PreCode = R"( #include "clang_tidy/tests/insert_includes_test_header.h"