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