Skip to content

Commit 3a20d20

Browse files
authored
Added line only matching option to XMLTransformer (#733)
1 parent 5f4f575 commit 3a20d20

File tree

2 files changed

+87
-7
lines changed

2 files changed

+87
-7
lines changed

src/codemodder/codemods/xml_transformer.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ def __init__(
3030
encoding: str = "utf-8",
3131
short_empty_elements: bool = False,
3232
results: list[Result] | None = None,
33+
line_only_matching=False,
3334
) -> None:
3435
self.file_context = file_context
3536
self.results = results
3637
self.changes: list[Change] = []
3738
self._my_locator = Locator()
39+
self.line_only_matching = line_only_matching
3840
super().__init__(out, encoding, short_empty_elements)
3941

4042
def startElement(self, name, attrs):
@@ -81,10 +83,15 @@ def match_result(self, line, column) -> bool:
8183
return True
8284
for result in self.results or []:
8385
for location in result.locations:
84-
# No two elements can have the same start but different ends.
85-
# It suffices to only match the start.
86-
if location.start.line == line and location.start.column - 1 == column:
87-
return True
86+
if self.line_only_matching:
87+
return location.start.line == line
88+
else:
89+
# No two elements can have the same start but different ends.
90+
# It suffices to only match the start.
91+
return (
92+
location.start.line == line
93+
and location.start.column - 1 == column
94+
)
8895
return False
8996

9097
def add_change(self, line):
@@ -110,9 +117,17 @@ def __init__(
110117
encoding: str = "utf-8",
111118
short_empty_elements: bool = False,
112119
results: list[Result] | None = None,
120+
line_only_matching=False,
113121
) -> None:
114122
self.name_attributes_map = name_attributes_map
115-
super().__init__(out, file_context, encoding, short_empty_elements, results)
123+
super().__init__(
124+
out,
125+
file_context,
126+
encoding,
127+
short_empty_elements,
128+
results,
129+
line_only_matching,
130+
)
116131

117132
def startElement(self, name, attrs):
118133
new_attrs: AttributesImpl = attrs

tests/test_xml_transformer.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
NewElementXMLTransformer,
1414
XMLTransformer,
1515
)
16+
from codemodder.semgrep import SemgrepResult
1617

1718

1819
class TestXMLTransformer:
@@ -56,10 +57,21 @@ def test_parse_cdata(self):
5657

5758
class TestElementAttributeXMLTransformer:
5859

59-
def run_and_assert(self, name_attr_map, input_code, expected_output):
60+
def run_and_assert(
61+
self,
62+
name_attr_map,
63+
input_code,
64+
expected_output,
65+
results=None,
66+
line_only_match=False,
67+
):
6068
with StringIO() as result, StringIO(dedent(input_code)) as input_stream:
6169
transformer = ElementAttributeXMLTransformer(
62-
result, mock.MagicMock(), name_attributes_map=name_attr_map
70+
result,
71+
mock.MagicMock(),
72+
name_attributes_map=name_attr_map,
73+
results=results,
74+
line_only_matching=line_only_match,
6375
)
6476
parser = make_parser()
6577
parser.setContentHandler(transformer)
@@ -97,6 +109,59 @@ def test_change_multiple_attr_and_preserve_existing(self):
97109
name_attr_map = {"element": {"first": "one", "second": "two"}}
98110
self.run_and_assert(name_attr_map, input_code, expected_output)
99111

112+
def test_change_only_line_matching_result(self):
113+
input_code = """\
114+
<?xml version="1.0" encoding="utf-8"?>
115+
<configuration>
116+
<element first="1">
117+
</element>
118+
<element first="1">
119+
</element>
120+
</configuration>"""
121+
expected_output = """\
122+
<?xml version="1.0" encoding="utf-8"?>
123+
<configuration>
124+
<element first="1">
125+
</element>
126+
<element first="one">
127+
</element>
128+
</configuration>"""
129+
name_attr_map = {"element": {"first": "one"}}
130+
data = {
131+
"runs": [
132+
{
133+
"results": [
134+
{
135+
"fingerprints": {"matchBasedId/v1": "123"},
136+
"locations": [
137+
{
138+
"ruleId": "rule",
139+
"physicalLocation": {
140+
"artifactLocation": {
141+
"uri": "code.py",
142+
"uriBaseId": "%SRCROOT%",
143+
},
144+
"region": {
145+
"snippet": {"text": "snip"},
146+
"endColumn": 1,
147+
"endLine": 5,
148+
"startColumn": 1,
149+
"startLine": 5,
150+
},
151+
},
152+
}
153+
],
154+
"ruleId": "rule",
155+
}
156+
]
157+
}
158+
]
159+
}
160+
sarif_run = data["runs"]
161+
sarif_results = sarif_run[0]["results"]
162+
results = [SemgrepResult.from_sarif(sarif_results[0], sarif_run)]
163+
self.run_and_assert(name_attr_map, input_code, expected_output, results, True)
164+
100165

101166
class TestNewElementXMLTransformer:
102167

0 commit comments

Comments
 (0)