| # Copyright (c) 2009-2011, 2014 LOGILAB S.A. (Paris, FRANCE) <[email protected]> |
| # Copyright (c) 2012 FELD Boris <[email protected]> |
| # Copyright (c) 2013-2014 Google, Inc. |
| # Copyright (c) 2014-2020 Claudiu Popa <[email protected]> |
| # Copyright (c) 2014 buck <[email protected]> |
| # Copyright (c) 2014 Arun Persaud <[email protected]> |
| # Copyright (c) 2015 Harut <[email protected]> |
| # Copyright (c) 2015 Ionel Cristian Maries <[email protected]> |
| # Copyright (c) 2016 Petr Pulc <[email protected]> |
| # Copyright (c) 2016 Derek Gustafson <[email protected]> |
| # Copyright (c) 2017 Krzysztof Czapla <[email protected]> |
| # Copyright (c) 2017 Łukasz Rogalski <[email protected]> |
| # Copyright (c) 2017 James M. Allen <[email protected]> |
| # Copyright (c) 2017 vinnyrose <[email protected]> |
| # Copyright (c) 2018, 2020 Bryce Guinta <[email protected]> |
| # Copyright (c) 2018 Bryce Guinta <[email protected]> |
| # Copyright (c) 2018, 2020 Anthony Sottile <[email protected]> |
| # Copyright (c) 2019-2021 Pierre Sassoulas <[email protected]> |
| # Copyright (c) 2019 Hugo van Kemenade <[email protected]> |
| # Copyright (c) 2019 Ashley Whetter <[email protected]> |
| # Copyright (c) 2020 hippo91 <[email protected]> |
| # Copyright (c) 2021 Daniël van Noord <[email protected]> |
| # Copyright (c) 2021 Marc Mueller <[email protected]> |
| # Copyright (c) 2021 Andreas Finkler <[email protected]> |
| |
| # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
| # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE |
| |
| """Check format checker helper functions""" |
| |
| import os |
| import tempfile |
| import tokenize |
| |
| import astroid |
| |
| from pylint import lint, reporters |
| from pylint.checkers.format import FormatChecker |
| from pylint.testutils import CheckerTestCase, Message, _tokenize_str |
| |
| |
| class TestMultiStatementLine(CheckerTestCase): |
| CHECKER_CLASS = FormatChecker |
| |
| def testSingleLineIfStmts(self): |
| stmt = astroid.extract_node( |
| """ |
| if True: pass #@ |
| """ |
| ) |
| self.checker.config.single_line_if_stmt = False |
| with self.assertAddsMessages(Message("multiple-statements", node=stmt.body[0])): |
| self.visitFirst(stmt) |
| self.checker.config.single_line_if_stmt = True |
| with self.assertNoMessages(): |
| self.visitFirst(stmt) |
| stmt = astroid.extract_node( |
| """ |
| if True: pass #@ |
| else: |
| pass |
| """ |
| ) |
| with self.assertAddsMessages(Message("multiple-statements", node=stmt.body[0])): |
| self.visitFirst(stmt) |
| |
| def testSingleLineClassStmts(self): |
| stmt = astroid.extract_node( |
| """ |
| class MyError(Exception): pass #@ |
| """ |
| ) |
| self.checker.config.single_line_class_stmt = False |
| with self.assertAddsMessages(Message("multiple-statements", node=stmt.body[0])): |
| self.visitFirst(stmt) |
| self.checker.config.single_line_class_stmt = True |
| with self.assertNoMessages(): |
| self.visitFirst(stmt) |
| |
| stmt = astroid.extract_node( |
| """ |
| class MyError(Exception): a='a' #@ |
| """ |
| ) |
| self.checker.config.single_line_class_stmt = False |
| with self.assertAddsMessages(Message("multiple-statements", node=stmt.body[0])): |
| self.visitFirst(stmt) |
| self.checker.config.single_line_class_stmt = True |
| with self.assertNoMessages(): |
| self.visitFirst(stmt) |
| |
| stmt = astroid.extract_node( |
| """ |
| class MyError(Exception): a='a'; b='b' #@ |
| """ |
| ) |
| self.checker.config.single_line_class_stmt = False |
| with self.assertAddsMessages(Message("multiple-statements", node=stmt.body[0])): |
| self.visitFirst(stmt) |
| self.checker.config.single_line_class_stmt = True |
| with self.assertAddsMessages(Message("multiple-statements", node=stmt.body[0])): |
| self.visitFirst(stmt) |
| |
| def testTryExceptFinallyNoMultipleStatement(self): |
| tree = astroid.extract_node( |
| """ |
| try: #@ |
| pass |
| except: |
| pass |
| finally: |
| pass""" |
| ) |
| with self.assertNoMessages(): |
| self.visitFirst(tree) |
| |
| def visitFirst(self, tree): |
| self.checker.process_tokens([]) |
| self.checker.visit_default(tree.body[0]) |
| |
| def test_ellipsis_is_ignored(self): |
| code = """ |
| from typing import overload |
| @overload |
| def concat2(arg1: str) -> str: ... |
| """ |
| tree = astroid.extract_node(code) |
| with self.assertNoMessages(): |
| self.visitFirst(tree) |
| |
| code = """ |
| def concat2(arg1: str) -> str: ... |
| """ |
| stmt = astroid.extract_node(code) |
| with self.assertAddsMessages(Message("multiple-statements", node=stmt.body[0])): |
| self.visitFirst(stmt) |
| |
| |
| class TestSuperfluousParentheses(CheckerTestCase): |
| CHECKER_CLASS = FormatChecker |
| |
| def testCheckKeywordParensHandlesValidCases(self): |
| cases = [ |
| "if foo:", |
| "if foo():", |
| "if (x and y) or z:", |
| "assert foo()", |
| "assert ()", |
| "if (1, 2) in (3, 4):", |
| "if (a or b) in c:", |
| "return (x for x in x)", |
| "if (x for x in x):", |
| "for x in (x for x in x):", |
| "not (foo or bar)", |
| "not (foo or bar) and baz", |
| "return [x for x in (3 if 1 else [4])]", |
| "return (x for x in ((3, 4) if 2 > 1 else (5, 6)))", |
| ] |
| with self.assertNoMessages(): |
| for code in cases: |
| self.checker._check_keyword_parentheses(_tokenize_str(code), 0) |
| |
| def testCheckKeywordParensHandlesUnnecessaryParens(self): |
| cases = [ |
| (Message("superfluous-parens", line=1, args="if"), "if (foo):", 0), |
| (Message("superfluous-parens", line=1, args="if"), "if ((foo, bar)):", 0), |
| (Message("superfluous-parens", line=1, args="if"), "if (foo(bar)):", 0), |
| (Message("superfluous-parens", line=1, args="not"), "not (foo)", 0), |
| (Message("superfluous-parens", line=1, args="not"), "if not (foo):", 1), |
| (Message("superfluous-parens", line=1, args="if"), "if (not (foo)):", 0), |
| (Message("superfluous-parens", line=1, args="not"), "if (not (foo)):", 2), |
| ( |
| Message("superfluous-parens", line=1, args="for"), |
| "for (x) in (1, 2, 3):", |
| 0, |
| ), |
| ( |
| Message("superfluous-parens", line=1, args="if"), |
| "if (1) in (1, 2, 3):", |
| 0, |
| ), |
| ] |
| for msg, code, offset in cases: |
| with self.assertAddsMessages(msg): |
| self.checker._check_keyword_parentheses(_tokenize_str(code), offset) |
| |
| def testNoSuperfluousParensWalrusOperatorIf(self): |
| """Parenthesis change the meaning of assignment in the walrus operator |
| and so are not always superfluous:""" |
| cases = [ |
| ("if (odd := is_odd(i))\n"), |
| ("not (foo := 5)\n"), |
| ] |
| for code in cases: |
| with self.assertNoMessages(): |
| self.checker.process_tokens(_tokenize_str(code)) |
| |
| def testPositiveSuperfluousParensWalrusOperatorIf(self): |
| """Test positive superfluous parens cases with the walrus operator""" |
| cases = [ |
| (Message("superfluous-parens", line=1, args="if"), "if ((x := y)):\n"), |
| (Message("superfluous-parens", line=1, args="not"), "if not ((x := y)):\n"), |
| ] |
| for msg, code in cases: |
| with self.assertAddsMessages(msg): |
| self.checker.process_tokens(_tokenize_str(code)) |
| |
| def testCheckIfArgsAreNotUnicode(self): |
| cases = [("if (foo):", 0), ("assert (1 == 1)", 0)] |
| |
| for code, offset in cases: |
| self.checker._check_keyword_parentheses(_tokenize_str(code), offset) |
| got = self.linter.release_messages() |
| assert isinstance(got[-1].args, str) |
| |
| def testFuturePrintStatementWithoutParensWarning(self): |
| code = """from __future__ import print_function |
| print('Hello world!') |
| """ |
| tree = astroid.parse(code) |
| with self.assertNoMessages(): |
| self.checker.process_module(tree) |
| self.checker.process_tokens(_tokenize_str(code)) |
| |
| def testKeywordParensFalsePositive(self): |
| code = "if 'bar' in (DICT or {}):" |
| with self.assertNoMessages(): |
| self.checker._check_keyword_parentheses(_tokenize_str(code), start=2) |
| |
| |
| class TestCheckSpace(CheckerTestCase): |
| CHECKER_CLASS = FormatChecker |
| |
| def test_encoding_token(self): |
| """Make sure the encoding token doesn't change the checker's behavior |
| |
| _tokenize_str doesn't produce an encoding token, but |
| reading a file does |
| """ |
| with self.assertNoMessages(): |
| encoding_token = tokenize.TokenInfo( |
| tokenize.ENCODING, "utf-8", (0, 0), (0, 0), "" |
| ) |
| tokens = [encoding_token] + _tokenize_str( |
| "if (\n None):\n pass\n" |
| ) |
| self.checker.process_tokens(tokens) |
| |
| |
| def test_disable_global_option_end_of_line(): |
| """ |
| Test for issue with disabling tokenizer messages |
| that extend beyond the scope of the ast tokens |
| """ |
| file_ = tempfile.NamedTemporaryFile( # pylint: disable=consider-using-with |
| "w", delete=False |
| ) |
| with file_: |
| file_.write( |
| """ |
| mylist = [ |
| None |
| ] |
| """ |
| ) |
| try: |
| linter = lint.PyLinter() |
| checker = FormatChecker(linter) |
| linter.register_checker(checker) |
| args = linter.load_command_line_configuration( |
| [file_.name, "-d", "bad-continuation"] |
| ) |
| myreporter = reporters.CollectingReporter() |
| linter.set_reporter(myreporter) |
| linter.check(args) |
| assert not myreporter.messages |
| finally: |
| os.remove(file_.name) |