1 //===- unittests/Rewrite/RewriteBufferTest.cpp - RewriteBuffer 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/Rewrite/Core/RewriteBuffer.h" 10 #include "gtest/gtest.h" 11 12 using namespace llvm; 13 using namespace clang; 14 15 namespace { 16 17 #define EXPECT_OUTPUT(Buf, Output) EXPECT_EQ(Output, writeOutput(Buf)) 18 19 static std::string writeOutput(const RewriteBuffer &Buf) { 20 std::string Result; 21 raw_string_ostream OS(Result); 22 Buf.write(OS); 23 OS.flush(); 24 return Result; 25 } 26 27 static void tagRange(unsigned Offset, unsigned Len, StringRef tagName, 28 RewriteBuffer &Buf) { 29 std::string BeginTag; 30 raw_string_ostream(BeginTag) << '<' << tagName << '>'; 31 std::string EndTag; 32 raw_string_ostream(EndTag) << "</" << tagName << '>'; 33 34 Buf.InsertTextAfter(Offset, BeginTag); 35 Buf.InsertTextBefore(Offset+Len, EndTag); 36 } 37 38 TEST(RewriteBuffer, TagRanges) { 39 StringRef Input = "hello world"; 40 const char *Output = "<outer><inner>hello</inner></outer> "; 41 42 RewriteBuffer Buf; 43 Buf.Initialize(Input); 44 StringRef RemoveStr = "world"; 45 size_t Pos = Input.find(RemoveStr); 46 Buf.RemoveText(Pos, RemoveStr.size()); 47 48 StringRef TagStr = "hello"; 49 Pos = Input.find(TagStr); 50 tagRange(Pos, TagStr.size(), "outer", Buf); 51 tagRange(Pos, TagStr.size(), "inner", Buf); 52 53 EXPECT_OUTPUT(Buf, Output); 54 } 55 56 TEST(RewriteBuffer, DISABLED_RemoveLineIfEmpty_XFAIL) { 57 StringRef Input = "def\n" 58 "ghi\n" 59 "jkl\n"; 60 RewriteBuffer Buf; 61 Buf.Initialize(Input); 62 63 // Insert "abc\n" at the start. 64 Buf.InsertText(0, "abc\n"); 65 EXPECT_OUTPUT(Buf, "abc\n" 66 "def\n" 67 "ghi\n" 68 "jkl\n"); 69 70 // Remove "def\n". 71 // 72 // After the removal of "def", we have: 73 // 74 // "abc\n" 75 // "\n" 76 // "ghi\n" 77 // "jkl\n" 78 // 79 // Because removeLineIfEmpty=true, RemoveText has to remove the "\n" left on 80 // the line. This happens correctly for the rewrite buffer itself, so the 81 // next check below passes. 82 // 83 // However, RemoveText's implementation incorrectly records the delta for 84 // removing the "\n" using the rewrite buffer offset, 4, where it was 85 // supposed to use the original input offset, 3. Interpreted as an original 86 // input offset, 4 points to "g" not to "\n". Thus, any future modifications 87 // at the original input's "g" will incorrectly see "g" as having become an 88 // empty string and so will map to the next character, "h", in the rewrite 89 // buffer. 90 StringRef RemoveStr0 = "def"; 91 Buf.RemoveText(Input.find(RemoveStr0), RemoveStr0.size(), 92 /*removeLineIfEmpty*/ true); 93 EXPECT_OUTPUT(Buf, "abc\n" 94 "ghi\n" 95 "jkl\n"); 96 97 // Try to remove "ghi\n". 98 // 99 // As discussed above, the original input offset for "ghi\n" incorrectly 100 // maps to the rewrite buffer offset for "hi\nj", so we end up with: 101 // 102 // "abc\n" 103 // "gkl\n" 104 // 105 // To show that removeLineIfEmpty=true is the culprit, change true to false 106 // and append a newline to RemoveStr0 above. The test then passes. 107 StringRef RemoveStr1 = "ghi\n"; 108 Buf.RemoveText(Input.find(RemoveStr1), RemoveStr1.size()); 109 EXPECT_OUTPUT(Buf, "abc\n" 110 "jkl\n"); 111 } 112 113 } // anonymous namespace 114