From adeb5ac2d6431f348b510e1eca5b91ac6a9aa86f Mon Sep 17 00:00:00 2001 From: "Joel E. Denny" Date: Sat, 6 Jul 2019 02:55:06 +0000 Subject: [PATCH] [Rewrite] Extend to further accept CharSourceRange Some Rewrite functions are already overloaded to accept CharSourceRange, and this extends others in the same manner. I'm calling these in code that's not ready to upstream, but I figure they might be useful to others in the meantime. Reviewed By: jdoerfert Differential Revision: https://reviews.llvm.org/D61467 llvm-svn: 365258 --- clang/include/clang/Rewrite/Core/Rewriter.h | 18 ++++- clang/lib/Rewrite/Rewriter.cpp | 9 ++- clang/unittests/Rewrite/CMakeLists.txt | 2 + clang/unittests/Rewrite/RewriterTest.cpp | 80 +++++++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 clang/unittests/Rewrite/RewriterTest.cpp diff --git a/clang/include/clang/Rewrite/Core/Rewriter.h b/clang/include/clang/Rewrite/Core/Rewriter.h index 5a3ff6c79844..84c5ac3d72ee 100644 --- a/clang/include/clang/Rewrite/Core/Rewriter.h +++ b/clang/include/clang/Rewrite/Core/Rewriter.h @@ -84,7 +84,16 @@ public: /// 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 @@ public: 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. diff --git a/clang/lib/Rewrite/Rewriter.cpp b/clang/lib/Rewrite/Rewriter.cpp index 281cf6f1be5e..881399e98e33 100644 --- a/clang/lib/Rewrite/Rewriter.cpp +++ b/clang/lib/Rewrite/Rewriter.cpp @@ -170,7 +170,7 @@ int Rewriter::getRangeSize(SourceRange Range, RewriteOptions opts) const { /// 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 @@ std::string Rewriter::getRewrittenText(SourceRange Range) const { // 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 @@ std::string Rewriter::getRewrittenText(SourceRange Range) const { // 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(); diff --git a/clang/unittests/Rewrite/CMakeLists.txt b/clang/unittests/Rewrite/CMakeLists.txt index a4c6e37245c6..613d716cb955 100644 --- a/clang/unittests/Rewrite/CMakeLists.txt +++ b/clang/unittests/Rewrite/CMakeLists.txt @@ -4,8 +4,10 @@ set(LLVM_LINK_COMPONENTS add_clang_unittest(RewriteTests RewriteBufferTest.cpp + RewriterTest.cpp ) clang_target_link_libraries(RewriteTests PRIVATE clangRewrite + clangTooling ) diff --git a/clang/unittests/Rewrite/RewriterTest.cpp b/clang/unittests/Rewrite/RewriterTest.cpp new file mode 100644 index 000000000000..ca72dde46fda --- /dev/null +++ b/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 remain 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