1 //===--- IncludeCleanerTest.cpp - clang-tidy -----------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "ClangTidyDiagnosticConsumer.h" 10 #include "ClangTidyOptions.h" 11 #include "ClangTidyTest.h" 12 #include "misc/IncludeCleanerCheck.h" 13 #include "llvm/ADT/SmallString.h" 14 #include "llvm/ADT/StringRef.h" 15 #include "llvm/Support/FormatVariadic.h" 16 #include "llvm/Support/Path.h" 17 #include "llvm/Support/Regex.h" 18 #include "llvm/Testing/Annotations/Annotations.h" 19 #include "gmock/gmock.h" 20 #include "gtest/gtest.h" 21 #include <initializer_list> 22 23 #include <optional> 24 #include <vector> 25 26 using namespace clang::tidy::misc; 27 28 namespace clang { 29 namespace tidy { 30 namespace test { 31 namespace { 32 33 std::string 34 appendPathFileSystemIndependent(std::initializer_list<std::string> Segments) { 35 llvm::SmallString<32> Result; 36 for (const auto &Segment : Segments) 37 llvm::sys::path::append(Result, llvm::sys::path::Style::native, Segment); 38 return std::string(Result.str()); 39 } 40 41 TEST(IncludeCleanerCheckTest, BasicUnusedIncludes) { 42 const char *PreCode = R"( 43 #include "bar.h" 44 #include <vector> 45 #include "bar.h" 46 )"; 47 const char *PostCode = "\n"; 48 49 std::vector<ClangTidyError> Errors; 50 EXPECT_EQ(PostCode, 51 runCheckOnCode<IncludeCleanerCheck>( 52 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(), 53 {{"bar.h", "#pragma once"}, {"vector", "#pragma once"}})); 54 } 55 56 TEST(IncludeCleanerCheckTest, SuppressUnusedIncludes) { 57 const char *PreCode = R"( 58 #include "bar.h" 59 #include "foo/qux.h" 60 #include "baz/qux/qux.h" 61 #include <vector> 62 #include <list> 63 )"; 64 65 const char *PostCode = R"( 66 #include "bar.h" 67 #include "foo/qux.h" 68 #include <vector> 69 #include <list> 70 )"; 71 72 std::vector<ClangTidyError> Errors; 73 ClangTidyOptions Opts; 74 Opts.CheckOptions["test-check-0.IgnoreHeaders"] = llvm::StringRef{ 75 llvm::formatv("bar.h;{0};{1};vector;<list>;", 76 llvm::Regex::escape( 77 appendPathFileSystemIndependent({"foo", "qux.h"})), 78 llvm::Regex::escape( 79 appendPathFileSystemIndependent({"baz", "qux"})))}; 80 EXPECT_EQ( 81 PostCode, 82 runCheckOnCode<IncludeCleanerCheck>( 83 PreCode, &Errors, "file.cpp", {}, Opts, 84 {{"bar.h", "#pragma once"}, 85 {"vector", "#pragma once"}, 86 {"list", "#pragma once"}, 87 {appendPathFileSystemIndependent({"foo", "qux.h"}), "#pragma once"}, 88 {appendPathFileSystemIndependent({"baz", "qux", "qux.h"}), 89 "#pragma once"}})); 90 } 91 92 TEST(IncludeCleanerCheckTest, BasicMissingIncludes) { 93 const char *PreCode = R"( 94 #include "bar.h" 95 96 int BarResult = bar(); 97 int BazResult = baz(); 98 )"; 99 const char *PostCode = R"( 100 #include "bar.h" 101 #include "baz.h" 102 103 int BarResult = bar(); 104 int BazResult = baz(); 105 )"; 106 107 std::vector<ClangTidyError> Errors; 108 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>( 109 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(), 110 {{"bar.h", R"(#pragma once 111 #include "baz.h" 112 int bar(); 113 )"}, 114 {"baz.h", R"(#pragma once 115 int baz(); 116 )"}})); 117 } 118 119 TEST(IncludeCleanerCheckTest, DedupsMissingIncludes) { 120 llvm::Annotations Code(R"( 121 #include "baz.h" // IWYU pragma: keep 122 123 int BarResult1 = $diag1^bar(); 124 int BarResult2 = $diag2^bar();)"); 125 126 { 127 std::vector<ClangTidyError> Errors; 128 runCheckOnCode<IncludeCleanerCheck>(Code.code(), &Errors, "file.cpp", {}, 129 ClangTidyOptions(), 130 {{"baz.h", R"(#pragma once 131 #include "bar.h" 132 )"}, 133 {"bar.h", R"(#pragma once 134 int bar(); 135 )"}}); 136 ASSERT_THAT(Errors.size(), testing::Eq(1U)); 137 EXPECT_EQ(Errors.front().Message.Message, 138 "no header providing \"bar\" is directly included"); 139 EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1")); 140 } 141 { 142 std::vector<ClangTidyError> Errors; 143 ClangTidyOptions Opts; 144 Opts.CheckOptions["test-check-0.DeduplicateFindings"] = "false"; 145 runCheckOnCode<IncludeCleanerCheck>(Code.code(), &Errors, "file.cpp", {}, 146 Opts, 147 {{"baz.h", R"(#pragma once 148 #include "bar.h" 149 )"}, 150 {"bar.h", R"(#pragma once 151 int bar(); 152 )"}}); 153 ASSERT_THAT(Errors.size(), testing::Eq(2U)); 154 EXPECT_EQ(Errors.front().Message.Message, 155 "no header providing \"bar\" is directly included"); 156 EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1")); 157 EXPECT_EQ(Errors.back().Message.Message, 158 "no header providing \"bar\" is directly included"); 159 EXPECT_EQ(Errors.back().Message.FileOffset, Code.point("diag2")); 160 } 161 } 162 163 TEST(IncludeCleanerCheckTest, SuppressMissingIncludes) { 164 const char *PreCode = R"( 165 #include "bar.h" 166 167 int BarResult = bar(); 168 int BazResult = baz(); 169 int QuxResult = qux(); 170 int PrivResult = test(); 171 std::vector x; 172 )"; 173 174 ClangTidyOptions Opts; 175 Opts.CheckOptions["test-check-0.IgnoreHeaders"] = llvm::StringRef{ 176 "public.h;<vector>;baz.h;" + 177 llvm::Regex::escape(appendPathFileSystemIndependent({"foo", "qux.h"}))}; 178 std::vector<ClangTidyError> Errors; 179 EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>( 180 PreCode, &Errors, "file.cpp", {}, Opts, 181 {{"bar.h", R"(#pragma once 182 #include "baz.h" 183 #include "foo/qux.h" 184 #include "private.h" 185 int bar(); 186 namespace std { struct vector {}; } 187 )"}, 188 {"baz.h", R"(#pragma once 189 int baz(); 190 )"}, 191 {"private.h", R"(#pragma once 192 // IWYU pragma: private, include "public.h" 193 int test(); 194 )"}, 195 {appendPathFileSystemIndependent({"foo", "qux.h"}), 196 R"(#pragma once 197 int qux(); 198 )"}})); 199 } 200 201 TEST(IncludeCleanerCheckTest, MultipleTimeMissingInclude) { 202 const char *PreCode = R"( 203 #include "bar.h" 204 205 int BarResult = bar(); 206 int BazResult_0 = baz_0(); 207 int BazResult_1 = baz_1(); 208 )"; 209 const char *PostCode = R"( 210 #include "bar.h" 211 #include "baz.h" 212 213 int BarResult = bar(); 214 int BazResult_0 = baz_0(); 215 int BazResult_1 = baz_1(); 216 )"; 217 218 std::vector<ClangTidyError> Errors; 219 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>( 220 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(), 221 {{"bar.h", R"(#pragma once 222 #include "baz.h" 223 int bar(); 224 )"}, 225 {"baz.h", R"(#pragma once 226 int baz_0(); 227 int baz_1(); 228 )"}})); 229 } 230 231 TEST(IncludeCleanerCheckTest, SystemMissingIncludes) { 232 const char *PreCode = R"( 233 #include <vector> 234 235 std::string HelloString; 236 std::vector Vec; 237 )"; 238 const char *PostCode = R"( 239 #include <string> 240 #include <vector> 241 242 std::string HelloString; 243 std::vector Vec; 244 )"; 245 246 std::vector<ClangTidyError> Errors; 247 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>( 248 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(), 249 {{"string", R"(#pragma once 250 namespace std { class string {}; } 251 )"}, 252 {"vector", R"(#pragma once 253 #include <string> 254 namespace std { class vector {}; } 255 )"}})); 256 } 257 258 TEST(IncludeCleanerCheckTest, PragmaMissingIncludes) { 259 const char *PreCode = R"( 260 #include "bar.h" 261 262 int BarResult = bar(); 263 int FooBarResult = foobar(); 264 )"; 265 const char *PostCode = R"( 266 #include "bar.h" 267 #include "public.h" 268 269 int BarResult = bar(); 270 int FooBarResult = foobar(); 271 )"; 272 273 std::vector<ClangTidyError> Errors; 274 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>( 275 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(), 276 {{"bar.h", R"(#pragma once 277 #include "private.h" 278 int bar(); 279 )"}, 280 {"private.h", R"(#pragma once 281 // IWYU pragma: private, include "public.h" 282 int foobar(); 283 )"}})); 284 } 285 286 TEST(IncludeCleanerCheckTest, DeclFromMacroExpansion) { 287 const char *PreCode = R"( 288 #include "foo.h" 289 290 DECLARE(myfunc) { 291 int a; 292 } 293 )"; 294 295 std::vector<ClangTidyError> Errors; 296 EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>( 297 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(), 298 {{"foo.h", 299 R"(#pragma once 300 #define DECLARE(X) void X() 301 )"}})); 302 303 PreCode = R"( 304 #include "foo.h" 305 306 DECLARE { 307 int a; 308 } 309 )"; 310 311 EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>( 312 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(), 313 {{"foo.h", 314 R"(#pragma once 315 #define DECLARE void myfunc() 316 )"}})); 317 } 318 319 } // namespace 320 } // namespace test 321 } // namespace tidy 322 } // namespace clang 323