Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 63 additions & 3 deletions mutmut/node_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,16 @@ def operator_arg_removal(
("rjust", "ljust"),
("index", "rindex"),
("rindex", "index"),
("split", "rsplit"),
("rsplit", "split"),
("removeprefix", "removesuffix"),
("removesuffix", "removeprefix"),
("partition", "rpartition"),
("rpartition", "partition")
]
]

supported_unsymmetrical_str_methods_swap = [
("split", "rsplit"),
("rsplit", "split")
]

def operator_string_methods_swap(
node: cst.Call
Expand All @@ -125,6 +128,62 @@ def operator_string_methods_swap(
func_name = cst.ensure_type(node.func, cst.Attribute).attr
yield node.with_deep_changes(func_name, value=new_call)

def unsymmetrical_string_methods_swap(
node: cst.Call
) -> Iterable[cst.Call]:
"""Try to handle specific mutations of string, which useful only in specific args combination."""

for old_call, new_call in supported_unsymmetrical_str_methods_swap:
if m.matches(node.func, m.Attribute(value=m.DoNotCare(), attr=m.Name(value=old_call))):
if old_call in {"split", "rsplit"}:
# split() -> split() # not len(node.args)
# rsplit() -> rsplit() # not len(node.args)
# split(" ") -> split(" ") # len(node.args) == 1 & maxsplit not in args
# rsplit(" ") -> rsplit(" ") # len(node.args) == 1 & maxsplit not in args
# split(sep=" ") -> split(sep=" ") # len(node.args) == 1 & maxsplit not in args
# rsplit(sep=" ") -> rsplit(sep=" ") # len(node.args) == 1 & maxsplit not in args
# split(maxsplit=-1) -> split(maxsplit=-1) # maxsplit=-1 in args
# rsplit(maxsplit=-1) -> rsplit(maxsplit=-1) # maxsplit=-1 in args
# split(" ", maxsplit=-1) -> split(" ", maxsplit=-1) # maxsplit=-1 in args
# rsplit(" ", maxsplit=-1) -> rsplit(" ", maxsplit=-1) # maxsplit=-1 in args

# split(maxsplit=1) -> rsplit(maxsplit=1) # maxsplit in args and maxsplit != -1
# rsplit(maxsplit=1) -> split(maxsplit=1) # maxsplit in args and maxsplit != -1

# split(" ", 1) -> rsplit(" ", 1) # len(node.args) == 2 & maxsplit not in args
# rsplit(" ", 1) -> split(" ", 1) # len(node.args) == 2 & maxsplit not in args

# split(" ", maxsplit=1) -> rsplit(" ", maxsplit=1) # maxsplit in args and maxsplit != -1
# rsplit(" ", maxsplit=1) -> split(" ", maxsplit=1) # maxsplit in args and maxsplit != -1
key_args: set[str] = {a.keyword.value for a in node.args if a.keyword}

maxsplit_val = None
for a in node.args:
if a.keyword and a.keyword.value == "maxsplit":
if any([isinstance(child, cst.Minus) for child in a.value.children]):
maxsplit_val = int(a.value.expression.value) * -1
break
if any([isinstance(child, cst.Plus) for child in a.value.children]):
maxsplit_val = int(a.value.expression.value)
break
maxsplit_val = a.value.evaluated_value


if (
not len(node.args) or
(len(node.args) == 1 and "maxsplit" not in key_args) or
("maxsplit" in key_args and maxsplit_val == -1)
):
continue

if (
(len(node.args) == 2 and "maxsplit" not in key_args) or
("maxsplit" in key_args and maxsplit_val != -1)
):
func_name = cst.ensure_type(node.func, cst.Attribute).attr
yield node.with_deep_changes(func_name, value=new_call)



def operator_remove_unary_ops(
node: cst.UnaryOperation
Expand Down Expand Up @@ -239,6 +298,7 @@ def operator_match(node: cst.Match) -> Iterable[cst.CSTNode]:
(cst.Call, operator_dict_arguments),
(cst.Call, operator_arg_removal),
(cst.Call, operator_string_methods_swap),
(cst.Call, unsymmetrical_string_methods_swap),
(cst.Lambda, operator_lambda),
(cst.CSTNode, operator_keywords),
(cst.CSTNode, operator_swap_op),
Expand Down
39 changes: 37 additions & 2 deletions tests/test_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,43 @@ def mutated_module(source: str) -> str:
]),
('a.index("+")', ['a.rindex("+")', 'a.index("XX+XX")', 'a.index(None)']),
('a.rindex("+")', ['a.index("+")', 'a.rindex("XX+XX")', 'a.rindex(None)']),
('a.split()', 'a.rsplit()'),
('a.rsplit()', 'a.split()'),
('a.split()', []),
('a.rsplit()', []),
('a.split(" ")', ['a.split("XX XX")', 'a.split(None)']),
('a.rsplit(" ")', ['a.rsplit("XX XX")', 'a.rsplit(None)']),
('a.split(sep="")', ['a.split(sep="XXXX")', 'a.split(sep=None)']),
('a.rsplit(sep="")', ['a.rsplit(sep="XXXX")', 'a.rsplit(sep=None)']),
('a.split(maxsplit=-1)', ['a.split(maxsplit=+1)', 'a.split(maxsplit=-2)', 'a.split(maxsplit=None)']),
('a.rsplit(maxsplit=-1)', ['a.rsplit(maxsplit=+1)', 'a.rsplit(maxsplit=-2)', 'a.rsplit(maxsplit=None)']),
('a.split(" ", maxsplit=-1)', [
'a.split(" ", )', 'a.split(" ", maxsplit=+1)', 'a.split(" ", maxsplit=-2)',
'a.split(" ", maxsplit=None)', 'a.split("XX XX", maxsplit=-1)', 'a.split(None, maxsplit=-1)',
'a.split(maxsplit=-1)'
]),
('a.rsplit(" ", maxsplit=-1)', [
'a.rsplit(" ", )', 'a.rsplit(" ", maxsplit=+1)', 'a.rsplit(" ", maxsplit=-2)',
'a.rsplit(" ", maxsplit=None)', 'a.rsplit("XX XX", maxsplit=-1)', 'a.rsplit(None, maxsplit=-1)',
'a.rsplit(maxsplit=-1)'
]),
('a.split(maxsplit=1)', ['a.split(maxsplit=2)', 'a.split(maxsplit=None)', 'a.rsplit(maxsplit=1)']),
('a.rsplit(maxsplit=1)', ['a.rsplit(maxsplit=2)', 'a.rsplit(maxsplit=None)', 'a.split(maxsplit=1)']),
('a.split(" ", 1)', [
'a.rsplit(" ", 1)', 'a.split(" ", )', 'a.split(" ", 2)', 'a.split(" ", None)',
'a.split("XX XX", 1)', 'a.split(1)', 'a.split(None, 1)'
]),
('a.rsplit(" ", 1)', [
'a.rsplit(" ", )', 'a.rsplit(" ", 2)', 'a.rsplit(" ", None)', 'a.rsplit("XX XX", 1)',
'a.rsplit(1)', 'a.rsplit(None, 1)', 'a.split(" ", 1)'
]),
('a.split(" ", maxsplit=1)', [
'a.rsplit(" ", maxsplit=1)', 'a.split(" ", )', 'a.split(" ", maxsplit=2)', 'a.split(" ", maxsplit=None)',
'a.split("XX XX", maxsplit=1)', 'a.split(None, maxsplit=1)', 'a.split(maxsplit=1)'
]),
('a.rsplit(" ", maxsplit=1)', [
'a.rsplit(" ", )', 'a.rsplit(" ", maxsplit=2)', 'a.rsplit(" ", maxsplit=None)',
'a.rsplit("XX XX", maxsplit=1)', 'a.rsplit(None, maxsplit=1)', 'a.rsplit(maxsplit=1)',
'a.split(" ", maxsplit=1)'
]),
('a.removeprefix("+")', ['a.removesuffix("+")', 'a.removeprefix("XX+XX")', 'a.removeprefix(None)']),
('a.removesuffix("+")', ['a.removeprefix("+")', 'a.removesuffix("XX+XX")', 'a.removesuffix(None)']),
('a.partition("++")', ['a.rpartition("++")', 'a.partition("XX++XX")', 'a.partition(None)']),
Expand Down