[clang] [libclang/python] Expose Rewriter to the python binding (PR #77269)
Jimmy Z via cfe-commits
cfe-commits at lists.llvm.org
Sat Jan 20 20:57:52 PST 2024
https://github.com/jimmy-zx updated https://github.com/llvm/llvm-project/pull/77269
>From a5379ca876d9531d48b37b9ad9c864db98c7dcd6 Mon Sep 17 00:00:00 2001
From: Jimmy Z <51149050+jimmy-zx at users.noreply.github.com>
Date: Mon, 8 Jan 2024 04:36:27 +0000
Subject: [PATCH 1/3] [libclang/python] Expose Rewriter to the python binding
---
clang/bindings/python/clang/cindex.py | 62 +++++++++++++++
.../python/tests/cindex/test_rewrite.py | 75 +++++++++++++++++++
2 files changed, 137 insertions(+)
create mode 100644 clang/bindings/python/tests/cindex/test_rewrite.py
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index d780ee353a133c..ced449180d98fc 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -3531,6 +3531,61 @@ def cursor(self):
return cursor
+class Rewriter(ClangObject):
+ """
+ The Rewriter is a wrapper class around clang::Rewriter
+
+ It enables rewriting buffers.
+ """
+
+ @staticmethod
+ def create(tu):
+ """
+ Creates a new Rewriter
+ Parameters:
+ tu -- The translation unit for the target AST.
+ """
+ return Rewriter(conf.lib.clang_CXRewriter_create(tu))
+
+ def __init__(self, ptr):
+ ClangObject.__init__(self, ptr)
+
+ def __del__(self):
+ conf.lib.clang_CXRewriter_dispose(self)
+
+ def insertTextBefore(self, loc, insert):
+ """
+ Insert the specified string at the specified location in the original buffer.
+ """
+ conf.lib.clang_CXRewriter_insertTextBefore(self, loc, insert)
+
+ def replaceText(self, toBeReplaced, replacement):
+ """
+ This method replaces a range of characters in the input buffer with a new string.
+ """
+ conf.lib.clang_CXRewriter_replaceText(self, toBeReplaced, replacement)
+
+ def removeText(self, toBeRemoved):
+ """
+ Remove the specified text region.
+ """
+ conf.lib.clang_CXRewriter_removeText(self, toBeRemoved)
+
+ def overwriteChangedFiles(self):
+ """
+ Save all changed files to disk.
+
+ Returns 1 if any files were not saved successfully, returns 0 otherwise.
+ """
+ return conf.lib.clang_CXRewriter_overwriteChangedFiles(self)
+
+ def writeMainFileToStdOut(self):
+ """
+ Writes the main file to stdout.
+ """
+ conf.lib.clang_CXRewriter_writeMainFileToStdOut(self)
+
+
# Now comes the plumbing to hook up the C library.
# Register callback types in common container.
@@ -3596,6 +3651,13 @@ def cursor(self):
("clang_codeCompleteGetNumDiagnostics", [CodeCompletionResults], c_int),
("clang_createIndex", [c_int, c_int], c_object_p),
("clang_createTranslationUnit", [Index, c_interop_string], c_object_p),
+ ("clang_CXRewriter_create", [TranslationUnit], c_object_p),
+ ("clang_CXRewriter_dispose", [Rewriter]),
+ ("clang_CXRewriter_insertTextBefore", [Rewriter, SourceLocation, c_interop_string]),
+ ("clang_CXRewriter_overwriteChangedFiles", [Rewriter], c_int),
+ ("clang_CXRewriter_removeText", [Rewriter, SourceRange]),
+ ("clang_CXRewriter_replaceText", [Rewriter, SourceRange, c_interop_string]),
+ ("clang_CXRewriter_writeMainFileToStdOut", [Rewriter]),
("clang_CXXConstructor_isConvertingConstructor", [Cursor], bool),
("clang_CXXConstructor_isCopyConstructor", [Cursor], bool),
("clang_CXXConstructor_isDefaultConstructor", [Cursor], bool),
diff --git a/clang/bindings/python/tests/cindex/test_rewrite.py b/clang/bindings/python/tests/cindex/test_rewrite.py
new file mode 100644
index 00000000000000..2c5f904ce50bdc
--- /dev/null
+++ b/clang/bindings/python/tests/cindex/test_rewrite.py
@@ -0,0 +1,75 @@
+import sys
+import io
+import unittest
+import tempfile
+
+from clang.cindex import (
+ Rewriter,
+ TranslationUnit,
+ Config,
+ File,
+ SourceLocation,
+ SourceRange,
+)
+
+
+class TestRewrite(unittest.TestCase):
+ code = """
+int test1;
+
+void test2(void);
+
+int f(int c) {
+ return c;
+}
+"""
+
+ def setUp(self):
+ self.tmp = tempfile.NamedTemporaryFile(suffix=".cpp", buffering=0)
+ self.tmp.write(TestRewrite.code.encode("utf-8"))
+ self.tmp.flush()
+ self.tu = TranslationUnit.from_source(self.tmp.name)
+ self.rew = Rewriter.create(self.tu)
+ self.file = File.from_name(self.tu, self.tmp.name)
+
+ def tearDown(self):
+ self.tmp.close()
+
+ def test_insert(self):
+ snip = "#include <cstdio>\n"
+
+ beginning = SourceLocation.from_offset(self.tu, self.file, 0)
+ self.rew.insertTextBefore(beginning, snip)
+ self.rew.overwriteChangedFiles()
+
+ with open(self.tmp.name, "r", encoding="utf-8") as f:
+ self.assertEqual(f.read(), snip + TestRewrite.code)
+
+ def test_replace(self):
+ pattern = "test2"
+ replacement = "func"
+
+ offset = TestRewrite.code.find(pattern)
+ pattern_range = SourceRange.from_locations(
+ SourceLocation.from_offset(self.tu, self.file, offset),
+ SourceLocation.from_offset(self.tu, self.file, offset + len(pattern)),
+ )
+ self.rew.replaceText(pattern_range, replacement)
+ self.rew.overwriteChangedFiles()
+
+ with open(self.tmp.name, "r", encoding="utf-8") as f:
+ self.assertEqual(f.read(), TestRewrite.code.replace(pattern, replacement))
+
+ def test_remove(self):
+ pattern = "int c"
+
+ offset = TestRewrite.code.find(pattern)
+ pattern_range = SourceRange.from_locations(
+ SourceLocation.from_offset(self.tu, self.file, offset),
+ SourceLocation.from_offset(self.tu, self.file, offset + len(pattern)),
+ )
+ self.rew.removeText(pattern_range)
+ self.rew.overwriteChangedFiles()
+
+ with open(self.tmp.name, "r", encoding="utf-8") as f:
+ self.assertEqual(f.read(), TestRewrite.code.replace(pattern, ""))
>From 15c6929848298cfae27e9b4c45534a200102ab81 Mon Sep 17 00:00:00 2001
From: Jimmy Z <51149050+jimmy-zx at users.noreply.github.com>
Date: Wed, 17 Jan 2024 11:17:43 -0500
Subject: [PATCH 2/3] [clang][docs] Update docs for Rewriter
---
clang/docs/ReleaseNotes.rst | 2 ++
1 file changed, 2 insertions(+)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c9b577bd549b1e..4b54515c05746d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1237,6 +1237,8 @@ Sanitizers
Python Binding Changes
----------------------
+- Exposed `CXRewriter` API as `class Rewriter`.
+
Additional Information
======================
>From 7a4ebf9c8c9ffa29d7f7f863d9a19bfe53432348 Mon Sep 17 00:00:00 2001
From: Jimmy Z <51149050+jimmy-zx at users.noreply.github.com>
Date: Sun, 21 Jan 2024 04:56:44 +0000
Subject: [PATCH 3/3] [libclang/python] Mirrored libclang rewrite tests to
python binding
---
.../python/tests/cindex/test_rewrite.py | 71 +++++++++----------
1 file changed, 35 insertions(+), 36 deletions(-)
diff --git a/clang/bindings/python/tests/cindex/test_rewrite.py b/clang/bindings/python/tests/cindex/test_rewrite.py
index 2c5f904ce50bdc..d589880e7a2711 100644
--- a/clang/bindings/python/tests/cindex/test_rewrite.py
+++ b/clang/bindings/python/tests/cindex/test_rewrite.py
@@ -14,15 +14,7 @@
class TestRewrite(unittest.TestCase):
- code = """
-int test1;
-
-void test2(void);
-
-int f(int c) {
- return c;
-}
-"""
+ code = """int main() { return 0; }"""
def setUp(self):
self.tmp = tempfile.NamedTemporaryFile(suffix=".cpp", buffering=0)
@@ -35,41 +27,48 @@ def setUp(self):
def tearDown(self):
self.tmp.close()
- def test_insert(self):
- snip = "#include <cstdio>\n"
-
- beginning = SourceLocation.from_offset(self.tu, self.file, 0)
- self.rew.insertTextBefore(beginning, snip)
- self.rew.overwriteChangedFiles()
-
+ def get_content(self) -> str:
with open(self.tmp.name, "r", encoding="utf-8") as f:
- self.assertEqual(f.read(), snip + TestRewrite.code)
+ return f.read()
def test_replace(self):
- pattern = "test2"
- replacement = "func"
+ rng = SourceRange.from_locations(
+ SourceLocation.from_position(self.tu, self.file, 1, 5),
+ SourceLocation.from_position(self.tu, self.file, 1, 9),
+ )
+ self.rew.replaceText(rng, "MAIN")
+ self.rew.overwriteChangedFiles()
+ self.assertEqual(self.get_content(), "int MAIN() { return 0; }")
- offset = TestRewrite.code.find(pattern)
- pattern_range = SourceRange.from_locations(
- SourceLocation.from_offset(self.tu, self.file, offset),
- SourceLocation.from_offset(self.tu, self.file, offset + len(pattern)),
+ def test_replace_shorter(self):
+ rng = SourceRange.from_locations(
+ SourceLocation.from_position(self.tu, self.file, 1, 5),
+ SourceLocation.from_position(self.tu, self.file, 1, 9),
)
- self.rew.replaceText(pattern_range, replacement)
+ self.rew.replaceText(rng, "foo")
self.rew.overwriteChangedFiles()
+ self.assertEqual(self.get_content(), "int foo() { return 0; }")
- with open(self.tmp.name, "r", encoding="utf-8") as f:
- self.assertEqual(f.read(), TestRewrite.code.replace(pattern, replacement))
+ def test_replace_longer(self):
+ rng = SourceRange.from_locations(
+ SourceLocation.from_position(self.tu, self.file, 1, 5),
+ SourceLocation.from_position(self.tu, self.file, 1, 9),
+ )
+ self.rew.replaceText(rng, "patatino")
+ self.rew.overwriteChangedFiles()
+ self.assertEqual(self.get_content(), "int patatino() { return 0; }")
- def test_remove(self):
- pattern = "int c"
+ def test_insert(self):
+ pos = SourceLocation.from_position(self.tu, self.file, 1, 5)
+ self.rew.insertTextBefore(pos, "ro")
+ self.rew.overwriteChangedFiles()
+ self.assertEqual(self.get_content(), "int romain() { return 0; }")
- offset = TestRewrite.code.find(pattern)
- pattern_range = SourceRange.from_locations(
- SourceLocation.from_offset(self.tu, self.file, offset),
- SourceLocation.from_offset(self.tu, self.file, offset + len(pattern)),
+ def test_remove(self):
+ rng = SourceRange.from_locations(
+ SourceLocation.from_position(self.tu, self.file, 1, 5),
+ SourceLocation.from_position(self.tu, self.file, 1, 9),
)
- self.rew.removeText(pattern_range)
+ self.rew.removeText(rng)
self.rew.overwriteChangedFiles()
-
- with open(self.tmp.name, "r", encoding="utf-8") as f:
- self.assertEqual(f.read(), TestRewrite.code.replace(pattern, ""))
+ self.assertEqual(self.get_content(), "int () { return 0; }")
More information about the cfe-commits
mailing list