diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 0e033471d2e9..35720b8580d0 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,23 @@ 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..ebb45fee377d --- /dev/null +++ b/mypy/test/testconfigparser.py @@ -0,0 +1,168 @@ +import os +import tempfile +from unittest import TestCase, main + +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: + """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) -> None: + """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) -> None: + """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) -> 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") + + 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) -> 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") + + 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) -> 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") + + 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) -> 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") + + 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) -> None: + """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()