Skip to content

Commit 09247f7

Browse files
[issue-466] chore: format codebase with Ruff and Black tools
The codebase was successfully formatted using the command `uv run poe format`, which involved installing necessary tools and running both Ruff and Black formatters. This process corrected 12 issues detected by Ruff and reformatted 2 specific files, ensuring they align with project coding standards. All changes were verified, and the formatted code has been submitted.
1 parent 44850e7 commit 09247f7

File tree

2 files changed

+81
-79
lines changed

2 files changed

+81
-79
lines changed

src/serena/tools/file_tools.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,11 @@ def apply(
186186
self.project.validate_relative_path(relative_path)
187187
with EditedFileContext(relative_path, self.agent) as context:
188188
original_content = context.get_original_content()
189-
189+
190190
# Process the replacement string to handle escape sequences properly
191191
# This ensures that escape sequences are preserved as-is in the output and not
192192
# interpreted as literal characters (e.g., \n should remain as \n, not become a newline)
193-
#
193+
#
194194
# The issue was that escape sequences in replacement strings were being interpreted
195195
# literally when they should be preserved as-is. For example, '\n' was becoming a literal
196196
# newline character, breaking string literals across multiple lines.
@@ -207,58 +207,58 @@ def escape_backslashes(s):
207207
i = 0
208208
while i < len(s):
209209
# Handle literal newlines - convert to escaped newlines
210-
if s[i] == '\n':
210+
if s[i] == "\n":
211211
# This is a literal newline, convert it to an escaped newline
212-
parts.append('\\n')
212+
parts.append("\\n")
213213
i += 1
214214
# Handle backreferences (\1, \2, etc.) - preserve these as-is
215-
elif s[i] == '\\' and i + 1 < len(s) and s[i+1].isdigit():
215+
elif s[i] == "\\" and i + 1 < len(s) and s[i + 1].isdigit():
216216
# This is a backreference, keep it as is
217-
parts.append(s[i:i+2])
217+
parts.append(s[i : i + 2])
218218
i += 2
219219
# Handle escape sequences (\n, \t, etc.) - double-escape these
220-
elif s[i] == '\\' and i + 1 < len(s) and s[i+1] in 'nrtbfv':
220+
elif s[i] == "\\" and i + 1 < len(s) and s[i + 1] in "nrtbfv":
221221
# This is an escape sequence, double-escape it
222-
parts.append('\\' + s[i:i+2])
222+
parts.append("\\" + s[i : i + 2])
223223
i += 2
224224
# Handle hex escape sequences (\x00, etc.)
225-
elif s[i] == '\\' and i + 3 < len(s) and s[i+1] == 'x' and s[i+2:i+4].isalnum():
225+
elif s[i] == "\\" and i + 3 < len(s) and s[i + 1] == "x" and s[i + 2 : i + 4].isalnum():
226226
# This is a hex escape sequence, double-escape it
227-
parts.append('\\' + s[i:i+4])
227+
parts.append("\\" + s[i : i + 4])
228228
i += 4
229229
# Handle octal escape sequences (\000, etc.)
230-
elif s[i] == '\\' and i + 1 < len(s) and s[i+1] in '01234567':
230+
elif s[i] == "\\" and i + 1 < len(s) and s[i + 1] in "01234567":
231231
# Determine the length of the octal sequence (1-3 digits)
232232
j = i + 2
233-
while j < len(s) and j < i + 4 and s[j] in '01234567':
233+
while j < len(s) and j < i + 4 and s[j] in "01234567":
234234
j += 1
235235
# This is an octal escape sequence, double-escape it
236-
parts.append('\\' + s[i:j])
236+
parts.append("\\" + s[i:j])
237237
i = j
238238
# Handle escaped backslashes (\\) - preserve these as-is
239-
elif s[i] == '\\' and i + 1 < len(s) and s[i+1] == '\\':
240-
parts.append(s[i:i+2])
239+
elif s[i] == "\\" and i + 1 < len(s) and s[i + 1] == "\\":
240+
parts.append(s[i : i + 2])
241241
i += 2
242242
# Handle other backslashes - escape them
243-
elif s[i] == '\\':
244-
parts.append('\\\\')
243+
elif s[i] == "\\":
244+
parts.append("\\\\")
245245
i += 1
246246
# Regular character
247247
else:
248248
parts.append(s[i])
249249
i += 1
250-
251-
return ''.join(parts)
252-
250+
251+
return "".join(parts)
252+
253253
# First, handle any literal newlines in the replacement string
254254
# This is necessary because the escape_backslashes function might not catch all cases
255255
# of literal newlines, especially if they're in a string that's passed directly
256256
# rather than as a raw string.
257-
repl_with_escaped_newlines = repl.replace('\n', '\\n')
258-
257+
repl_with_escaped_newlines = repl.replace("\n", "\\n")
258+
259259
# Then process the string with the escape_backslashes function to handle other escape sequences
260260
processed_repl = escape_backslashes(repl_with_escaped_newlines)
261-
261+
262262
updated_content, n = re.subn(regex, processed_repl, original_content, flags=re.DOTALL | re.MULTILINE)
263263
if n == 0:
264264
return f"Error: No matches found for regex '{regex}' in file '{relative_path}'."
Lines changed: 58 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import os
22
import re
33
import tempfile
4-
from pathlib import Path
4+
55
import pytest
66

7-
from serena.tools.file_tools import ReplaceRegexTool
87
from serena.tools import SUCCESS_RESULT
8+
from serena.tools.file_tools import ReplaceRegexTool
99

1010

1111
class MockProject:
@@ -18,15 +18,16 @@ def validate_relative_path(self, path):
1818
return True
1919

2020
def read_file(self, path):
21-
with open(os.path.join(self.root_dir, path), 'r') as f:
21+
with open(os.path.join(self.root_dir, path)) as f:
2222
return f.read()
2323

2424
def is_ignored_path(self, path):
2525
return False
26-
26+
27+
2728
class MockProjectConfig:
2829
def __init__(self):
29-
self.encoding = 'utf-8'
30+
self.encoding = "utf-8"
3031

3132

3233
class MockAgent:
@@ -35,21 +36,22 @@ def __init__(self, project):
3536
self._active_project = project
3637
self.lines_read = None
3738
self.serena_config = None
38-
39+
3940
def get_active_project_or_raise(self):
4041
return self.project
41-
42+
4243
def get_active_project(self):
4344
return self.project
44-
45+
4546
def is_using_language_server(self):
4647
return False
47-
48+
4849
def record_tool_usage_if_enabled(self, *args, **kwargs):
4950
pass
50-
51+
5152
def issue_task(self, task, name):
5253
from concurrent.futures import Future
54+
5355
future = Future()
5456
future.set_result(task())
5557
return future
@@ -69,73 +71,73 @@ def setup(self):
6971
# Setup mock project and agent
7072
project = MockProject(temp_dir)
7173
agent = MockAgent(project)
72-
74+
7375
# Create the tool
7476
tool = ReplaceRegexTool(agent)
75-
77+
7678
yield temp_dir, "test_file.py", tool
7779

7880
def test_replace_with_newline(self, setup):
7981
"""Test replacing text with a string containing newline escape sequences."""
8082
temp_dir, test_file_path, tool = setup
81-
83+
8284
# Replace text with a string containing \n
8385
result = tool.apply(
8486
relative_path=test_file_path,
8587
regex=r"print\(f'Some text'\)",
8688
repl=r"print(f'\n Confidence Calculation Breakdown:')",
8789
)
88-
90+
8991
assert result == SUCCESS_RESULT
90-
92+
9193
# Read the file and check the content
92-
with open(os.path.join(temp_dir, test_file_path), "r") as f:
94+
with open(os.path.join(temp_dir, test_file_path)) as f:
9395
content = f.read()
94-
96+
9597
# The newline should be preserved as \n, not converted to a literal newline
9698
assert "print(f'\\n Confidence Calculation Breakdown:')" in content
9799
assert "print(f'\n Confidence" not in content
98-
100+
99101
def test_replace_with_tab(self, setup):
100102
"""Test replacing text with a string containing tab escape sequences."""
101103
temp_dir, test_file_path, tool = setup
102-
104+
103105
# Replace text with a string containing \t
104106
result = tool.apply(
105107
relative_path=test_file_path,
106108
regex=r"print\(f'Some text'\)",
107109
repl=r"print(f'\t Indented text')",
108110
)
109-
111+
110112
assert result == SUCCESS_RESULT
111-
113+
112114
# Read the file and check the content
113-
with open(os.path.join(temp_dir, test_file_path), "r") as f:
115+
with open(os.path.join(temp_dir, test_file_path)) as f:
114116
content = f.read()
115-
117+
116118
# The tab should be preserved as \t, not converted to a literal tab
117119
assert "print(f'\\t Indented text')" in content
118-
120+
119121
def test_replace_with_multiple_escapes(self, setup):
120122
"""Test replacing text with a string containing multiple escape sequences."""
121123
temp_dir, test_file_path, tool = setup
122-
124+
123125
# Replace text with a string containing multiple escape sequences
124126
result = tool.apply(
125127
relative_path=test_file_path,
126128
regex=r"print\(f'Some text'\)",
127129
repl=r"print(f'\n\t\r Multiple escapes')",
128130
)
129-
131+
130132
assert result == SUCCESS_RESULT
131-
133+
132134
# Read the file and check the content
133-
with open(os.path.join(temp_dir, test_file_path), "r") as f:
135+
with open(os.path.join(temp_dir, test_file_path)) as f:
134136
content = f.read()
135-
137+
136138
# All escape sequences should be preserved
137139
assert "print(f'\\n\\t\\r Multiple escapes')" in content
138-
140+
139141
def test_replace_with_backreferences(self, setup):
140142
"""Test replacing text with backreferences."""
141143
temp_dir, test_file_path, tool = setup
@@ -156,94 +158,94 @@ def test_replace_with_backreferences(self, setup):
156158
assert result == SUCCESS_RESULT
157159

158160
# Read the file and check the content
159-
with open(backrefs_file, "r") as f:
161+
with open(backrefs_file) as f:
160162
content = f.read()
161163

162164
# Backreferences should work and newlines should be preserved
163165
assert "def function_name(arg1, arg2):" in content
164166
assert " # Function with arguments: arg1, arg2" in content
165167
assert " # Returns the sum" in content
166-
168+
167169
def test_simulated_api_call(self, setup):
168170
"""Test that simulates how the tool is called through the API."""
169171
temp_dir, test_file_path, tool = setup
170-
172+
171173
# Create a test file with an f-string
172174
fstring_file = os.path.join(temp_dir, "fstring.py")
173175
with open(fstring_file, "w") as f:
174176
f.write("print(f'Some text')\n")
175-
177+
176178
# This is how the replacement string might look when it comes from JSON
177179
# The \n is already escaped once in the JSON string
178180
json_style_repl = "print(f'\\n Confidence Calculation Breakdown:')"
179-
181+
180182
# Apply the replacement
181183
result = tool.apply(
182184
relative_path="fstring.py",
183185
regex=r"print\(f'Some text'\)",
184186
repl=json_style_repl,
185187
)
186-
188+
187189
assert result == SUCCESS_RESULT
188-
190+
189191
# Read the file and check the content
190-
with open(fstring_file, "r") as f:
192+
with open(fstring_file) as f:
191193
content = f.read()
192-
194+
193195
# The newline should be preserved as \n, not converted to a literal newline
194196
assert "print(f'\\n Confidence Calculation Breakdown:')" in content
195197
# This is what we don't want to see - a literal newline in the string
196198
assert "print(f'\n Confidence" not in content
197-
199+
198200
def test_real_world_scenario(self, setup):
199201
"""Test that simulates the real-world scenario where the issue occurs."""
200202
temp_dir, test_file_path, tool = setup
201-
203+
202204
# Create a test file with an indented f-string (similar to the issue description)
203205
fstring_file = os.path.join(temp_dir, "real_world.py")
204206
with open(fstring_file, "w") as f:
205207
f.write(" print(f'Some text')\n")
206-
208+
207209
# This simulates how the replacement string might be processed in production
208210
# The issue description shows the newline being interpreted literally
209211
repl = "print(f'\n Confidence Calculation Breakdown:')"
210-
212+
211213
# Apply the replacement directly using re.sub to simulate what might be happening
212214
# in production without the double-escaping
213-
with open(fstring_file, "r") as f:
215+
with open(fstring_file) as f:
214216
content = f.read()
215-
217+
216218
# This is what might be happening in production - direct substitution without proper escaping
217219
broken_content = re.sub(r"print\(f'Some text'\)", repl, content, flags=re.DOTALL | re.MULTILINE)
218-
220+
219221
with open(fstring_file, "w") as f:
220222
f.write(broken_content)
221-
223+
222224
# Read the file and check if it contains the broken string (literal newline)
223-
with open(fstring_file, "r") as f:
225+
with open(fstring_file) as f:
224226
content = f.read()
225-
227+
226228
# This should be true if the issue is reproduced - the string is broken across lines
227229
assert "print(f'\n Confidence" in content
228-
230+
229231
# Now fix the file and try with our tool
230232
with open(fstring_file, "w") as f:
231233
f.write(" print(f'Some text')\n")
232-
234+
233235
# Apply the replacement using our tool
234236
result = tool.apply(
235237
relative_path="real_world.py",
236238
regex=r"print\(f'Some text'\)",
237239
repl=r"print(f'\n Confidence Calculation Breakdown:')",
238240
)
239-
241+
240242
assert result == SUCCESS_RESULT
241-
243+
242244
# Read the file and check the content
243-
with open(fstring_file, "r") as f:
245+
with open(fstring_file) as f:
244246
content = f.read()
245-
247+
246248
# The newline should be preserved as \n, not converted to a literal newline
247249
assert "print(f'\\n Confidence Calculation Breakdown:')" in content
248250
# This is what we don't want to see - a literal newline in the string
249-
assert "print(f'\n Confidence" not in content
251+
assert "print(f'\n Confidence" not in content

0 commit comments

Comments
 (0)