8000 Allow per-module error codes by ilevkivskyi · Pull Request #13502 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Allow per-module error codes #13502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Allow per-module error codes
  • Loading branch information
ilevkivskyi committed Aug 24, 2022
commit e0f196a078f7603779b73784d243e0ea43a21e30
1 change: 1 addition & 0 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ def _build(
options.enabled_error_codes,
options.disabled_error_codes,
options.many_errors_threshold,
options,
)
plugin, snapshot = load_plugins(options, errors, stdout, extra_plugins)

Expand Down
19 changes: 15 additions & 4 deletions mypy/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import sys
from io import StringIO

from mypy.errorcodes import error_codes

if sys.version_info >= (3, 11):
import tomllib
else:
Expand Down Expand Up @@ -69,6 +71,15 @@ def try_split(v: str | Sequence[str], split_regex: str = "[,]") -> list[str]:
return [p.strip() for p in v]


def validate_codes(codes: list[str]) -> list[str]:
invalid_codes = set(codes) - set(error_codes.keys())
if invalid_codes:
raise argparse.ArgumentTypeError(
f"Invalid error code(s): {', '.join(sorted(invalid_codes))}"
)
return codes


def expand_path(path: str) -> str:
"""Expand the user home directory and any environment variables contained within
the provided path.
Expand Down Expand Up @@ -147,8 +158,8 @@ def check_follow_imports(choice: str) -> str:
"plugins": lambda s: [p.strip() for p in s.split(",")],
"always_true": lambda s: [p.strip() for p in s.split(",")],
"always_false": lambda s: [p.strip() for p in s.split(",")],
"disable_error_code": lambda s: [p.strip() for p in s.split(",")],
"enable_error_code": lambda s: [p.strip() for p in s.split(",")],
"disable_error_code": lambda s: validate_codes([p.strip() for p in s.split(",")]),
"enable_error_code": lambda s: validate_codes([p.strip() for p in s.split(",")]),
"package_root": lambda s: [p.strip() for p in s.split(",")],
"cache_dir": expand_path,
"python_executable": expand_path,
Expand All @@ -168,8 +179,8 @@ def check_follow_imports(choice: str) -> str:
"plugins": try_split,
"always_true": try_split,
"always_false": try_split,
"disable_error_code": try_split,
"enable_error_code": try_split,
"disable_error_code": lambda s: validate_codes(try_split(s)),
"enable_error_code": lambda s: validate_codes(try_split(s)),
"package_root": try_split,
"exclude": str_or_array_as_list,
}
Expand Down
28 changes: 25 additions & 3 deletions mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing_extensions import Final, Literal, TypeAlias as _TypeAlias

from mypy import errorcodes as codes
from mypy.errorcodes import IMPORT, ErrorCode
from mypy.errorcodes import IMPORT, ErrorCode, error_codes
from mypy.message_registry import ErrorMessage
from mypy.options import Options
from mypy.scope import Scope
Expand Down Expand Up @@ -265,6 +265,7 @@ def __init__(
enabled_error_codes: set[ErrorCode] | None = None,
disabled_error_codes: set[ErrorCode] | None = None,
many_errors_threshold: int = -1,
options: Options | None = None,
) -> None:
self.show_error_context = show_error_context
self.show_column_numbers = show_column_numbers
Expand All @@ -279,6 +280,7 @@ def __init__(
self.enabled_error_codes = enabled_error_codes or set()
self.disabled_error_codes = disabled_error_codes or set()
self.many_errors_threshold = many_errors_threshold
self.options = options
self.initialize()

def initialize(self) -> None:
Expand Down Expand Up @@ -586,9 +588,29 @@ def is_ignored_error(self, line: int, info: ErrorInfo, ignores: dict[int, list[s
return False

def is_error_code_enabled(self, error_code: ErrorCode) -> bool:
if error_code in self.disabled_error_codes:
# Start with globally disabled/enabled codes.
current_mod_disabled = self.disabled_error_codes
current_mod_enabled = self.enabled_error_codes

module = self.current_module()
if self.options and module is not None:
# Clone is cached, so it is OK to call this often.
current_mod_options = self.options.clone_for_module(module)

# Similar to global codes enabling overrides disabling, so we start from latter.
for code_str in current_mod_options.disable_error_code:
code = error_codes[code_str]
current_mod_disabled.add(code)
current_mod_enabled.discard(code)

for code_str in current_mod_options.enable_error_code:
code = error_codes[code_str]
current_mod_enabled.add(code)
current_mod_disabled.discard(code)

if error_code in current_mod_disabled:
return False
elif error_code in self.enabled_error_codes:
elif error_code in current_mod_enabled:
return True
else:
return error_code.default_enabled
Expand Down
2 changes: 2 additions & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class BuildType:
"always_true",
"check_untyped_defs",
"debug_cache",
"disable_error_code",
"disallow_any_decorated",
"disallow_any_explicit",
"disallow_any_expr",
Expand All @@ -37,6 +38,7 @@ class BuildType:
"disallow_untyped_calls",
"disallow_untyped_decorators",
"disallow_untyped_defs",
"enable_error_code",
"follow_imports",
"follow_imports_for_stubs",
"ignore_errors",
Expand Down
18 changes: 18 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -2053,3 +2053,21 @@ def f(x):
y = 1
f(reveal_type(y)) # E: Call to untyped function "f" in typed context \
# N: Revealed type is "builtins.int"

[case testPerModuleErrorCodes]
# flags: --config-file tmp/mypy.ini
import tests.foo
import bar
[file bar.py]
x = [] # E: Need type annotation for "x" (hint: "x: List[<type>] = ...")
[file tests/__init__.py]
[file tests/foo.py]
x = [] # OK
[file mypy.ini]
\[mypy]
strict = True

\[mypy-tests.*]
allow_untyped_defs = True
allow_untyped_calls = True
disable_error_code = var-annotated
0