From ec969415a455e6b81c738180078eae6fe84615be Mon Sep 17 00:00:00 2001 From: SOUBHIK KUMAR MITRA <soubhikmitra98@gmail.com> Date: Wed, 15 Jan 2025 15:02:13 +0530 Subject: [PATCH 1/3] Fix config parsing to allow trailing commas but prevent empty entries - Modified `split_and_match_files_list` to handle trailing commas correctly. - Ensured empty string entries result in an error to match CLI behavior. - Updated unit tests to cover various edge cases. - Improved test coverage for config parsing with different file list formats. --- mypy/config_parser.py | 19 +++ mypy/test/testconfigparser.py | 213 ++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 mypy/test/testconfigparser.py diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 0e033471d2e9..0cb6fea2a167 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -109,6 +109,9 @@ def split_and_match_files_list(paths: Sequence[str]) -> list[str]: expanded_paths = [] for path in paths: + if not path: + continue + path = expand_path(path.strip()) globbed_files = fileglob.glob(path, recursive=True) if globbed_files: @@ -318,6 +321,22 @@ def parse_config_file( print(f"{file_read}: No [mypy] section in config file", file=stderr) else: section = parser["mypy"] + + if "files" in section: + raw_files = section["files"].strip() + files_split = [file.strip() for file in raw_files.split(",")] + + # Remove trailing empty entry if present + if files_split and files_split[-1] == "": + files_split.pop() + + # Raise an error if there are any remaining empty strings + if "" in files_split: + raise ValueError("Invalid config: Empty filenames are not allowed except for trailing commas.") + + options.files = files_split + + prefix = f"{file_read}: [mypy]: " updates, report_dirs = parse_section( prefix, options, set_strict_flags, section, config_types, stderr diff --git a/mypy/test/testconfigparser.py b/mypy/test/testconfigparser.py new file mode 100644 index 000000000000..30155bca7047 --- /dev/null +++ b/mypy/test/testconfigparser.py @@ -0,0 +1,213 @@ +import os +import tempfile +from unittest import TestCase, main +from mypy.options import Options +from mypy.config_parser import parse_config_file + +class TestConfigParser(TestCase): + def test_parse_config_file_with_single_file(self): + """A single file should be correctly parsed.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files = file1.py + """ + ) + + options = Options() + + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertEqual(options.files, ["file1.py"]) + + def test_parse_config_file_with_no_spaces(self): + """Files listed without spaces should be correctly parsed.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files =file1.py,file2.py,file3.py + """ + ) + + options = Options() + + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) + + def test_parse_config_file_with_extra_spaces(self): + """Files with extra spaces should be correctly parsed.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files = file1.py , file2.py , file3.py + """ + ) + + options = Options() + + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) + + def test_parse_config_file_with_empty_files_key(self): + """An empty files key should result in an empty list.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files = + """ + ) + + options = Options() + + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertEqual(options.files, []) + + def test_parse_config_file_with_only_comma(self): + """A files key with only a comma should raise an error.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files = , + """ + ) + + options = Options() + + with self.assertRaises(ValueError) as cm: + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertIn("Invalid config", str(cm.exception)) + + def test_parse_config_file_with_only_whitespace(self): + """A files key with only whitespace should result in an empty list.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files = + """ + ) + + options = Options() + + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertEqual(options.files, []) + + def test_parse_config_file_with_mixed_valid_and_invalid_entries(self): + """Mix of valid and invalid filenames should raise an error.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files = file1.py, , , file2.py + """ + ) + + options = Options() + + with self.assertRaises(ValueError) as cm: + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertIn("Invalid config", str(cm.exception)) + + def test_parse_config_file_with_newlines_between_files(self): + """Newlines between file entries should be correctly handled.""" + with tempfile.TemporaryDirectory() as tmpdirname: + config_path = os.path.join(tmpdirname, "test_config.ini") + + with open(config_path, "w") as f: + f.write( + """ + [mypy] + files = file1.py, + file2.py, + file3.py + """ + ) + + options = Options() + + parse_config_file( + options, + lambda: None, + config_path, + stdout=None, + stderr=None, + ) + + self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) + +if __name__ == "__main__": + main() From 082075271b648842f02c04e4b2ba5a754866fe34 Mon Sep 17 00:00:00 2001 From: SOUBHIK KUMAR MITRA <soubhikmitra98@gmail.com> Date: Tue, 25 Feb 2025 02:13:49 +0530 Subject: [PATCH 2/3] fix the type check error in the pipeline checks --- mypy/test/testconfigparser.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mypy/test/testconfigparser.py b/mypy/test/testconfigparser.py index 30155bca7047..fe1ac03bec37 100644 --- a/mypy/test/testconfigparser.py +++ b/mypy/test/testconfigparser.py @@ -5,7 +5,7 @@ from mypy.config_parser import parse_config_file class TestConfigParser(TestCase): - def test_parse_config_file_with_single_file(self): + def test_parse_config_file_with_single_file(self) -> None: """A single file should be correctly parsed.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") @@ -30,7 +30,7 @@ def test_parse_config_file_with_single_file(self): self.assertEqual(options.files, ["file1.py"]) - def test_parse_config_file_with_no_spaces(self): + def test_parse_config_file_with_no_spaces(self) -> None: """Files listed without spaces should be correctly parsed.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") @@ -55,7 +55,7 @@ def test_parse_config_file_with_no_spaces(self): self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) - def test_parse_config_file_with_extra_spaces(self): + def test_parse_config_file_with_extra_spaces(self) -> None: """Files with extra spaces should be correctly parsed.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") @@ -80,7 +80,7 @@ def test_parse_config_file_with_extra_spaces(self): self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) - def test_parse_config_file_with_empty_files_key(self): + def test_parse_config_file_with_empty_files_key(self) -> None: """An empty files key should result in an empty list.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") @@ -105,7 +105,7 @@ def test_parse_config_file_with_empty_files_key(self): self.assertEqual(options.files, []) - def test_parse_config_file_with_only_comma(self): + def test_parse_config_file_with_only_comma(self) -> None: """A files key with only a comma should raise an error.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") @@ -131,7 +131,7 @@ def test_parse_config_file_with_only_comma(self): self.assertIn("Invalid config", str(cm.exception)) - def test_parse_config_file_with_only_whitespace(self): + def test_parse_config_file_with_only_whitespace(self) -> None: """A files key with only whitespace should result in an empty list.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") @@ -156,7 +156,7 @@ def test_parse_config_file_with_only_whitespace(self): self.assertEqual(options.files, []) - def test_parse_config_file_with_mixed_valid_and_invalid_entries(self): + def test_parse_config_file_with_mixed_valid_and_invalid_entries(self) -> None: """Mix of valid and invalid filenames should raise an error.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") @@ -182,7 +182,7 @@ def test_parse_config_file_with_mixed_valid_and_invalid_entries(self): self.assertIn("Invalid config", str(cm.exception)) - def test_parse_config_file_with_newlines_between_files(self): + def test_parse_config_file_with_newlines_between_files(self) -> None: """Newlines between file entries should be correctly handled.""" with tempfile.TemporaryDirectory() as tmpdirname: config_path = os.path.join(tmpdirname, "test_config.ini") From e80c2fae7b8d55aac3bf68e552d02230be05e14a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 20:46:33 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/config_parser.py | 5 ++- mypy/test/testconfigparser.py | 75 +++++++---------------------------- 2 files changed, 18 insertions(+), 62 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 0cb6fea2a167..35720b8580d0 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -332,11 +332,12 @@ def parse_config_file( # Raise an error if there are any remaining empty strings if "" in files_split: - raise ValueError("Invalid config: Empty filenames are not allowed except for trailing commas.") + raise ValueError( + "Invalid config: Empty filenames are not allowed except for trailing commas." + ) options.files = files_split - prefix = f"{file_read}: [mypy]: " updates, report_dirs = parse_section( prefix, options, set_strict_flags, section, config_types, stderr diff --git a/mypy/test/testconfigparser.py b/mypy/test/testconfigparser.py index fe1ac03bec37..ebb45fee377d 100644 --- a/mypy/test/testconfigparser.py +++ b/mypy/test/testconfigparser.py @@ -1,8 +1,10 @@ import os import tempfile from unittest import TestCase, main -from mypy.options import Options + from mypy.config_parser import parse_config_file +from mypy.options import Options + class TestConfigParser(TestCase): def test_parse_config_file_with_single_file(self) -> None: @@ -20,13 +22,7 @@ def test_parse_config_file_with_single_file(self) -> None: options = Options() - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertEqual(options.files, ["file1.py"]) @@ -45,13 +41,7 @@ def test_parse_config_file_with_no_spaces(self) -> None: options = Options() - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) @@ -64,19 +54,13 @@ def test_parse_config_file_with_extra_spaces(self) -> None: f.write( """ [mypy] - files = file1.py , file2.py , file3.py + files = file1.py , file2.py , file3.py """ ) options = Options() - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) @@ -89,19 +73,13 @@ def test_parse_config_file_with_empty_files_key(self) -> None: f.write( """ [mypy] - files = + files = """ ) options = Options() - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertEqual(options.files, []) @@ -121,13 +99,7 @@ def test_parse_config_file_with_only_comma(self) -> None: options = Options() with self.assertRaises(ValueError) as cm: - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertIn("Invalid config", str(cm.exception)) @@ -140,19 +112,13 @@ def test_parse_config_file_with_only_whitespace(self) -> None: f.write( """ [mypy] - files = + files = """ ) options = Options() - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertEqual(options.files, []) @@ -172,13 +138,7 @@ def test_parse_config_file_with_mixed_valid_and_invalid_entries(self) -> None: options = Options() with self.assertRaises(ValueError) as cm: - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertIn("Invalid config", str(cm.exception)) @@ -199,15 +159,10 @@ def test_parse_config_file_with_newlines_between_files(self) -> None: options = Options() - parse_config_file( - options, - lambda: None, - config_path, - stdout=None, - stderr=None, - ) + parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) + if __name__ == "__main__": main()