diff --git a/pyproject.toml b/pyproject.toml index f870176..bc637c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "salt-rewrite" -version = "1.3.3" +version = "2.0.0" description = "A set of Bowler code to rewrite parts of Salt" authors = ["Pedro Algarvio "] license = "Apache-2.0" diff --git a/src/saltrewrite/salt/__init__.py b/src/saltrewrite/salt/__init__.py index 44890c9..4c05d48 100644 --- a/src/saltrewrite/salt/__init__.py +++ b/src/saltrewrite/salt/__init__.py @@ -1,5 +1,6 @@ # pylint: disable=missing-module-docstring from saltrewrite.salt import fix_docstrings +from saltrewrite.salt import fix_dunder_utils __all__ = [modname for modname in list(globals()) if modname.startswith("fix_")] diff --git a/src/saltrewrite/salt/fix_dunder_utils.py b/src/saltrewrite/salt/fix_dunder_utils.py new file mode 100644 index 0000000..eeccd74 --- /dev/null +++ b/src/saltrewrite/salt/fix_dunder_utils.py @@ -0,0 +1,89 @@ +""" + saltrewrite.salt.fix_docstrings + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @todo: add description +""" +import logging + +from bowler import Query +from bowler import SYMBOL +from bowler import TOKEN +from bowler.types import Leaf +from bowler.types import Node +from fissix.fixer_util import Call +from fissix.fixer_util import Dot +from fissix.fixer_util import touch_import + +log = logging.getLogger(__name__) + + +def rewrite(paths, interactive=False, silent=False): + """ + Rewrite the passed in paths + """ + ( + Query(paths) + .select( + """ + ( + dunder_call=power< + '__utils__' + trailer< '[' dunder_mod_func=any* ']' > + trailer< '(' function_arguments=any* ')' > + > + + ) + """ + ) + .modify(fix_module_docstrings) + .execute(write=True, interactive=interactive, silent=silent) + ) + + +def fix_module_docstrings(node, capture, filename): + """ + Automaticaly run fixes against docstrings + """ + if "dunder_mod_func" not in capture: + return + dunder_mod_func = capture["dunder_mod_func"][0].value.strip("'").strip('"') + utils_module, utils_module_funcname = dunder_mod_func.split(".") + + # Make sure we import the right utils module + touch_import(None, f"salt.utils.{utils_module}", node) + log.info("Dunder Module Func: %r", dunder_mod_func) + + # Un-parent the function arguments so we can add them to a new call + for leaf in capture["function_arguments"]: + leaf.parent = None + + # Create the new function call + call_node = Call( + Leaf(TOKEN.NAME, utils_module_funcname, prefix=""), + capture["function_arguments"], + ) + + # Create replacement node + replacement = Node( + SYMBOL.power, + [ + Leaf(TOKEN.NAME, "salt", prefix=capture["node"].prefix), + Node( + SYMBOL.trailer, + [ + Dot(), + Leaf(TOKEN.NAME, "utils", prefix=""), + Dot(), + Leaf(TOKEN.NAME, utils_module, prefix=""), + Dot(), + call_node, + ], + ), + ], + ) + # Replace the whole node with the new function call + node.replace(replacement) + + +# pylint: enable=missing-function-docstring diff --git a/tests/salt/test_dunder_utils.py b/tests/salt/test_dunder_utils.py new file mode 100644 index 0000000..d03bc4a --- /dev/null +++ b/tests/salt/test_dunder_utils.py @@ -0,0 +1,144 @@ +# pylint: disable=missing-module-docstring,missing-function-docstring,too-many-lines +import textwrap + +from saltrewrite.salt import fix_dunder_utils + + +def test_fix_call_one_arg(tempfiles): + code = textwrap.dedent( + """ + import one.two + + def one(): + one.two.three("four") + __utils__["foo.bar"]("one") + """ + ) + expected_code = textwrap.dedent( + """ + import one.two + import salt.utils.foo + + def one(): + one.two.three("four") + salt.utils.foo.bar("one") + """ + ) + fpath = tempfiles.makepyfile(code, prefix="test_") + fix_dunder_utils.rewrite(fpath) + with open(fpath) as rfh: + new_code = rfh.read() + assert new_code == expected_code + + +def test_fix_call_multiple_args(tempfiles): + code = textwrap.dedent( + """ + import one.two + + def one(): + one.two.three("four") + __utils__["foo.bar"]("one", True, 1, 2.0) + """ + ) + expected_code = textwrap.dedent( + """ + import one.two + import salt.utils.foo + + def one(): + one.two.three("four") + salt.utils.foo.bar("one", True, 1, 2.0) + """ + ) + fpath = tempfiles.makepyfile(code, prefix="test_") + fix_dunder_utils.rewrite(fpath) + with open(fpath) as rfh: + new_code = rfh.read() + assert new_code == expected_code + + +def test_fix_call_keyword_arguments(tempfiles): + code = textwrap.dedent( + """ + import one.two + + def one(): + one.two.three("four") + __utils__["foo.bar"](one="one") + """ + ) + expected_code = textwrap.dedent( + """ + import one.two + import salt.utils.foo + + def one(): + one.two.three("four") + salt.utils.foo.bar(one="one") + """ + ) + fpath = tempfiles.makepyfile(code, prefix="test_") + fix_dunder_utils.rewrite(fpath) + with open(fpath) as rfh: + new_code = rfh.read() + assert new_code == expected_code + + +def test_fix_call_multiple_keyword_arguments(tempfiles): + code = textwrap.dedent( + """ + import one.two + + def one(): + one.two.three("four") + __utils__["foo.bar"]( + one="one", + two="two" + ) + """ + ) + expected_code = textwrap.dedent( + """ + import one.two + import salt.utils.foo + + def one(): + one.two.three("four") + salt.utils.foo.bar( + one="one", + two="two") + """ + ) + fpath = tempfiles.makepyfile(code, prefix="test_") + fix_dunder_utils.rewrite(fpath) + with open(fpath) as rfh: + new_code = rfh.read() + assert new_code == expected_code + + +def test_fix_call_mixed(tempfiles): + code = textwrap.dedent( + """ + import one.two + + def one(): + one.two.three("four") + __utils__["foo.bar"]("one", two="two") + """ + ) + expected_code = textwrap.dedent( + """ + import one.two + import salt.utils.foo + + def one(): + one.two.three("four") + salt.utils.foo.bar("one", two="two") + """ + ) + fpath = tempfiles.makepyfile(code, prefix="test_") + fix_dunder_utils.rewrite(fpath) + with open(fpath) as rfh: + new_code = rfh.read() + assert new_code == expected_code