1 //===- unittest/Format/CleanupTest.cpp - Code cleanup unit tests ----------===// 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 "clang/Format/Format.h" 10 11 #include "../Tooling/ReplacementTest.h" 12 #include "../Tooling/RewriterTestContext.h" 13 #include "clang/Tooling/Core/Replacement.h" 14 15 #include "gtest/gtest.h" 16 17 using clang::tooling::ReplacementTest; 18 using clang::tooling::toReplacements; 19 20 namespace clang { 21 namespace format { 22 namespace { 23 24 class CleanupTest : public ::testing::Test { 25 protected: 26 std::string cleanup(llvm::StringRef Code, 27 const std::vector<tooling::Range> &Ranges, 28 const FormatStyle &Style = getLLVMStyle()) { 29 tooling::Replacements Replaces = format::cleanup(Style, Code, Ranges); 30 31 auto Result = applyAllReplacements(Code, Replaces); 32 EXPECT_TRUE(static_cast<bool>(Result)); 33 return *Result; 34 } 35 36 // Returns code after cleanup around \p Offsets. 37 std::string cleanupAroundOffsets(llvm::ArrayRef<unsigned> Offsets, 38 llvm::StringRef Code, 39 const FormatStyle &Style = getLLVMStyle()) { 40 std::vector<tooling::Range> Ranges; 41 for (auto Offset : Offsets) 42 Ranges.push_back(tooling::Range(Offset, 0)); 43 return cleanup(Code, Ranges, Style); 44 } 45 }; 46 47 TEST_F(CleanupTest, DeleteEmptyNamespaces) { 48 std::string Code = "namespace A {\n" 49 "namespace B {\n" 50 "} // namespace B\n" 51 "} // namespace A\n\n" 52 "namespace C {\n" 53 "namespace D { int i; }\n" 54 "inline namespace E { namespace { } }\n" 55 "}"; 56 std::string Expected = "\n\n\n\n\nnamespace C {\n" 57 "namespace D { int i; }\n \n" 58 "}"; 59 EXPECT_EQ(Expected, cleanupAroundOffsets({28, 91, 132}, Code)); 60 } 61 62 TEST_F(CleanupTest, NamespaceWithSyntaxError) { 63 std::string Code = "namespace A {\n" 64 "namespace B {\n" // missing r_brace 65 "} // namespace A\n\n" 66 "namespace C {\n" 67 "namespace D int i; }\n" 68 "inline namespace E { namespace { } }\n" 69 "}"; 70 std::string Expected = "namespace A {\n" 71 "\n\n\nnamespace C {\n" 72 "namespace D int i; }\n \n" 73 "}"; 74 std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size())); 75 EXPECT_EQ(Expected, cleanup(Code, Ranges)); 76 } 77 78 TEST_F(CleanupTest, EmptyNamespaceNotAffected) { 79 std::string Code = "namespace A {\n\n" 80 "namespace {\n\n}}"; 81 // Even though the namespaces are empty, but the inner most empty namespace 82 // block is not affected by the changed ranges. 83 std::string Expected = "namespace A {\n\n" 84 "namespace {\n\n}}"; 85 // Set the changed range to be the second "\n". 86 EXPECT_EQ(Expected, cleanupAroundOffsets({14}, Code)); 87 } 88 89 TEST_F(CleanupTest, EmptyNamespaceWithCommentsNoBreakBeforeBrace) { 90 std::string Code = "namespace A {\n" 91 "namespace B {\n" 92 "// Yo\n" 93 "} // namespace B\n" 94 "} // namespace A\n" 95 "namespace C { // Yo\n" 96 "}"; 97 std::string Expected = "\n\n\n\n\n\n"; 98 std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size())); 99 std::string Result = cleanup(Code, Ranges); 100 EXPECT_EQ(Expected, Result); 101 } 102 103 TEST_F(CleanupTest, EmptyNamespaceWithCommentsBreakBeforeBrace) { 104 std::string Code = "namespace A\n" 105 "/* Yo */ {\n" 106 "namespace B\n" 107 "{\n" 108 "// Yo\n" 109 "} // namespace B\n" 110 "} // namespace A\n" 111 "namespace C\n" 112 "{ // Yo\n" 113 "}\n"; 114 std::string Expected = "\n\n\n\n\n\n\n\n\n\n"; 115 std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size())); 116 FormatStyle Style = getLLVMStyle(); 117 Style.BraceWrapping.AfterNamespace = true; 118 std::string Result = cleanup(Code, Ranges, Style); 119 EXPECT_EQ(Expected, Result); 120 } 121 122 TEST_F(CleanupTest, EmptyNamespaceAroundConditionalCompilation) { 123 std::string Code = "#ifdef A\n" 124 "int a;\n" 125 "int b;\n" 126 "#else\n" 127 "#endif\n" 128 "namespace {}"; 129 std::string Expected = "#ifdef A\n" 130 "int a;\n" 131 "int b;\n" 132 "#else\n" 133 "#endif\n"; 134 std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size())); 135 FormatStyle Style = getLLVMStyle(); 136 std::string Result = cleanup(Code, Ranges, Style); 137 EXPECT_EQ(Expected, Result); 138 } 139 140 TEST_F(CleanupTest, CtorInitializationSimpleRedundantComma) { 141 std::string Code = "class A {\nA() : , {} };"; 142 std::string Expected = "class A {\nA() {} };"; 143 EXPECT_EQ(Expected, cleanupAroundOffsets({17, 19}, Code)); 144 145 Code = "class A {\nA() : x(1), {} };"; 146 Expected = "class A {\nA() : x(1) {} };"; 147 EXPECT_EQ(Expected, cleanupAroundOffsets({23}, Code)); 148 149 Code = "class A {\nA() :,,,,{} };"; 150 Expected = "class A {\nA() {} };"; 151 EXPECT_EQ(Expected, cleanupAroundOffsets({15}, Code)); 152 } 153 154 TEST_F(CleanupTest, CtorInitializationSimpleRedundantColon) { 155 std::string Code = "class A {\nA() : =default; };"; 156 std::string Expected = "class A {\nA() =default; };"; 157 EXPECT_EQ(Expected, cleanupAroundOffsets({15}, Code)); 158 159 Code = "class A {\nA() : , =default; };"; 160 Expected = "class A {\nA() =default; };"; 161 EXPECT_EQ(Expected, cleanupAroundOffsets({15}, Code)); 162 } 163 164 TEST_F(CleanupTest, ListRedundantComma) { 165 std::string Code = "void f() { std::vector<int> v = {1,2,,,3,{4,5}}; }"; 166 std::string Expected = "void f() { std::vector<int> v = {1,2,3,{4,5}}; }"; 167 EXPECT_EQ(Expected, cleanupAroundOffsets({40}, Code)); 168 169 Code = "int main() { f(1,,2,3,,4);}"; 170 Expected = "int main() { f(1,2,3,4);}"; 171 EXPECT_EQ(Expected, cleanupAroundOffsets({17, 22}, Code)); 172 } 173 174 TEST_F(CleanupTest, NoCleanupsForJavaScript) { 175 std::string Code = "function f() { var x = [a, b, , c]; }"; 176 std::string Expected = "function f() { var x = [a, b, , c]; }"; 177 const FormatStyle &Style = getGoogleStyle(FormatStyle::LK_JavaScript); 178 179 EXPECT_EQ(Expected, cleanupAroundOffsets({30}, Code, Style)); 180 } 181 182 TEST_F(CleanupTest, TrailingCommaInParens) { 183 std::string Code = "int main() { f(,1,,2,3,f(1,2,),4,,);}"; 184 std::string Expected = "int main() { f(1,2,3,f(1,2),4);}"; 185 EXPECT_EQ(Expected, cleanupAroundOffsets({15, 18, 29, 33}, Code)); 186 } 187 188 TEST_F(CleanupTest, TrailingCommaInBraces) { 189 // Trainling comma is allowed in brace list. 190 // If there was trailing comma in the original code, then trailing comma is 191 // preserved. In this example, element between the last two commas is deleted 192 // causing the second-last comma to be redundant. 193 std::string Code = "void f() { std::vector<int> v = {1,2,3,,}; }"; 194 std::string Expected = "void f() { std::vector<int> v = {1,2,3,}; }"; 195 EXPECT_EQ(Expected, cleanupAroundOffsets({39}, Code)); 196 197 // If there was no trailing comma in the original code, then trainling comma 198 // introduced by replacements should be cleaned up. In this example, the 199 // element after the last comma is deleted causing the last comma to be 200 // redundant. 201 Code = "void f() { std::vector<int> v = {1,2,3,}; }"; 202 // FIXME: redundant trailing comma should be removed. 203 Expected = "void f() { std::vector<int> v = {1,2,3,}; }"; 204 EXPECT_EQ(Expected, cleanupAroundOffsets({39}, Code)); 205 206 // Still no trailing comma in the original code, but two elements are deleted, 207 // which makes it seems like there was trailing comma. 208 Code = "void f() { std::vector<int> v = {1, 2, 3, , }; }"; 209 // FIXME: redundant trailing comma should also be removed. 210 Expected = "void f() { std::vector<int> v = {1, 2, 3, }; }"; 211 EXPECT_EQ(Expected, cleanupAroundOffsets({42, 44}, Code)); 212 } 213 214 TEST_F(CleanupTest, CtorInitializationBracesInParens) { 215 std::string Code = "class A {\nA() : x({1}),, {} };"; 216 std::string Expected = "class A {\nA() : x({1}) {} };"; 217 EXPECT_EQ(Expected, cleanupAroundOffsets({24, 26}, Code)); 218 } 219 220 TEST_F(CleanupTest, RedundantCommaNotInAffectedRanges) { 221 std::string Code = 222 "class A {\nA() : x({1}), /* comment */, { int x = 0; } };"; 223 std::string Expected = 224 "class A {\nA() : x({1}), /* comment */, { int x = 0; } };"; 225 // Set the affected range to be "int x = 0", which does not intercept the 226 // constructor initialization list. 227 std::vector<tooling::Range> Ranges(1, tooling::Range(42, 9)); 228 std::string Result = cleanup(Code, Ranges); 229 EXPECT_EQ(Expected, Result); 230 231 Code = "class A {\nA() : x(1), {} };"; 232 Expected = "class A {\nA() : x(1), {} };"; 233 // No range. Fixer should do nothing. 234 Ranges.clear(); 235 Result = cleanup(Code, Ranges); 236 EXPECT_EQ(Expected, Result); 237 } 238 239 TEST_F(CleanupTest, RemoveCommentsAroundDeleteCode) { 240 std::string Code = 241 "class A {\nA() : x({1}), /* comment */, /* comment */ {} };"; 242 std::string Expected = "class A {\nA() : x({1}) {} };"; 243 EXPECT_EQ(Expected, cleanupAroundOffsets({25, 40}, Code)); 244 245 Code = "class A {\nA() : x({1}), // comment\n {} };"; 246 Expected = "class A {\nA() : x({1})\n {} };"; 247 EXPECT_EQ(Expected, cleanupAroundOffsets({25}, Code)); 248 249 Code = "class A {\nA() : x({1}), // comment\n , y(1),{} };"; 250 Expected = "class A {\nA() : x({1}), y(1){} };"; 251 EXPECT_EQ(Expected, cleanupAroundOffsets({38}, Code)); 252 253 Code = "class A {\nA() : x({1}), \n/* comment */, y(1),{} };"; 254 Expected = "class A {\nA() : x({1}), \n y(1){} };"; 255 EXPECT_EQ(Expected, cleanupAroundOffsets({40}, Code)); 256 257 Code = "class A {\nA() : , // comment\n y(1),{} };"; 258 Expected = "class A {\nA() : // comment\n y(1){} };"; 259 EXPECT_EQ(Expected, cleanupAroundOffsets({17}, Code)); 260 261 Code = "class A {\nA() // comment\n : ,,{} };"; 262 Expected = "class A {\nA() // comment\n {} };"; 263 EXPECT_EQ(Expected, cleanupAroundOffsets({30}, Code)); 264 265 Code = "class A {\nA() // comment\n : ,,=default; };"; 266 Expected = "class A {\nA() // comment\n =default; };"; 267 EXPECT_EQ(Expected, cleanupAroundOffsets({30}, Code)); 268 } 269 270 TEST_F(CleanupTest, CtorInitializerInNamespace) { 271 std::string Code = "namespace A {\n" 272 "namespace B {\n" // missing r_brace 273 "} // namespace A\n\n" 274 "namespace C {\n" 275 "class A { A() : x(0),, {} };\n" 276 "inline namespace E { namespace { } }\n" 277 "}"; 278 std::string Expected = "namespace A {\n" 279 "\n\n\nnamespace C {\n" 280 "class A { A() : x(0) {} };\n \n" 281 "}"; 282 std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size())); 283 std::string Result = cleanup(Code, Ranges); 284 EXPECT_EQ(Expected, Result); 285 } 286 287 class CleanUpReplacementsTest : public ReplacementTest { 288 protected: 289 tooling::Replacement createReplacement(unsigned Offset, unsigned Length, 290 StringRef Text) { 291 return tooling::Replacement(FileName, Offset, Length, Text); 292 } 293 294 tooling::Replacement createInsertion(StringRef IncludeDirective) { 295 return createReplacement(UINT_MAX, 0, IncludeDirective); 296 } 297 298 tooling::Replacement createDeletion(StringRef HeaderName) { 299 return createReplacement(UINT_MAX, 1, HeaderName); 300 } 301 302 inline std::string apply(StringRef Code, 303 const tooling::Replacements &Replaces) { 304 auto CleanReplaces = cleanupAroundReplacements(Code, Replaces, Style); 305 EXPECT_TRUE(static_cast<bool>(CleanReplaces)) 306 << llvm::toString(CleanReplaces.takeError()) << "\n"; 307 auto Result = applyAllReplacements(Code, *CleanReplaces); 308 EXPECT_TRUE(static_cast<bool>(Result)); 309 return *Result; 310 } 311 312 inline std::string formatAndApply(StringRef Code, 313 const tooling::Replacements &Replaces) { 314 auto CleanReplaces = cleanupAroundReplacements(Code, Replaces, Style); 315 EXPECT_TRUE(static_cast<bool>(CleanReplaces)) 316 << llvm::toString(CleanReplaces.takeError()) << "\n"; 317 auto FormattedReplaces = formatReplacements(Code, *CleanReplaces, Style); 318 EXPECT_TRUE(static_cast<bool>(FormattedReplaces)) 319 << llvm::toString(FormattedReplaces.takeError()) << "\n"; 320 auto Result = applyAllReplacements(Code, *FormattedReplaces); 321 EXPECT_TRUE(static_cast<bool>(Result)); 322 return *Result; 323 } 324 325 int getOffset(StringRef Code, int Line, int Column) { 326 RewriterTestContext Context; 327 FileID ID = Context.createInMemoryFile(FileName, Code); 328 auto DecomposedLocation = 329 Context.Sources.getDecomposedLoc(Context.getLocation(ID, Line, Column)); 330 return DecomposedLocation.second; 331 } 332 333 const std::string FileName = "fix.cpp"; 334 FormatStyle Style = getLLVMStyle(); 335 }; 336 337 TEST_F(CleanUpReplacementsTest, FixOnlyAffectedCodeAfterReplacements) { 338 std::string Code = "namespace A {\n" 339 "namespace B {\n" 340 " int x;\n" 341 "} // namespace B\n" 342 "} // namespace A\n" 343 "\n" 344 "namespace C {\n" 345 "namespace D { int i; }\n" 346 "inline namespace E { namespace { int y; } }\n" 347 "int x= 0;" 348 "}"; 349 std::string Expected = "\n\nnamespace C {\n" 350 "namespace D { int i; }\n\n" 351 "int x= 0;" 352 "}"; 353 tooling::Replacements Replaces = 354 toReplacements({createReplacement(getOffset(Code, 3, 3), 6, ""), 355 createReplacement(getOffset(Code, 9, 34), 6, "")}); 356 357 EXPECT_EQ(Expected, formatAndApply(Code, Replaces)); 358 } 359 360 TEST_F(CleanUpReplacementsTest, InsertMultipleIncludesLLVMStyle) { 361 std::string Code = "#include \"x/fix.h\"\n" 362 "#include \"a.h\"\n" 363 "#include \"b.h\"\n" 364 "#include \"z.h\"\n" 365 "#include \"clang/Format/Format.h\"\n" 366 "#include <memory>\n"; 367 std::string Expected = "#include \"x/fix.h\"\n" 368 "#include \"a.h\"\n" 369 "#include \"b.h\"\n" 370 "#include \"new/new.h\"\n" 371 "#include \"z.h\"\n" 372 "#include \"clang/Format/Format.h\"\n" 373 "#include <list>\n" 374 "#include <memory>\n"; 375 tooling::Replacements Replaces = 376 toReplacements({createInsertion("#include <list>"), 377 createInsertion("#include \"new/new.h\"")}); 378 EXPECT_EQ(Expected, apply(Code, Replaces)); 379 } 380 381 TEST_F(CleanUpReplacementsTest, InsertMultipleIncludesGoogleStyle) { 382 std::string Code = "#include \"x/fix.h\"\n" 383 "\n" 384 "#include <vector>\n" 385 "\n" 386 "#include \"y/a.h\"\n" 387 "#include \"z/b.h\"\n"; 388 std::string Expected = "#include \"x/fix.h\"\n" 389 "\n" 390 "#include <list>\n" 391 "#include <vector>\n" 392 "\n" 393 "#include \"x/x.h\"\n" 394 "#include \"y/a.h\"\n" 395 "#include \"z/b.h\"\n"; 396 tooling::Replacements Replaces = 397 toReplacements({createInsertion("#include <list>"), 398 createInsertion("#include \"x/x.h\"")}); 399 Style = format::getGoogleStyle(format::FormatStyle::LanguageKind::LK_Cpp); 400 EXPECT_EQ(Expected, apply(Code, Replaces)); 401 } 402 403 TEST_F(CleanUpReplacementsTest, InsertMultipleNewHeadersAndSortLLVM) { 404 std::string Code = "\nint x;"; 405 std::string Expected = "\n#include \"fix.h\"\n" 406 "#include \"a.h\"\n" 407 "#include \"b.h\"\n" 408 "#include \"c.h\"\n" 409 "#include <list>\n" 410 "#include <vector>\n" 411 "int x;"; 412 tooling::Replacements Replaces = toReplacements( 413 {createInsertion("#include \"a.h\""), createInsertion("#include \"c.h\""), 414 createInsertion("#include \"b.h\""), 415 createInsertion("#include <vector>"), createInsertion("#include <list>"), 416 createInsertion("#include \"fix.h\"")}); 417 EXPECT_EQ(Expected, formatAndApply(Code, Replaces)); 418 } 419 420 TEST_F(CleanUpReplacementsTest, InsertMultipleNewHeadersAndSortGoogle) { 421 std::string Code = "\nint x;"; 422 std::string Expected = "\n#include \"fix.h\"\n" 423 "\n" 424 "#include <list>\n" 425 "#include <vector>\n" 426 "\n" 427 "#include \"a.h\"\n" 428 "#include \"b.h\"\n" 429 "#include \"c.h\"\n" 430 "int x;"; 431 tooling::Replacements Replaces = toReplacements( 432 {createInsertion("#include \"a.h\""), createInsertion("#include \"c.h\""), 433 createInsertion("#include \"b.h\""), 434 createInsertion("#include <vector>"), createInsertion("#include <list>"), 435 createInsertion("#include \"fix.h\"")}); 436 Style = format::getGoogleStyle(format::FormatStyle::LanguageKind::LK_Cpp); 437 EXPECT_EQ(Expected, formatAndApply(Code, Replaces)); 438 } 439 440 TEST_F(CleanUpReplacementsTest, NoNewLineAtTheEndOfCodeMultipleInsertions) { 441 std::string Code = "#include <map>"; 442 // FIXME: a better behavior is to only append on newline to Code, but this 443 // case should be rare in practice. 444 std::string Expected = 445 "#include <map>\n#include <string>\n\n#include <vector>\n"; 446 tooling::Replacements Replaces = 447 toReplacements({createInsertion("#include <string>"), 448 createInsertion("#include <vector>")}); 449 EXPECT_EQ(Expected, apply(Code, Replaces)); 450 } 451 452 453 TEST_F(CleanUpReplacementsTest, FormatCorrectLineWhenHeadersAreInserted) { 454 std::string Code = "\n" 455 "int x;\n" 456 "int a;\n" 457 "int a;\n" 458 "int a;"; 459 460 std::string Expected = "\n#include \"x.h\"\n" 461 "#include \"y.h\"\n" 462 "#include \"clang/x/x.h\"\n" 463 "#include <list>\n" 464 "#include <vector>\n" 465 "int x;\n" 466 "int a;\n" 467 "int b;\n" 468 "int a;"; 469 tooling::Replacements Replaces = toReplacements( 470 {createReplacement(getOffset(Code, 4, 8), 1, "b"), 471 createInsertion("#include <vector>"), createInsertion("#include <list>"), 472 createInsertion("#include \"clang/x/x.h\""), 473 createInsertion("#include \"y.h\""), 474 createInsertion("#include \"x.h\"")}); 475 EXPECT_EQ(Expected, formatAndApply(Code, Replaces)); 476 } 477 478 TEST_F(CleanUpReplacementsTest, SimpleDeleteIncludes) { 479 std::string Code = "#include \"abc.h\"\n" 480 "#include \"xyz.h\" // comment\n" 481 "#include \"xyz\"\n" 482 "int x;\n"; 483 std::string Expected = "#include \"xyz\"\n" 484 "int x;\n"; 485 tooling::Replacements Replaces = 486 toReplacements({createDeletion("abc.h"), createDeletion("xyz.h")}); 487 EXPECT_EQ(Expected, apply(Code, Replaces)); 488 } 489 490 TEST_F(CleanUpReplacementsTest, InsertionAndDeleteHeader) { 491 std::string Code = "#include \"a.h\"\n" 492 "\n" 493 "#include <vector>\n"; 494 std::string Expected = "#include \"a.h\"\n" 495 "\n" 496 "#include <map>\n"; 497 tooling::Replacements Replaces = toReplacements( 498 {createDeletion("<vector>"), createInsertion("#include <map>")}); 499 EXPECT_EQ(Expected, apply(Code, Replaces)); 500 } 501 502 } // end namespace 503 } // end namespace format 504 } // end namespace clang 505