From 6eb3ee55a2ea2cefccb41acf0fb520d6d404d24d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 3 Aug 2024 17:51:18 +0300 Subject: [PATCH] Fix error code handling in `stubtest` with `--mypy-config-file` (#17629) This is the first PR in the series of unifing configuration parsing for different mypy tools. I would like to have the simple and focused. --- mypy/main.py | 17 +---------------- mypy/options.py | 17 +++++++++++++++++ mypy/stubtest.py | 6 ++++++ mypy/test/teststubtest.py | 13 +++++++++++++ 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 05044335ecee..49a395b478b3 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -19,7 +19,6 @@ validate_package_allow_list, ) from mypy.error_formatter import OUTPUT_CHOICES -from mypy.errorcodes import error_codes from mypy.errors import CompileError from mypy.find_sources import InvalidSourceList, create_source_list from mypy.fscache import FileSystemCache @@ -1336,21 +1335,7 @@ def set_strict_flags() -> None: validate_package_allow_list(options.untyped_calls_exclude) - # Process `--enable-error-code` and `--disable-error-code` flags - disabled_codes = set(options.disable_error_code) - enabled_codes = set(options.enable_error_code) - - valid_error_codes = set(error_codes.keys()) - - invalid_codes = (enabled_codes | disabled_codes) - valid_error_codes - if invalid_codes: - parser.error(f"Invalid error code(s): {', '.join(sorted(invalid_codes))}") - - options.disabled_error_codes |= {error_codes[code] for code in disabled_codes} - options.enabled_error_codes |= {error_codes[code] for code in enabled_codes} - - # Enabling an error code always overrides disabling - options.disabled_error_codes -= options.enabled_error_codes + options.process_error_codes(error_callback=parser.error) # Validate incomplete features. for feature in options.enable_incomplete_feature: diff --git a/mypy/options.py b/mypy/options.py index bff096d82c15..5ab397e0e156 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -416,6 +416,23 @@ def snapshot(self) -> dict[str, object]: def __repr__(self) -> str: return f"Options({pprint.pformat(self.snapshot())})" + def process_error_codes(self, *, error_callback: Callable[[str], Any]) -> None: + # Process `--enable-error-code` and `--disable-error-code` flags + disabled_codes = set(self.disable_error_code) + enabled_codes = set(self.enable_error_code) + + valid_error_codes = set(error_codes.keys()) + + invalid_codes = (enabled_codes | disabled_codes) - valid_error_codes + if invalid_codes: + error_callback(f"Invalid error code(s): {', '.join(sorted(invalid_codes))}") + + self.disabled_error_codes |= {error_codes[code] for code in disabled_codes} + self.enabled_error_codes |= {error_codes[code] for code in enabled_codes} + + # Enabling an error code always overrides disabling + self.disabled_error_codes -= self.enabled_error_codes + def apply_changes(self, changes: dict[str, object]) -> Options: # Note: effects of this method *must* be idempotent. new_options = Options() diff --git a/mypy/stubtest.py b/mypy/stubtest.py index a7cde8b8fe6c..6299f21e48e9 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1941,6 +1941,12 @@ def set_strict_flags() -> None: # not needed yet parse_config_file(options, set_strict_flags, options.config_file, sys.stdout, sys.stderr) + def error_callback(msg: str) -> typing.NoReturn: + print(_style("error:", color="red", bold=True), msg) + sys.exit(1) + + options.process_error_codes(error_callback=error_callback) + try: modules = build_stubs(modules, options, find_submodules=not args.check_typeshed) except StubtestFailure as stubtest_failure: diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 418308e2e65e..1cc6c38e6e85 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -2477,6 +2477,19 @@ def test_config_file(self) -> None: output = run_stubtest(stub=stub, runtime=runtime, options=[], config_file=config_file) assert output == "Success: no issues found in 1 module\n" + def test_config_file_error_codes(self) -> None: + runtime = "temp = 5\n" + stub = "temp = SOME_GLOBAL_CONST" + output = run_stubtest(stub=stub, runtime=runtime, options=[]) + assert output == ( + "error: not checking stubs due to mypy build errors:\n" + 'test_module.pyi:1: error: Name "SOME_GLOBAL_CONST" is not defined [name-defined]\n' + ) + + config_file = "[mypy]\ndisable_error_code = name-defined\n" + output = run_stubtest(stub=stub, runtime=runtime, options=[], config_file=config_file) + assert output == "Success: no issues found in 1 module\n" + def test_no_modules(self) -> None: output = io.StringIO() with contextlib.redirect_stdout(output):