diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 35720b8580d0..247cec13eca3 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -109,9 +109,6 @@ 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: @@ -287,6 +284,22 @@ def _find_config_file( return None +def parse_and_validate_filenames( + raw_files: str +) -> list[str]: + # Split and strip filenames + 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() + + if "" in files_split: + raise ValueError( + "Invalid config: Empty filenames are not allowed except for trailing commas." + ) + + return files_split def parse_config_file( options: Options, @@ -322,21 +335,8 @@ def parse_config_file( 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 + if "files" in section and isinstance(raw_files := section["files"], str): + options.files = parse_and_validate_filenames(raw_files) prefix = f"{file_read}: [mypy]: " updates, report_dirs = parse_section( diff --git a/mypy/test/testconfigparser.py b/mypy/test/testconfigparser.py index ebb45fee377d..334587ad10f1 100644 --- a/mypy/test/testconfigparser.py +++ b/mypy/test/testconfigparser.py @@ -1,168 +1,50 @@ -import os -import tempfile -from unittest import TestCase, main - +import pytest 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 - """ - ) +@pytest.mark.parametrize( + "config_content, expected_files, expected_exception", + [ + # Files listed without spaces + ("[mypy]\nfiles =file1.py,file2.py,file3.py", ["file1.py", "file2.py", "file3.py"], None), - options = Options() + # Files listed with adequate space + ("[mypy]\nfiles =file1.py, file2.py, file3.py", ["file1.py", "file2.py", "file3.py"], None), - with self.assertRaises(ValueError) as cm: - parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) + # Files with extra spaces + ("[mypy]\nfiles = file1.py , file2.py , file3.py", ["file1.py", "file2.py", "file3.py"], None), - self.assertIn("Invalid config", str(cm.exception)) + # Files listed with a trailing comma + ("[mypy]\nfiles = file1.py, file2.py, file3.py,", ["file1.py", "file2.py", "file3.py"], None), - 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") + # Empty files key + ("[mypy]\nfiles =", [], None), - with open(config_path, "w") as f: - f.write( - """ - [mypy] - files = file1.py, - file2.py, - file3.py - """ - ) + # Files key with only a comma + ("[mypy]\nfiles = ,", None, ValueError), - options = Options() + # Mixed valid and invalid filenames + ("[mypy]\nfiles = file1.py, , , file2.py", None, ValueError), - parse_config_file(options, lambda: None, config_path, stdout=None, stderr=None) + # Files listed with multiple trailing comma + ("[mypy]\nfiles = file1.py, file2.py, file3.py,", None, ValueError), - self.assertEqual(options.files, ["file1.py", "file2.py", "file3.py"]) + # Newlines between file entries + ("[mypy]\nfiles = file1.py,\nfile2.py,\nfile3.py", ["file1.py", "file2.py", "file3.py"], None), + ] +) +def test_parse_config_file(tmp_path, config_content, expected_files, expected_exception): + """Parameterized test for parse_config_file handling various configurations.""" + config_path = tmp_path / "test_config.ini" + config_path.write_text(config_content) + options = Options() -if __name__ == "__main__": - main() + if expected_exception: + with pytest.raises(expected_exception) as exc_info: + parse_config_file(options, lambda: None, str(config_path), stdout=None, stderr=None) + assert "Invalid config" in str(exc_info.value) + else: + parse_config_file(options, lambda: None, str(config_path), stdout=None, stderr=None) + assert options.files == expected_files