Skip to content

Commit

Permalink
Add option to keep useless pass statements (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarotroya committed Sep 18, 2022
1 parent b71e395 commit d389870
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 14 deletions.
6 changes: 5 additions & 1 deletion README.md
Expand Up @@ -12,7 +12,7 @@ the standard library. (Other modules may have side effects that make them
unsafe to remove automatically.) Removal of unused variables is also disabled
by default.

autoflake also removes useless ``pass`` statements.
autoflake also removes useless ``pass`` statements by default.

## Example

Expand Down Expand Up @@ -106,6 +106,10 @@ options:
remove all unused imports (not just those from the standard library)
--ignore-init-module-imports
exclude __init__.py when removing unused imports
--ignore-pass-statements
keep all `pass` statements
--ignore-pass-after-docstring
keep `pass` statements after a new line ending on """
--remove-duplicate-keys
remove all duplicate keys in objects
--remove-unused-variables
Expand Down
64 changes: 52 additions & 12 deletions autoflake.py
Expand Up @@ -743,7 +743,10 @@ def is_literal_or_name(value):
return re.match(r"^\w+\s*$", value)


def useless_pass_line_numbers(source):
def useless_pass_line_numbers(
source,
ignore_pass_after_docstring=False,
):
"""Yield line numbers of unneeded "pass" statements."""
sio = io.StringIO(source)
previous_token_type = None
Expand All @@ -770,24 +773,45 @@ def useless_pass_line_numbers(source):
last_pass_row = start_row
last_pass_indentation = get_indentation(line)

# Trailing "pass".
if (
is_pass
and previous_token_type != tokenize.INDENT
and not previous_line.rstrip().endswith("\\")
):
yield start_row
is_trailing_pass = (
previous_token_type != tokenize.INDENT
and not previous_line.rstrip().endswith("\\")
)

is_pass_after_docstring = (
previous_token_type == tokenize.NEWLINE
and previous_line.rstrip().endswith('"""')
)

# Trailing "pass".
if is_trailing_pass:
if is_pass_after_docstring and ignore_pass_after_docstring:
continue
else:
yield start_row

previous_token_type = token_type
previous_line = line


def filter_useless_pass(source):
def filter_useless_pass(
source,
ignore_pass_statements=False,
ignore_pass_after_docstring=False,
):
"""Yield code with useless "pass" lines removed."""
try:
marked_lines = frozenset(useless_pass_line_numbers(source))
except (SyntaxError, tokenize.TokenError):
if ignore_pass_statements:
marked_lines = frozenset()
else:
try:
marked_lines = frozenset(
useless_pass_line_numbers(
source,
ignore_pass_after_docstring,
),
)
except (SyntaxError, tokenize.TokenError):
marked_lines = frozenset()

sio = io.StringIO(source)
for line_number, line in enumerate(sio.readlines(), start=1):
Expand Down Expand Up @@ -822,6 +846,8 @@ def fix_code(
remove_unused_variables=False,
remove_rhs_for_unused_variables=False,
ignore_init_module_imports=False,
ignore_pass_statements=False,
ignore_pass_after_docstring=False,
):
"""Return code with all filtering run on it."""
if not source:
Expand Down Expand Up @@ -849,6 +875,8 @@ def fix_code(
ignore_init_module_imports=ignore_init_module_imports,
),
),
ignore_pass_statements=ignore_pass_statements,
ignore_pass_after_docstring=ignore_pass_after_docstring,
),
)

Expand Down Expand Up @@ -902,6 +930,8 @@ def _fix_file(
remove_unused_variables=args["remove_unused_variables"],
remove_rhs_for_unused_variables=(args["remove_rhs_for_unused_variables"]),
ignore_init_module_imports=ignore_init_module_imports,
ignore_pass_statements=args["ignore_pass_statements"],
ignore_pass_after_docstring=args["ignore_pass_after_docstring"],
)

