Skip to content

Update tabnanny.py from 3.13.5 #6021

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

Merged
merged 1 commit into from
Jul 23, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions Lib/tabnanny.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
import os
import sys
import tokenize
if not hasattr(tokenize, 'NL'):
raise ValueError("tokenize.NL doesn't exist -- tokenize module too old")

__all__ = ["check", "NannyNag", "process_tokens"]

Expand All @@ -37,6 +35,7 @@ def errprint(*args):
sys.stderr.write(sep + str(arg))
sep = " "
sys.stderr.write("\n")
sys.exit(1)

def main():
import getopt
Expand All @@ -46,15 +45,13 @@ def main():
opts, args = getopt.getopt(sys.argv[1:], "qv")
except getopt.error as msg:
errprint(msg)
return
for o, a in opts:
if o == '-q':
filename_only = filename_only + 1
if o == '-v':
verbose = verbose + 1
if not args:
errprint("Usage:", sys.argv[0], "[-v] file_or_directory ...")
return
for arg in args:
check(arg)

Expand Down Expand Up @@ -114,6 +111,10 @@ def check(file):
errprint("%r: Indentation Error: %s" % (file, msg))
return

except SyntaxError as msg:
errprint("%r: Syntax Error: %s" % (file, msg))
return

except NannyNag as nag:
badline = nag.get_lineno()
line = nag.get_line()
Expand Down Expand Up @@ -275,6 +276,12 @@ def format_witnesses(w):
return prefix + " " + ', '.join(firsts)

def process_tokens(tokens):
try:
_process_tokens(tokens)
except TabError as e:
raise NannyNag(e.lineno, e.msg, e.text)

def _process_tokens(tokens):
INDENT = tokenize.INDENT
DEDENT = tokenize.DEDENT
NEWLINE = tokenize.NEWLINE
Expand Down
58 changes: 34 additions & 24 deletions Lib/test/test_tabnanny.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
findfile)
from test.support.os_helper import unlink

import unittest # XXX: RUSTPYTHON
import unittest # TODO: RUSTPYTHON


