Skip to content

Commit 48a3e47

Browse files
authored
Regex Transformer (#729)
* new regex transformer * add typing
1 parent 8db8518 commit 48a3e47

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import re
2+
from typing import Pattern
3+
4+
from codemodder.codemods.base_transformer import BaseTransformerPipeline
5+
from codemodder.codetf import Change, ChangeSet
6+
from codemodder.context import CodemodExecutionContext
7+
from codemodder.diff import create_diff
8+
from codemodder.file_context import FileContext
9+
from codemodder.logging import logger
10+
from codemodder.result import Result
11+
12+
13+
class RegexTransformerPipeline(BaseTransformerPipeline):
14+
pattern: Pattern
15+
replacement: str
16+
change_description: str
17+
18+
def __init__(self, pattern: Pattern, replacement: str, change_description: str):
19+
super().__init__()
20+
self.pattern = pattern
21+
self.replacement = replacement
22+
self.change_description = change_description
23+
24+
def apply(
25+
self,
26+
context: CodemodExecutionContext,
27+
file_context: FileContext,
28+
results: list[Result] | None,
29+
) -> ChangeSet | None:
30+
31+
changes = []
32+
updated_lines = []
33+
34+
with open(file_context.file_path, "r", encoding="utf-8") as f:
35+
original_lines = f.readlines()
36+
37+
for lineno, line in enumerate(original_lines):
38+
changed_line = re.sub(self.pattern, self.replacement, line)
39+
updated_lines.append(changed_line)
40+
if line != changed_line:
41+
changes.append(
42+
Change(
43+
lineNumber=lineno,
44+
description=self.change_description,
45+
findings=file_context.get_findings_for_location(lineno),
46+
)
47+
)
48+
49+
if not changes:
50+
logger.debug("No changes produced for %s", file_context.file_path)
51+
return None
52+
53+
diff = create_diff(original_lines, updated_lines)
54+
55+
if not context.dry_run:
56+
with open(file_context.file_path, "w+", encoding="utf-8") as original:
57+
original.writelines(updated_lines)
58+
59+
return ChangeSet(
60+
path=str(file_context.file_path.relative_to(context.directory)),
61+
diff=diff,
62+
changes=changes,
63+
)

tests/test_regex_transformer.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import logging
2+
3+
from codemodder.codemods.regex_transformer import RegexTransformerPipeline
4+
from codemodder.context import CodemodExecutionContext
5+
from codemodder.file_context import FileContext
6+
7+
8+
def test_transformer_no_change(mocker, caplog, tmp_path_factory):
9+
caplog.set_level(logging.DEBUG)
10+
base_dir = tmp_path_factory.mktemp("foo")
11+
code = base_dir / "code.py"
12+
code.write_text("# Something that won't match")
13+
14+
file_context = FileContext(
15+
base_dir,
16+
code,
17+
)
18+
execution_context = CodemodExecutionContext(
19+
directory=base_dir,
20+
dry_run=True,
21+
verbose=False,
22+
registry=mocker.MagicMock(),
23+
providers=None,
24+
repo_manager=mocker.MagicMock(),
25+
path_include=[],
26+
path_exclude=[],
27+
)
28+
pipeline = RegexTransformerPipeline(
29+
pattern=r"hello", replacement="bye", change_description="testing"
30+
)
31+
32+
changeset = pipeline.apply(
33+
context=execution_context,
34+
file_context=file_context,
35+
results=None,
36+
)
37+
assert changeset is None
38+
assert "No changes produced for" in caplog.text
39+
40+
41+
def test_transformer(mocker, tmp_path_factory):
42+
base_dir = tmp_path_factory.mktemp("foo")
43+
code = base_dir / "code.py"
44+
text = "# Something that will match pattern hello"
45+
code.write_text(text)
46+
47+
file_context = FileContext(
48+
base_dir,
49+
code,
50+
)
51+
execution_context = CodemodExecutionContext(
52+
directory=base_dir,
53+
dry_run=False,
54+
verbose=False,
55+
registry=mocker.MagicMock(),
56+
providers=None,
57+
repo_manager=mocker.MagicMock(),
58+
path_include=[],
59+
path_exclude=[],
60+
)
61+
pipeline = RegexTransformerPipeline(
62+
pattern=r"hello", replacement="bye", change_description="testing"
63+
)
64+
65+
changeset = pipeline.apply(
66+
context=execution_context,
67+
file_context=file_context,
68+
results=None,
69+
)
70+
assert changeset is not None
71+
assert code.read_text() == text.replace("hello", "bye")

0 commit comments

Comments
 (0)