[clang] [libclang/python] Expose Rewriter to the python binding (PR #77269)

Jimmy Z via cfe-commits cfe-commits at lists.llvm.org
Tue Jan 23 07:44:32 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/5] [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 d780ee353a133cb..ced449180d98fca 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 000000000000000..2c5f904ce50bdc7
--- /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/5] [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 c9b577bd549b1e2..4b54515c05746d9 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/5] [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 2c5f904ce50bdc7..d589880e7a27111 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; }")

>From f97b693f36c991ba09ab8953704f235a78bd5fe2 Mon Sep 17 00:00:00 2001
From: Jimmy Z <51149050+jimmy-zx at users.noreply.github.com>
Date: Mon, 22 Jan 2024 01:15:48 +0000
Subject: [PATCH 4/5] [libclang/python] Update Rewriter to use snake_case

---
 clang/bindings/python/clang/cindex.py         | 23 +++++++++++--------
 .../python/tests/cindex/test_rewrite.py       | 23 ++++++++-----------
 2 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index ced449180d98fca..06e301d50ba9f02 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -3553,33 +3553,36 @@ def __init__(self, ptr):
     def __del__(self):
         conf.lib.clang_CXRewriter_dispose(self)
 
-    def insertTextBefore(self, loc, insert):
+    def insert_text_before(self, loc, insert):
         """
-        Insert the specified string at the specified location in the original buffer.
+        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):
+    def replace_text(self, extent, replacement):
         """
-        This method replaces a range of characters in the input buffer with a new string.
+        This method replaces a range of characters in the input buffer with
+        a new string.
         """
-        conf.lib.clang_CXRewriter_replaceText(self, toBeReplaced, replacement)
+        conf.lib.clang_CXRewriter_replaceText(self, extent, replacement)
 
-    def removeText(self, toBeRemoved):
+    def remove_text(self, extent):
         """
         Remove the specified text region.
         """
-        conf.lib.clang_CXRewriter_removeText(self, toBeRemoved)
+        conf.lib.clang_CXRewriter_removeText(self, extent)
 
-    def overwriteChangedFiles(self):
+    def overwrite_changed_files(self):
         """
         Save all changed files to disk.
 
-        Returns 1 if any files were not saved successfully, returns 0 otherwise.
+        Returns 1 if any files were not saved successfully,
+        returns 0 otherwise.
         """
         return conf.lib.clang_CXRewriter_overwriteChangedFiles(self)
 
-    def writeMainFileToStdOut(self):
+    def write_main_file_to_stdout(self):
         """
         Writes the main file to stdout.
         """
diff --git a/clang/bindings/python/tests/cindex/test_rewrite.py b/clang/bindings/python/tests/cindex/test_rewrite.py
index d589880e7a27111..42006f57554e281 100644
--- a/clang/bindings/python/tests/cindex/test_rewrite.py
+++ b/clang/bindings/python/tests/cindex/test_rewrite.py
@@ -1,12 +1,9 @@
-import sys
-import io
 import unittest
 import tempfile
 
 from clang.cindex import (
     Rewriter,
     TranslationUnit,
-    Config,
     File,
     SourceLocation,
     SourceRange,
@@ -36,8 +33,8 @@ def test_replace(self):
             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.rew.replace_text(rng, "MAIN")
+        self.rew.overwrite_changed_files()
         self.assertEqual(self.get_content(), "int MAIN() { return 0; }")
 
     def test_replace_shorter(self):
@@ -45,8 +42,8 @@ def test_replace_shorter(self):
             SourceLocation.from_position(self.tu, self.file, 1, 5),
             SourceLocation.from_position(self.tu, self.file, 1, 9),
         )
-        self.rew.replaceText(rng, "foo")
-        self.rew.overwriteChangedFiles()
+        self.rew.replace_text(rng, "foo")
+        self.rew.overwrite_changed_files()
         self.assertEqual(self.get_content(), "int foo() { return 0; }")
 
     def test_replace_longer(self):
@@ -54,14 +51,14 @@ def test_replace_longer(self):
             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.rew.replace_text(rng, "patatino")
+        self.rew.overwrite_changed_files()
         self.assertEqual(self.get_content(), "int patatino() { return 0; }")
 
     def test_insert(self):
         pos = SourceLocation.from_position(self.tu, self.file, 1, 5)
-        self.rew.insertTextBefore(pos, "ro")
-        self.rew.overwriteChangedFiles()
+        self.rew.insert_text_before(pos, "ro")
+        self.rew.overwrite_changed_files()
         self.assertEqual(self.get_content(), "int romain() { return 0; }")
 
     def test_remove(self):
@@ -69,6 +66,6 @@ def test_remove(self):
             SourceLocation.from_position(self.tu, self.file, 1, 5),
             SourceLocation.from_position(self.tu, self.file, 1, 9),
         )
-        self.rew.removeText(rng)
-        self.rew.overwriteChangedFiles()
+        self.rew.remove_text(rng)
+        self.rew.overwrite_changed_files()
         self.assertEqual(self.get_content(), "int () { return 0; }")

>From d169b31c5b64fbde1f35ca46f465b1aa508811be Mon Sep 17 00:00:00 2001
From: Jimmy Z <51149050+jimmy-zx at users.noreply.github.com>
Date: Tue, 23 Jan 2024 15:44:14 +0000
Subject: [PATCH 5/5] [libclang/python] flush stdout before calling
 writeMainFileToStdOut

---
 clang/bindings/python/clang/cindex.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 06e301d50ba9f02..13f209d21db2344 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -3586,6 +3586,7 @@ def write_main_file_to_stdout(self):
         """
         Writes the main file to stdout.
         """
+        sys.stdout.flush()
         conf.lib.clang_CXRewriter_writeMainFileToStdOut(self)
 
 



More information about the cfe-commits mailing list