diff --git a/clang/include/clang-c/Rewrite.h b/clang/include/clang-c/Rewrite.h new file mode 100644 --- /dev/null +++ b/clang/include/clang-c/Rewrite.h @@ -0,0 +1,63 @@ +/*===-- clang-c/Rewrite.h - C CXRewriter --------------------------*- C -*-===*\ +|* *| +|* 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 *| +|* *| +|*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_CLANG_C_REWRITE_H +#define LLVM_CLANG_C_REWRITE_H + +#include "clang-c/CXString.h" +#include "clang-c/ExternC.h" +#include "clang-c/Index.h" +#include "clang-c/Platform.h" + +LLVM_CLANG_C_EXTERN_C_BEGIN + +typedef void *CXRewriter; + +/** + * Create CXRewriter. + */ +CINDEX_LINKAGE CXRewriter clang_CXRewriter_create(CXTranslationUnit TU); + +/** + * Insert the specified string at the specified location in the original buffer. + */ +CINDEX_LINKAGE void clang_CXRewriter_insertTextBefore(CXRewriter Rew, CXSourceLocation Loc, + const char *Insert); + +/** + * Replace the specified range of characters in the input with the specified + * replacement. + */ +CINDEX_LINKAGE void clang_CXRewriter_replaceText(CXRewriter Rew, CXSourceRange ToBeReplaced, + const char *Replacement); + +/** + * Remove the specified range. + */ +CINDEX_LINKAGE void clang_CXRewriter_removeText(CXRewriter Rew, CXSourceRange ToBeRemoved); + +/** + * Save all changed files to disk. + * Returns 1 if any files were not saved successfully, returns 0 otherwise. + */ +CINDEX_LINKAGE int clang_CXRewriter_overwriteChangedFiles(CXRewriter Rew); + +/** + * Write out rewritten version of the main file to stdout. + */ +CINDEX_LINKAGE void clang_CXRewriter_writeMainFileToStdOut(CXRewriter Rew); + +/** + * Free the given CXRewriter. + */ +CINDEX_LINKAGE void clang_CXRewriter_dispose(CXRewriter Rew); + +LLVM_CLANG_C_EXTERN_C_END + +#endif diff --git a/clang/tools/libclang/CMakeLists.txt b/clang/tools/libclang/CMakeLists.txt --- a/clang/tools/libclang/CMakeLists.txt +++ b/clang/tools/libclang/CMakeLists.txt @@ -20,6 +20,7 @@ CXType.cpp Indexing.cpp FatalErrorHandler.cpp + Rewrite.cpp ADDITIONAL_HEADERS CIndexDiagnostic.h diff --git a/clang/tools/libclang/Rewrite.cpp b/clang/tools/libclang/Rewrite.cpp new file mode 100644 --- /dev/null +++ b/clang/tools/libclang/Rewrite.cpp @@ -0,0 +1,63 @@ +//===- Rewrite.cpp --------------------------------------------------------===// +// +// 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-c/Rewrite.h" +#include "CXSourceLocation.h" +#include "CXTranslationUnit.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Rewrite/Core/Rewriter.h" + +CXRewriter clang_CXRewriter_create(CXTranslationUnit TU) { + if (clang::cxtu::isNotUsableTU(TU)) { + LOG_BAD_TU(TU); + return {}; + } + clang::ASTUnit *AU = clang::cxtu::getASTUnit(TU); + assert(AU); + return reinterpret_cast( + new clang::Rewriter(AU->getSourceManager(), AU->getLangOpts())); +} + +void clang_CXRewriter_insertTextBefore(CXRewriter Rew, CXSourceLocation Loc, + const char *Insert) { + assert(Rew); + clang::Rewriter &R = *reinterpret_cast(Rew); + R.InsertTextBefore(clang::cxloc::translateSourceLocation(Loc), Insert); +} + +void clang_CXRewriter_replaceText(CXRewriter Rew, CXSourceRange ToBeReplaced, + const char *Replacement) { + assert(Rew); + clang::Rewriter &R = *reinterpret_cast(Rew); + R.ReplaceText(clang::cxloc::translateCXRangeToCharRange(ToBeReplaced), + Replacement); +} + +void clang_CXRewriter_removeText(CXRewriter Rew, CXSourceRange ToBeRemoved) { + assert(Rew); + clang::Rewriter &R = *reinterpret_cast(Rew); + R.RemoveText(clang::cxloc::translateCXRangeToCharRange(ToBeRemoved)); +} + +int clang_CXRewriter_overwriteChangedFiles(CXRewriter Rew) { + assert(Rew); + clang::Rewriter &R = *reinterpret_cast(Rew); + return R.overwriteChangedFiles(); +} + +void clang_CXRewriter_writeMainFileToStdOut(CXRewriter Rew) { + assert(Rew); + clang::Rewriter &R = *reinterpret_cast(Rew); + R.getEditBuffer(R.getSourceMgr().getMainFileID()).write(llvm::outs()); +} + +void clang_CXRewriter_dispose(CXRewriter Rew) { + if (Rew) + delete reinterpret_cast(Rew); +} diff --git a/clang/tools/libclang/libclang.exports b/clang/tools/libclang/libclang.exports --- a/clang/tools/libclang/libclang.exports +++ b/clang/tools/libclang/libclang.exports @@ -385,3 +385,10 @@ clang_Cursor_getVarDeclInitializer clang_Cursor_hasVarDeclGlobalStorage clang_Cursor_hasVarDeclExternalStorage +clang_CXRewriter_create +clang_CXRewriter_insertTextBefore +clang_CXRewriter_replaceText +clang_CXRewriter_removeText +clang_CXRewriter_overwriteChangedFiles +clang_CXRewriter_writeMainFileToStdOut +clang_CXRewriter_dispose diff --git a/clang/unittests/libclang/LibclangTest.cpp b/clang/unittests/libclang/LibclangTest.cpp --- a/clang/unittests/libclang/LibclangTest.cpp +++ b/clang/unittests/libclang/LibclangTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang-c/Index.h" +#include "clang-c/Rewrite.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" @@ -842,3 +843,90 @@ }, nullptr); } +class LibclangRewriteTest : public LibclangParseTest { +public: + CXRewriter Rew = nullptr; + std::string Filename; + CXFile File = nullptr; + + void SetUp() override { + LibclangParseTest::SetUp(); + Filename = "file.cpp"; + WriteFile(Filename, "int main() { return 0; }"); + ClangTU = clang_parseTranslationUnit(Index, Filename.c_str(), nullptr, 0, + nullptr, 0, TUFlags); + Rew = clang_CXRewriter_create(ClangTU); + File = clang_getFile(ClangTU, Filename.c_str()); + } + void TearDown() override { + clang_CXRewriter_dispose(Rew); + LibclangParseTest::TearDown(); + } +}; + +static std::string getFileContent(const std::string& Filename) { + std::ifstream RewrittenFile(Filename); + std::string RewrittenFileContent; + std::string Line; + while (std::getline(RewrittenFile, Line)) { + if (RewrittenFileContent.empty()) + RewrittenFileContent = Line; + else { + RewrittenFileContent += "\n" + Line; + } + } + return RewrittenFileContent; +} + +TEST_F(LibclangRewriteTest, RewriteReplace) { + CXSourceLocation B = clang_getLocation(ClangTU, File, 1, 5); + CXSourceLocation E = clang_getLocation(ClangTU, File, 1, 9); + CXSourceRange Rng = clang_getRange(B, E); + + clang_CXRewriter_replaceText(Rew, Rng, "MAIN"); + + ASSERT_EQ(clang_CXRewriter_overwriteChangedFiles(Rew), 0); + EXPECT_EQ(getFileContent(Filename), "int MAIN() { return 0; }"); +} + +TEST_F(LibclangRewriteTest, RewriteReplaceShorter) { + CXSourceLocation B = clang_getLocation(ClangTU, File, 1, 5); + CXSourceLocation E = clang_getLocation(ClangTU, File, 1, 9); + CXSourceRange Rng = clang_getRange(B, E); + + clang_CXRewriter_replaceText(Rew, Rng, "foo"); + + ASSERT_EQ(clang_CXRewriter_overwriteChangedFiles(Rew), 0); + EXPECT_EQ(getFileContent(Filename), "int foo() { return 0; }"); +} + +TEST_F(LibclangRewriteTest, RewriteReplaceLonger) { + CXSourceLocation B = clang_getLocation(ClangTU, File, 1, 5); + CXSourceLocation E = clang_getLocation(ClangTU, File, 1, 9); + CXSourceRange Rng = clang_getRange(B, E); + + clang_CXRewriter_replaceText(Rew, Rng, "patatino"); + + ASSERT_EQ(clang_CXRewriter_overwriteChangedFiles(Rew), 0); + EXPECT_EQ(getFileContent(Filename), "int patatino() { return 0; }"); +} + +TEST_F(LibclangRewriteTest, RewriteInsert) { + CXSourceLocation Loc = clang_getLocation(ClangTU, File, 1, 5); + + clang_CXRewriter_insertTextBefore(Rew, Loc, "ro"); + + ASSERT_EQ(clang_CXRewriter_overwriteChangedFiles(Rew), 0); + EXPECT_EQ(getFileContent(Filename), "int romain() { return 0; }"); +} + +TEST_F(LibclangRewriteTest, RewriteRemove) { + CXSourceLocation B = clang_getLocation(ClangTU, File, 1, 5); + CXSourceLocation E = clang_getLocation(ClangTU, File, 1, 9); + CXSourceRange Rng = clang_getRange(B, E); + + clang_CXRewriter_removeText(Rew, Rng); + + ASSERT_EQ(clang_CXRewriter_overwriteChangedFiles(Rew), 0); + EXPECT_EQ(getFileContent(Filename), "int () { return 0; }"); +}