if original_source != filtered_source:
Expand Down Expand Up @@ -1268,6 +1298,16 @@ def _main(argv, standard_out, standard_error, standard_input=None) -> int:
action="store_true",
help="remove RHS of statements when removing unused " "variables (unsafe)",
)
parser.add_argument(
"--ignore-pass-statements",
action="store_true",
help="ignore all pass statements",
)
parser.add_argument(
"--ignore-pass-after-docstring",
action="store_true",
help='ignore pass statements after a newline ending on \'"""\'',
)
parser.add_argument(
"--version",
action="version",
Expand Down
146 changes: 145 additions & 1 deletion test_autoflake.py
Expand Up @@ -1412,6 +1412,72 @@ def test_fix_code_should_ignore_duplicate_key_with_no_comma(self):
),
)

def test_fix_code_keeps_pass_statements(self):
code = """\
if True:
pass
else:
def foo():
\"\"\" A docstring. \"\"\"
pass
def bar():
# abc
pass
def blah():
123
pass
pass # Nope.
pass
"""

self.assertEqual(
code,
"".join(
autoflake.fix_code(
code,
ignore_pass_statements=True,
),
),
)

def test_fix_code_keeps_passes_after_docstrings(self):
actual = autoflake.fix_code(
"""\
if True:
pass
else:
def foo():
\"\"\" A docstring. \"\"\"
pass
def bar():
# abc
pass
def blah():
123
pass
pass # Nope.
pass
""",
ignore_pass_after_docstring=True,
)

expected = """\
if True:
pass
else:
def foo():
\"\"\" A docstring. \"\"\"
pass
def bar():
# abc
pass
def blah():
123
pass # Nope.
"""

self.assertEqual(actual, expected)

def test_useless_pass_line_numbers(self):
self.assertEqual(
[1],
Expand Down Expand Up @@ -1458,6 +1524,37 @@ def test_useless_pass_line_numbers_with_more_complex(self):
),
)

def test_useless_pass_line_numbers_after_docstring(self):
actual_pass_line_numbers = list(
autoflake.useless_pass_line_numbers(
"""\
@abc.abstractmethod
def some_abstract_method():
\"\"\"Some docstring.\"\"\"
pass
""",
),
)

expected_pass_line_numbers = [4]
self.assertEqual(expected_pass_line_numbers, actual_pass_line_numbers)

def test_useless_pass_line_numbers_keep_pass_after_docstring(self):
actual_pass_line_numbers = list(
autoflake.useless_pass_line_numbers(
"""\
@abc.abstractmethod
def some_abstract_method():
\"\"\"Some docstring.\"\"\"
pass
""",
ignore_pass_after_docstring=True,
),
)

expected_pass_line_numbers = []
self.assertEqual(expected_pass_line_numbers, actual_pass_line_numbers)

def test_filter_useless_pass(self):
self.assertEqual(
"""\
Expand Down Expand Up @@ -1545,7 +1642,54 @@ def blah():
),
)

def test_filter_useless_pass_with_try(self):
def test_filter_useless_pass_keep_pass_after_docstring(self):
source = """\
def foo():
\"\"\" This is not a useless 'pass'. \"\"\"
pass
@abc.abstractmethod
def bar():
\"\"\"
Also this is not a useless 'pass'.
\"\"\"
pass
"""
self.assertEqual(
source,
"".join(
autoflake.filter_useless_pass(
source,
ignore_pass_after_docstring=True,
),
),
)

def test_filter_useless_pass_keeps_pass_statements(self):
source = """\
if True:
pass
pass
pass
pass
else:
pass
True
x = 1
pass
"""

self.assertEqual(
source,
"".join(
autoflake.filter_useless_pass(
source,
ignore_pass_statements=True,
),
),
)

def test_filter_useless_paspasss_with_try(self):
self.assertEqual(
"""\
import os
Expand Down

0 comments on commit d389870

Please sign in to comment.