| # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
| # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE |
| # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt |
| |
| """Check format checker helper functions.""" |
| |
| import os |
| import tempfile |
| import tokenize |
| |
| import astroid |
| |
| from pylint import lint, reporters |
| from pylint.checkers.base.basic_checker import BasicChecker |
| from pylint.checkers.format import FormatChecker |
| from pylint.testutils import CheckerTestCase, MessageTest, _tokenize_str |
| |
| |
| class TestSuperfluousParentheses(CheckerTestCase): |
| CHECKER_CLASS = FormatChecker |
| |
| def testCheckKeywordParensHandlesValidCases(self) -> None: |
| 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) -> None: |
| cases = [ |
| (MessageTest("superfluous-parens", line=1, args="if"), "if (foo):", 0), |
| ( |
| MessageTest("superfluous-parens", line=1, args="if"), |
| "if ((foo, bar)):", |
| 0, |
| ), |
| ( |
| MessageTest("superfluous-parens", line=1, args="if"), |
| "if (foo(bar)):", |
| 0, |
| ), |
| (MessageTest("superfluous-parens", line=1, args="not"), "not (foo)", 0), |
| ( |
| MessageTest("superfluous-parens", line=1, args="not"), |
| "if not (foo):", |
| 1, |
| ), |
| ( |
| MessageTest("superfluous-parens", line=1, args="if"), |
| "if (not (foo)):", |
| 0, |
| ), |
| ( |
| MessageTest("superfluous-parens", line=1, args="not"), |
| "if (not (foo)):", |
| 2, |
| ), |
| ( |
| MessageTest("superfluous-parens", line=1, args="for"), |
| "for (x) in (1, 2, 3):", |
| 0, |
| ), |
| ( |
| MessageTest("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) -> None: |
| """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) -> None: |
| """Test positive superfluous parens cases with the walrus operator.""" |
| cases = [ |
| ( |
| MessageTest("superfluous-parens", line=1, args="if"), |
| "if ((x := y)):\n", |
| ), |
| ( |
| MessageTest("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) -> None: |
| 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) -> None: |
| 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) -> None: |
| 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) -> None: |
| """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() -> None: |
| """Test for issue with disabling tokenizer messages |
| that extend beyond the scope of the ast tokens. |
| """ |
| file_ = tempfile.NamedTemporaryFile("w", delete=False) |
| with file_: |
| file_.write( |
| """ |
| 1 |
| """ |
| ) |
| # pylint: disable = too-many-try-statements |
| try: |
| linter = lint.PyLinter() |
| checker = BasicChecker(linter) |
| linter.register_checker(checker) |
| args = linter._arguments_manager._parse_command_line_configuration( |
| [file_.name, "-d", "pointless-statement"] |
| ) |
| myreporter = reporters.CollectingReporter() |
| linter.set_reporter(myreporter) |
| linter.check(args) |
| assert not myreporter.messages |
| finally: |
| os.remove(file_.name) |