Index: clang/include/clang/Rewrite/Core/Rewriter.h =================================================================== --- clang/include/clang/Rewrite/Core/Rewriter.h +++ clang/include/clang/Rewrite/Core/Rewriter.h @@ -84,7 +84,16 @@ /// in different buffers, this returns an empty string. /// /// Note that this method is not particularly efficient. - std::string getRewrittenText(SourceRange Range) const; + std::string getRewrittenText(CharSourceRange Range) const; + + /// getRewrittenText - Return the rewritten form of the text in the specified + /// range. If the start or end of the range was unrewritable or if they are + /// in different buffers, this returns an empty string. + /// + /// Note that this method is not particularly efficient. + std::string getRewrittenText(SourceRange Range) const { + return getRewrittenText(CharSourceRange::getTokenRange(Range)); + } /// InsertText - Insert the specified string at the specified location in the /// original buffer. This method returns true (and does nothing) if the input @@ -138,6 +147,13 @@ bool ReplaceText(SourceLocation Start, unsigned OrigLength, StringRef NewStr); + /// ReplaceText - This method replaces a range of characters in the input + /// buffer with a new string. This is effectively a combined "remove/insert" + /// operation. + bool ReplaceText(CharSourceRange range, StringRef NewStr) { + return ReplaceText(range.getBegin(), getRangeSize(range), NewStr); + } + /// ReplaceText - This method replaces a range of characters in the input /// buffer with a new string. This is effectively a combined "remove/insert" /// operation. Index: clang/lib/Rewrite/Rewriter.cpp =================================================================== --- clang/lib/Rewrite/Rewriter.cpp +++ clang/lib/Rewrite/Rewriter.cpp @@ -170,7 +170,7 @@ /// in different buffers, this returns an empty string. /// /// Note that this method is not particularly efficient. -std::string Rewriter::getRewrittenText(SourceRange Range) const { +std::string Rewriter::getRewrittenText(CharSourceRange Range) const { if (!isRewritable(Range.getBegin()) || !isRewritable(Range.getEnd())) return {}; @@ -193,7 +193,9 @@ // Adjust the end offset to the end of the last token, instead of being the // start of the last token. - EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); + if (Range.isTokenRange()) + EndOff += + Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); return std::string(Ptr, Ptr+EndOff-StartOff); } @@ -203,7 +205,8 @@ // Adjust the end offset to the end of the last token, instead of being the // start of the last token. - EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); + if (Range.isTokenRange()) + EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); // Advance the iterators to the right spot, yay for linear time algorithms. RewriteBuffer::iterator Start = RB.begin(); Index: clang/unittests/Rewrite/CMakeLists.txt =================================================================== --- clang/unittests/Rewrite/CMakeLists.txt +++ clang/unittests/Rewrite/CMakeLists.txt @@ -4,8 +4,10 @@ add_clang_unittest(RewriteTests RewriteBufferTest.cpp + RewriterTest.cpp ) target_link_libraries(RewriteTests PRIVATE clangRewrite + clangTooling ) Index: clang/unittests/Rewrite/RewriterTest.cpp =================================================================== --- /dev/null +++ clang/unittests/Rewrite/RewriterTest.cpp @@ -0,0 +1,80 @@ +//===- unittests/Rewrite/RewriterTest.cpp - Rewriter tests ----------------===// +// +// 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/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +using namespace clang; + +namespace { + +struct RangeTypeTest { + std::unique_ptr AST; + Rewriter Rewrite; + SourceLocation FileStart; + CharSourceRange CRange; // covers exact char range + CharSourceRange TRange; // extends CRange to whole tokens + SourceRange SRange; // different type but behaves like TRange + SourceLocation makeLoc(int Off) { return FileStart.getLocWithOffset(Off); } + CharSourceRange makeCharRange(int StartOff, int EndOff) { + return CharSourceRange::getCharRange(makeLoc(StartOff), makeLoc(EndOff)); + } + RangeTypeTest(StringRef Code, int StartOff, int EndOff) { + AST = tooling::buildASTFromCode(Code); + ASTContext &C = AST->getASTContext(); + Rewrite = Rewriter(C.getSourceManager(), C.getLangOpts()); + FileStart = AST->getStartOfMainFileID(); + CRange = makeCharRange(StartOff, EndOff); + SRange = CRange.getAsRange(); + TRange = CharSourceRange::getTokenRange(SRange); + } +}; + +TEST(Rewriter, GetRewrittenTextRangeTypes) { + // Check that correct text is retrieved for each range type. Check again + // after a modification. Ranges remaing in terms of the original text but + // include the new text. + StringRef Code = "int main() { return 0; }"; + // get char range ^~~ = "ret" + // get token range ^~~+++ = "return" + // get source range ^~~+++ = "return" + // insert "x" ^ + // get char range ^~~ = "xret" + // get token range ^~~+++ = "xreturn" + // get source range ^~~+++ = "xreturn" + RangeTypeTest T(Code, 13, 16); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.CRange), "ret"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.TRange), "return"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.SRange), "return"); + T.Rewrite.InsertText(T.makeLoc(13), "x"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.CRange), "xret"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.TRange), "xreturn"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.SRange), "xreturn"); +} + +TEST(Rewriter, ReplaceTextRangeTypes) { + // Check that correct text is replaced for each range type. Ranges remain in + // terms of the original text but include the new text. + StringRef Code = "int main(int argc, char *argv[]) { return argc; }"; + // replace char range with "foo" ^~ + // get ^~~~~ = "foogc;" + // replace token range with "bar" ^~++ + // get ^~~~~ = "bar;" + // replace source range with "0" ^~++ + // get ^~~~~ = "0;" + RangeTypeTest T(Code, 42, 44); + T.Rewrite.ReplaceText(T.CRange, "foo"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.makeCharRange(42, 47)), "foogc;"); + T.Rewrite.ReplaceText(T.TRange, "bar"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.makeCharRange(42, 47)), "bar;"); + T.Rewrite.ReplaceText(T.SRange, "0"); + EXPECT_EQ(T.Rewrite.getRewrittenText(T.makeCharRange(42, 47)), "0;"); +} + +} // anonymous namespace