From 02898b13da1f4dbe102020cf38d6a036e69f9c44 Mon Sep 17 00:00:00 2001 From: Alvaro Frias Date: Mon, 24 Nov 2025 22:44:31 -0300 Subject: [PATCH 1/4] fix unsupported logging check Signed-off-by: Alvaro Frias --- pylint/checkers/logging.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py index 3426abdd67..f1de1c5e61 100644 --- a/pylint/checkers/logging.py +++ b/pylint/checkers/logging.py @@ -355,12 +355,16 @@ def _check_format_string(self, node: nodes.Call, format_arg: Literal[0, 1]) -> N keyword_args_cnt + implicit_pos_args + explicit_pos_args ) except utils.UnsupportedFormatCharacter as ex: - char = format_string[ex.index] - self.add_message( - "logging-unsupported-format", - node=node, - args=(char, ord(char), ex.index), - ) + if num_args > 0: + # Only report unsupported format characters if arguments are provided + # When no arguments are supplied, no formatting is performed + # https://docs.python.org/3/library/logging.html#logging.Logger.debug + char = format_string[ex.index] + self.add_message( + "logging-unsupported-format", + node=node, + args=(char, ord(char), ex.index), + ) return except utils.IncompleteFormatString: self.add_message("logging-format-truncated", node=node) From 00b9d7e9b31ad0da96c96b5b015a872f0567512f Mon Sep 17 00:00:00 2001 From: Alvaro Frias Date: Mon, 24 Nov 2025 22:51:37 -0300 Subject: [PATCH 2/4] Add news fragment for logging-unsupported-format fix Refs #10752 --- doc/whatsnew/fragments/10752.false_positive | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/whatsnew/fragments/10752.false_positive diff --git a/doc/whatsnew/fragments/10752.false_positive b/doc/whatsnew/fragments/10752.false_positive new file mode 100644 index 0000000000..f835d8d7d2 --- /dev/null +++ b/doc/whatsnew/fragments/10752.false_positive @@ -0,0 +1,5 @@ +Fixed false positive for ``logging-unsupported-format`` when no arguments are provided to logging functions. + +According to Python's logging documentation, no formatting is performed when no arguments are supplied, so strings like ``logging.error("%test")`` are valid. + +Closes #10752 From 94388e8d17d0588555304d30b4066ebee959992b Mon Sep 17 00:00:00 2001 From: Alvaro Frias Date: Sun, 30 Nov 2025 21:04:25 -0300 Subject: [PATCH 3/4] add tests Signed-off-by: Alvaro Frias --- tests/checkers/unittest_logging.py | 112 +++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/checkers/unittest_logging.py diff --git a/tests/checkers/unittest_logging.py b/tests/checkers/unittest_logging.py new file mode 100644 index 0000000000..3a784df48d --- /dev/null +++ b/tests/checkers/unittest_logging.py @@ -0,0 +1,112 @@ +# 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 + +"""Tests for the logging checker.""" + +from __future__ import annotations + +import astroid +from astroid import nodes + +from pylint.checkers.logging import LoggingChecker +from pylint.testutils import CheckerTestCase + + +class TestLoggingChecker(CheckerTestCase): + """Tests for LoggingChecker.""" + + CHECKER_CLASS = LoggingChecker + + def _check_code(self, code: str) -> None: + """Helper to properly check a code snippet with the logging checker.""" + module = astroid.parse(code) + self.checker.visit_module(module) + # Walk the entire AST tree + for node in module.nodes_of_class((nodes.Import, nodes.ImportFrom)): + if isinstance(node, nodes.Import): + self.checker.visit_import(node) + else: + self.checker.visit_importfrom(node) + for node in module.nodes_of_class(nodes.Call): + self.checker.visit_call(node) + + def test_logging_unsupported_format_without_args(self) -> None: + """Test that unsupported format chars are not reported without args. + + According to Python docs, no formatting is performed when no args are supplied. + This test verifies the fix for issue #10752. + """ + with self.assertNoMessages(): + self._check_code( + """ +import logging +logging.warning("%badformat") + """ + ) + + def test_logging_original_issue_10752(self) -> None: + """Test the exact case from issue #10752.""" + with self.assertNoMessages(): + self._check_code( + """ +import logging +logging.error("%test") + """ + ) + + def test_logging_invalid_format_specifier_without_args(self) -> None: + """Test invalid format specifiers without args don't trigger unsupported-format.""" + with self.assertNoMessages(): + self._check_code( + """ +import logging +logging.info("%z - invalid but no args so no formatting") + """ + ) + + def test_logging_multiple_invalid_format_without_args(self) -> None: + """Test multiple invalid format chars without args.""" + with self.assertNoMessages(): + self._check_code( + """ +import logging +logging.warning("%q %z %k - all invalid but no args") + """ + ) + + def test_logging_valid_format_with_args(self) -> None: + """Test that valid format strings with args don't trigger warnings.""" + with self.assertNoMessages(): + self._check_code( + """ +import logging +logging.info("User %s logged in", "john") + """ + ) + + def test_logging_unsupported_format_with_args_still_caught(self) -> None: + """Test that unsupported format chars ARE reported when args are provided.""" + self._check_code( + """ +import logging +logging.error("%test", 123) + """ + ) + # Check that at least one message was added + messages = self.linter.release_messages() + assert len(messages) > 0 + assert any(msg.msg_id == "logging-unsupported-format" for msg in messages) + + def test_logging_mixed_invalid_format_with_args_still_caught(self) -> None: + """Test that invalid format chars are still caught when args provided.""" + self._check_code( + """ +import logging +logging.error("Value: %s, Invalid: %z", "test", "value") + """ + ) + # Check that unsupported-format message was added + messages = self.linter.release_messages() + assert len(messages) > 0 + assert any(msg.msg_id == "logging-unsupported-format" for msg in messages) From 3c1b8ec8938365f59981739a94ba726d03adaf4a Mon Sep 17 00:00:00 2001 From: Alvaro Frias Date: Sun, 30 Nov 2025 22:57:04 -0300 Subject: [PATCH 4/4] move tests Signed-off-by: Alvaro Frias --- tests/checkers/unittest_logging.py | 112 ------------------ .../l/logging/logging_unsupported_format.py | 23 ++++ .../l/logging/logging_unsupported_format.txt | 3 + 3 files changed, 26 insertions(+), 112 deletions(-) delete mode 100644 tests/checkers/unittest_logging.py create mode 100644 tests/functional/l/logging/logging_unsupported_format.py create mode 100644 tests/functional/l/logging/logging_unsupported_format.txt diff --git a/tests/checkers/unittest_logging.py b/tests/checkers/unittest_logging.py deleted file mode 100644 index 3a784df48d..0000000000 --- a/tests/checkers/unittest_logging.py +++ /dev/null @@ -1,112 +0,0 @@ -# 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 - -"""Tests for the logging checker.""" - -from __future__ import annotations - -import astroid -from astroid import nodes - -from pylint.checkers.logging import LoggingChecker -from pylint.testutils import CheckerTestCase - - -class TestLoggingChecker(CheckerTestCase): - """Tests for LoggingChecker.""" - - CHECKER_CLASS = LoggingChecker - - def _check_code(self, code: str) -> None: - """Helper to properly check a code snippet with the logging checker.""" - module = astroid.parse(code) - self.checker.visit_module(module) - # Walk the entire AST tree - for node in module.nodes_of_class((nodes.Import, nodes.ImportFrom)): - if isinstance(node, nodes.Import): - self.checker.visit_import(node) - else: - self.checker.visit_importfrom(node) - for node in module.nodes_of_class(nodes.Call): - self.checker.visit_call(node) - - def test_logging_unsupported_format_without_args(self) -> None: - """Test that unsupported format chars are not reported without args. - - According to Python docs, no formatting is performed when no args are supplied. - This test verifies the fix for issue #10752. - """ - with self.assertNoMessages(): - self._check_code( - """ -import logging -logging.warning("%badformat") - """ - ) - - def test_logging_original_issue_10752(self) -> None: - """Test the exact case from issue #10752.""" - with self.assertNoMessages(): - self._check_code( - """ -import logging -logging.error("%test") - """ - ) - - def test_logging_invalid_format_specifier_without_args(self) -> None: - """Test invalid format specifiers without args don't trigger unsupported-format.""" - with self.assertNoMessages(): - self._check_code( - """ -import logging -logging.info("%z - invalid but no args so no formatting") - """ - ) - - def test_logging_multiple_invalid_format_without_args(self) -> None: - """Test multiple invalid format chars without args.""" - with self.assertNoMessages(): - self._check_code( - """ -import logging -logging.warning("%q %z %k - all invalid but no args") - """ - ) - - def test_logging_valid_format_with_args(self) -> None: - """Test that valid format strings with args don't trigger warnings.""" - with self.assertNoMessages(): - self._check_code( - """ -import logging -logging.info("User %s logged in", "john") - """ - ) - - def test_logging_unsupported_format_with_args_still_caught(self) -> None: - """Test that unsupported format chars ARE reported when args are provided.""" - self._check_code( - """ -import logging -logging.error("%test", 123) - """ - ) - # Check that at least one message was added - messages = self.linter.release_messages() - assert len(messages) > 0 - assert any(msg.msg_id == "logging-unsupported-format" for msg in messages) - - def test_logging_mixed_invalid_format_with_args_still_caught(self) -> None: - """Test that invalid format chars are still caught when args provided.""" - self._check_code( - """ -import logging -logging.error("Value: %s, Invalid: %z", "test", "value") - """ - ) - # Check that unsupported-format message was added - messages = self.linter.release_messages() - assert len(messages) > 0 - assert any(msg.msg_id == "logging-unsupported-format" for msg in messages) diff --git a/tests/functional/l/logging/logging_unsupported_format.py b/tests/functional/l/logging/logging_unsupported_format.py new file mode 100644 index 0000000000..0b70968cc8 --- /dev/null +++ b/tests/functional/l/logging/logging_unsupported_format.py @@ -0,0 +1,23 @@ +"""Tests for logging-unsupported-format (issue #10752) + +According to Python logging documentation, no formatting is performed +when no arguments are supplied. This test verifies that unsupported +format characters are only reported when arguments are provided. +""" + +import logging + +# These should NOT trigger warnings (no args = no formatting) +logging.error("%test") +logging.warning("%badformat") +logging.info("%z - invalid specifier") +logging.debug("%q %k %z - multiple invalid") + +# These SHOULD trigger warnings (args provided = formatting attempted) +logging.error("%test", 123) # [logging-unsupported-format] +logging.warning("%bad", "arg") # [logging-unsupported-format] +logging.info("Value: %s, Invalid: %z", "test", "val") # [logging-unsupported-format] + +# Valid format strings should work fine +logging.info("User %s logged in", "john") +logging.debug("Count: %d", 42) diff --git a/tests/functional/l/logging/logging_unsupported_format.txt b/tests/functional/l/logging/logging_unsupported_format.txt new file mode 100644 index 0000000000..d1dfc5a9a2 --- /dev/null +++ b/tests/functional/l/logging/logging_unsupported_format.txt @@ -0,0 +1,3 @@ +logging-unsupported-format:17:0:17:27::Unsupported logging format character 't' (0x74) at index 1:UNDEFINED +logging-unsupported-format:18:0:18:30::Unsupported logging format character 'b' (0x62) at index 1:UNDEFINED +logging-unsupported-format:19:0:19:53::Unsupported logging format character 'z' (0x7a) at index 21:UNDEFINED