SOURCE_CODES = {
Expand Down Expand Up @@ -112,9 +112,10 @@ def test_errprint(self):

for args, expected in tests:
with self.subTest(arguments=args, expected=expected):
with captured_stderr() as stderr:
tabnanny.errprint(*args)
self.assertEqual(stderr.getvalue() , expected)
with self.assertRaises(SystemExit):
with captured_stderr() as stderr:
tabnanny.errprint(*args)
self.assertEqual(stderr.getvalue() , expected)


class TestNannyNag(TestCase):
Expand Down Expand Up @@ -199,53 +200,54 @@ def test_correct_directory(self):
with TemporaryPyFile(SOURCE_CODES["error_free"], directory=tmp_dir):
self.verify_tabnanny_check(tmp_dir)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_when_wrong_indented(self):
"""A python source code file eligible for raising `IndentationError`."""
with TemporaryPyFile(SOURCE_CODES["wrong_indented"]) as file_path:
err = ('unindent does not match any outer indentation level'
' (<tokenize>, line 3)\n')
err = f"{file_path!r}: Indentation Error: {err}"
self.verify_tabnanny_check(file_path, err=err)
with self.assertRaises(SystemExit):
self.verify_tabnanny_check(file_path, err=err)

def test_when_tokenize_tokenerror(self):
"""A python source code file eligible for raising 'tokenize.TokenError'."""
with TemporaryPyFile(SOURCE_CODES["incomplete_expression"]) as file_path:
err = "('EOF in multi-line statement', (7, 0))\n"
err = f"{file_path!r}: Token Error: {err}"
self.verify_tabnanny_check(file_path, err=err)
with self.assertRaises(SystemExit):
self.verify_tabnanny_check(file_path, err=err)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_when_nannynag_error_verbose(self):
"""A python source code file eligible for raising `tabnanny.NannyNag`.

Tests will assert `stdout` after activating `tabnanny.verbose` mode.
"""
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as file_path:
out = f"{file_path!r}: *** Line 3: trouble in tab city! ***\n"
out += "offending line: '\\tprint(\"world\")\\n'\n"
out += "indent not equal e.g. at tab size 1\n"
out += "offending line: '\\tprint(\"world\")'\n"
out += "inconsistent use of tabs and spaces in indentation\n"

tabnanny.verbose = 1
self.verify_tabnanny_check(file_path, out=out)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_when_nannynag_error(self):
"""A python source code file eligible for raising `tabnanny.NannyNag`."""
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as file_path:
out = f"{file_path} 3 '\\tprint(\"world\")\\n'\n"
out = f"{file_path} 3 '\\tprint(\"world\")'\n"
self.verify_tabnanny_check(file_path, out=out)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_when_no_file(self):
"""A python file which does not exist actually in system."""
path = 'no_file.py'
err = (f"{path!r}: I/O Error: [Errno {errno.ENOENT}] "
f"{os.strerror(errno.ENOENT)}: {path!r}\n")
self.verify_tabnanny_check(path, err=err)
with self.assertRaises(SystemExit):
self.verify_tabnanny_check(path, err=err)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_errored_directory(self):
"""Directory containing wrongly indented python source code files."""
with tempfile.TemporaryDirectory() as tmp_dir:
Expand All @@ -259,7 +261,8 @@ def test_errored_directory(self):
err = ('unindent does not match any outer indentation level'
' (<tokenize>, line 3)\n')
err = f"{e_file!r}: Indentation Error: {err}"
self.verify_tabnanny_check(tmp_dir, err=err)
with self.assertRaises(SystemExit):
self.verify_tabnanny_check(tmp_dir, err=err)


class TestProcessTokens(TestCase):
Expand Down Expand Up @@ -295,9 +298,12 @@ def test_with_errored_codes_samples(self):
class TestCommandLine(TestCase):
"""Tests command line interface of `tabnanny`."""

def validate_cmd(self, *args, stdout="", stderr="", partial=False):
def validate_cmd(self, *args, stdout="", stderr="", partial=False, expect_failure=False):
"""Common function to assert the behaviour of command line interface."""
_, out, err = script_helper.assert_python_ok('-m', 'tabnanny', *args)
if expect_failure:
_, out, err = script_helper.assert_python_failure('-m', 'tabnanny', *args)
else:
_, out, err = script_helper.assert_python_ok('-m', 'tabnanny', *args)
# Note: The `splitlines()` will solve the problem of CRLF(\r) added
# by OS Windows.
out = os.fsdecode(out)
Expand All @@ -319,8 +325,8 @@ def test_with_errored_file(self):
with TemporaryPyFile(SOURCE_CODES["wrong_indented"]) as file_path:
stderr = f"{file_path!r}: Indentation Error: "
stderr += ('unindent does not match any outer indentation level'
' (<tokenize>, line 3)')
self.validate_cmd(file_path, stderr=stderr)
' (<string>, line 3)')
self.validate_cmd(file_path, stderr=stderr, expect_failure=True)

def test_with_error_free_file(self):
"""Should not display anything if python file is correctly indented."""
Expand All @@ -331,26 +337,30 @@ def test_command_usage(self):
"""Should display usage on no arguments."""
path = findfile('tabnanny.py')
stderr = f"Usage: {path} [-v] file_or_directory ..."
self.validate_cmd(stderr=stderr)
self.validate_cmd(stderr=stderr, expect_failure=True)

def test_quiet_flag(self):
"""Should display less when quite mode is on."""
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as file_path:
stdout = f"{file_path}\n"
self.validate_cmd("-q", file_path, stdout=stdout)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_verbose_mode(self):
"""Should display more error information if verbose mode is on."""
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as path:
stdout = textwrap.dedent(
"offending line: '\\tprint(\"world\")\\n'"
"offending line: '\\tprint(\"world\")'"
).strip()
self.validate_cmd("-v", path, stdout=stdout, partial=True)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_double_verbose_mode(self):
"""Should display detailed error information if double verbose is on."""
with TemporaryPyFile(SOURCE_CODES["nannynag_errored"]) as path:
stdout = textwrap.dedent(
"offending line: '\\tprint(\"world\")\\n'"
"offending line: '\\tprint(\"world\")'"
).strip()
self.validate_cmd("-vv", path, stdout=stdout, partial=True)
Loading