| //===---- IncludeInserterTest.cpp - clang-tidy ----------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "../clang-tidy/utils/IncludeInserter.h" |
| #include "clang/Lex/Preprocessor.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "ClangTidyTest.h" |
| #include "gtest/gtest.h" |
| |
| // FIXME: Canonicalize paths correctly on windows. |
| // Currently, adding virtual files will canonicalize the paths before |
| // storing the virtual entries. |
| // When resolving virtual entries in the FileManager, the paths (for |
| // example coming from a #include directive) are not canonicalized |
| // to native paths; thus, the virtual file is not found. |
| // This needs to be fixed in the FileManager before we can make |
| // clang-tidy tests work. |
| #if !defined(_WIN32) |
| |
| namespace clang { |
| namespace tidy { |
| namespace { |
| |
| class IncludeInserterCheckBase : public ClangTidyCheck { |
| public: |
| IncludeInserterCheckBase(StringRef CheckName, ClangTidyContext *Context, |
| utils::IncludeSorter::IncludeStyle Style = |
| utils::IncludeSorter::IS_Google) |
| : ClangTidyCheck(CheckName, Context), Inserter(Style) {} |
| |
| void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, |
| Preprocessor *ModuleExpanderPP) override { |
| Inserter.registerPreprocessor(PP); |
| } |
| |
| 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 Diag = diag(Result.Nodes.getNodeAs<DeclStmt>("stmt")->getBeginLoc(), |
| "foo, bar"); |
| for (StringRef Header : headersToInclude()) { |
| Diag << Inserter.createMainFileIncludeInsertion(Header); |
| } |
| } |
| |
| virtual std::vector<StringRef> headersToInclude() const = 0; |
| |
| utils::IncludeInserter Inserter; |
| }; |
| |
| class NonSystemHeaderInserterCheck : public IncludeInserterCheckBase { |
| public: |
| NonSystemHeaderInserterCheck(StringRef CheckName, ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context) {} |
| |
| std::vector<StringRef> headersToInclude() const override { |
| return {"path/to/header.h"}; |
| } |
| }; |
| |
| class EarlyInAlphabetHeaderInserterCheck : public IncludeInserterCheckBase { |
| public: |
| EarlyInAlphabetHeaderInserterCheck(StringRef CheckName, ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context) {} |
| |
| std::vector<StringRef> headersToInclude() const override { |
| return {"a/header.h"}; |
| } |
| }; |
| |
| class MultipleHeaderInserterCheck : public IncludeInserterCheckBase { |
| public: |
| MultipleHeaderInserterCheck(StringRef CheckName, ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context) {} |
| |
| std::vector<StringRef> 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) |
| : IncludeInserterCheckBase(CheckName, Context) {} |
| |
| std::vector<StringRef> headersToInclude() const override { |
| return {"<stdlib.h>"}; |
| } |
| }; |
| |
| class CXXSystemIncludeInserterCheck : public IncludeInserterCheckBase { |
| public: |
| CXXSystemIncludeInserterCheck(StringRef CheckName, ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context) {} |
| |
| std::vector<StringRef> headersToInclude() const override { return {"<set>"}; } |
| }; |
| |
| class InvalidIncludeInserterCheck : public IncludeInserterCheckBase { |
| public: |
| InvalidIncludeInserterCheck(StringRef CheckName, ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context) {} |
| |
| std::vector<StringRef> headersToInclude() const override { |
| return {"a.h", "<stdlib.h", "cstdlib>", "b.h", "<c.h>", "<d>"}; |
| } |
| }; |
| |
| class ObjCEarlyInAlphabetHeaderInserterCheck : public IncludeInserterCheckBase { |
| public: |
| ObjCEarlyInAlphabetHeaderInserterCheck(StringRef CheckName, |
| ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context, |
| utils::IncludeSorter::IS_Google_ObjC) {} |
| |
| std::vector<StringRef> headersToInclude() const override { |
| return {"a/header.h"}; |
| } |
| }; |
| |
| class ObjCCategoryHeaderInserterCheck : public IncludeInserterCheckBase { |
| public: |
| ObjCCategoryHeaderInserterCheck(StringRef CheckName, |
| ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context, |
| utils::IncludeSorter::IS_Google_ObjC) {} |
| |
| std::vector<StringRef> headersToInclude() const override { |
| return {"top_level_test_header+foo.h"}; |
| } |
| }; |
| |
| class ObjCGeneratedHeaderInserterCheck : public IncludeInserterCheckBase { |
| public: |
| ObjCGeneratedHeaderInserterCheck(StringRef CheckName, |
| ClangTidyContext *Context) |
| : IncludeInserterCheckBase(CheckName, Context, |
| utils::IncludeSorter::IS_Google_ObjC) {} |
| |
| std::vector<StringRef> headersToInclude() const override { |
| return {"clang_tidy/tests/generated_file.proto.h"}; |
| } |
| }; |
| |
| template <typename Check> |
| std::string runCheckOnCode(StringRef Code, StringRef Filename) { |
| std::vector<ClangTidyError> Errors; |
| return test::runCheckOnCode<Check>(Code, &Errors, Filename, None, |
| ClangTidyOptions(), |
| {// Main file include |
| {"clang_tidy/tests/" |
| "insert_includes_test_header.h", |
| "\n"}, |
| // Top-level main file include + |
| // category. |
| {"top_level_test_header.h", "\n"}, |
| {"top_level_test_header+foo.h", "\n"}, |
| // ObjC category. |
| {"clang_tidy/tests/" |
| "insert_includes_test_header+foo.h", |
| "\n"}, |
| // Non system headers |
| {"a/header.h", "\n"}, |
| {"path/to/a/header.h", "\n"}, |
| {"path/to/z/header.h", "\n"}, |
| {"path/to/header.h", "\n"}, |
| {"path/to/header2.h", "\n"}, |
| // Generated headers |
| {"clang_tidy/tests/" |
| "generated_file.proto.h", |
| "\n"}, |
| // Fake system headers. |
| {"stdlib.h", "\n"}, |
| {"unistd.h", "\n"}, |
| {"list", "\n"}, |
| {"map", "\n"}, |
| {"set", "\n"}, |
| {"vector", "\n"}}); |
| } |
| |
| TEST(IncludeInserterTest, InsertAfterLastNonSystemInclude) { |
| const char *PreCode = R"( |
| #include "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 "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, "clang_tidy/tests/insert_includes_test_input2.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertMultipleIncludesAndDeduplicate) { |
| const char *PreCode = R"( |
| #include "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 "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/header2.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ(PostCode, |
| runCheckOnCode<MultipleHeaderInserterCheck>( |
| 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" |
| |
| #include <list> |
| #include <map> |
| |
| #include "path/to/z/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"( |
| #include "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, "clang_tidy/tests/insert_includes_test_input2.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertBetweenNonSystemIncludes) { |
| const char *PreCode = R"( |
| #include "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 "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, "clang_tidy/tests/insert_includes_test_input2.cc")); |
| } |
| |
| TEST(IncludeInserterTest, NonSystemIncludeAlreadyIncluded) { |
| const char *PreCode = R"( |
| #include "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, "clang_tidy/tests/insert_includes_test_input2.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertNonSystemIncludeAfterLastCXXSystemInclude) { |
| const char *PreCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include <list> |
| #include <map> |
| |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"( |
| #include "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, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertNonSystemIncludeAfterMainFileInclude) { |
| const char *PreCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include "path/to/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ(PostCode, |
| runCheckOnCode<NonSystemHeaderInserterCheck>( |
| PreCode, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterLastCXXSystemInclude) { |
| const char *PreCode = R"( |
| #include "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 "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, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCXXSystemIncludeBeforeFirstCXXSystemInclude) { |
| const char *PreCode = R"( |
| #include "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 "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, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCXXSystemIncludeBetweenCXXSystemIncludes) { |
| const char *PreCode = R"( |
| #include "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 "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, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterMainFileInclude) { |
| const char *PreCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #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 <set> |
| |
| #include "path/to/a/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ(PostCode, |
| runCheckOnCode<CXXSystemIncludeInserterCheck>( |
| PreCode, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterCSystemInclude) { |
| const char *PreCode = R"( |
| #include "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 "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, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCXXSystemIncludeBeforeNonSystemInclude) { |
| const char *PreCode = R"( |
| #include "path/to/a/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"( |
| #include <set> |
| |
| #include "path/to/a/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ( |
| PostCode, |
| runCheckOnCode<CXXSystemIncludeInserterCheck>( |
| PreCode, "repo/clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCSystemIncludeBeforeCXXSystemInclude) { |
| const char *PreCode = R"( |
| #include <set> |
| |
| #include "path/to/a/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"( |
| #include <stdlib.h> |
| |
| #include <set> |
| |
| #include "path/to/a/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ( |
| PostCode, |
| runCheckOnCode<CSystemIncludeInserterCheck>( |
| PreCode, "repo/clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertIncludeIfThereWasNoneBefore) { |
| const char *PreCode = R"( |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"(#include <set> |
| |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ( |
| PostCode, |
| runCheckOnCode<CXXSystemIncludeInserterCheck>( |
| PreCode, "repo/clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, DontInsertDuplicateIncludeEvenIfMiscategorized) { |
| const char *PreCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include <map> |
| #include <set> |
| #include <vector> |
| |
| #include "a/header.h" |
| #include "path/to/a/header.h" |
| #include "path/to/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| const char *PostCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include <map> |
| #include <set> |
| #include <vector> |
| |
| #include "a/header.h" |
| #include "path/to/a/header.h" |
| #include "path/to/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ(PostCode, runCheckOnCode<EarlyInAlphabetHeaderInserterCheck>( |
| PreCode, "workspace_folder/clang_tidy/tests/" |
| "insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, HandleOrderInSubdirectory) { |
| const char *PreCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include <map> |
| #include <set> |
| #include <vector> |
| |
| #include "path/to/a/header.h" |
| #include "path/to/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| const char *PostCode = R"( |
| #include "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include <map> |
| #include <set> |
| #include <vector> |
| |
| #include "a/header.h" |
| #include "path/to/a/header.h" |
| #include "path/to/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ(PostCode, runCheckOnCode<EarlyInAlphabetHeaderInserterCheck>( |
| PreCode, "workspace_folder/clang_tidy/tests/" |
| "insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InvalidHeaderName) { |
| const char *PreCode = R"( |
| #include "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 "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include <c.h> |
| |
| #include <d> |
| #include <list> |
| #include <map> |
| |
| #include "a.h" |
| #include "b.h" |
| #include "path/to/a/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ(PostCode, |
| runCheckOnCode<InvalidIncludeInserterCheck>( |
| PreCode, "clang_tidy/tests/insert_includes_test_header.cc")); |
| } |
| |
| TEST(IncludeInserterTest, InsertHeaderObjectiveC) { |
| const char *PreCode = R"( |
| #import "clang_tidy/tests/insert_includes_test_header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"( |
| #import "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #import "a/header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ( |
| PostCode, |
| runCheckOnCode<ObjCEarlyInAlphabetHeaderInserterCheck>( |
| PreCode, "repo/clang_tidy/tests/insert_includes_test_header.mm")); |
| } |
| |
| TEST(IncludeInserterTest, InsertCategoryHeaderObjectiveC) { |
| const char *PreCode = R"( |
| #import "top_level_test_header.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| const char *PostCode = R"( |
| #import "top_level_test_header.h" |
| #import "top_level_test_header+foo.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ(PostCode, runCheckOnCode<ObjCCategoryHeaderInserterCheck>( |
| PreCode, "top_level_test_header.mm")); |
| } |
| |
| TEST(IncludeInserterTest, InsertGeneratedHeaderObjectiveC) { |
| const char *PreCode = R"( |
| #import "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"( |
| #import "clang_tidy/tests/insert_includes_test_header.h" |
| |
| #include <list> |
| #include <map> |
| |
| #include "path/to/a/header.h" |
| |
| #import "clang_tidy/tests/generated_file.proto.h" |
| |
| void foo() { |
| int a = 0; |
| })"; |
| |
| EXPECT_EQ( |
| PostCode, |
| runCheckOnCode<ObjCGeneratedHeaderInserterCheck>( |
| PreCode, "repo/clang_tidy/tests/insert_includes_test_header.mm")); |
| } |
| |
| } // anonymous namespace |
| } // namespace tidy |
| } // namespace clang |
| |
| #endif |