Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to keep pass statements #143

Merged
merged 1 commit into from Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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