Skip to content

Commit d0711bd

Browse files
authored
Add support for --enable-error-code and --disable-error-code (#9172)
Add ability to enable error codes globally from the command line Add ability to disable error codes globally from the command line Enabling error codes will always override disabling error codes Update documentation to include new flags Fixes #8975.
1 parent 88eb84e commit d0711bd

File tree

8 files changed

+195
-8
lines changed

8 files changed

+195
-8
lines changed

docs/source/command_line.rst

+30
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,36 @@ of the above sections.
553553
Note: the exact list of flags enabled by running :option:`--strict` may change
554554
over time.
555555

556+
.. option:: --disable-error-code
557+
558+
This flag allows disabling one or multiple error codes globally.
559+
560+
.. code-block:: python
561+
562+
# no flag
563+
x = 'a string'
564+
x.trim() # error: "str" has no attribute "trim" [attr-defined]
565+
566+
# --disable-error-code attr-defined
567+
x = 'a string'
568+
x.trim()
569+
570+
.. option:: --enable-error-code
571+
572+
This flag allows enabling one or multiple error codes globally.
573+
574+
Note: This flag will override disabled error codes from the --disable-error-code
575+
flag
576+
577+
.. code-block:: python
578+
579+
# --disable-error-code attr-defined
580+
x = 'a string'
581+
x.trim()
582+
583+
# --disable-error-code attr-defined --enable-error-code attr-defined
584+
x = 'a string'
585+
x.trim() # error: "str" has no attribute "trim" [attr-defined]
556586
557587
.. _configuring-error-messages:
558588

mypy/build.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ def _build(sources: List[BuildSource],
223223
options.show_error_codes,
224224
options.pretty,
225225
lambda path: read_py_file(path, cached_read, options.python_version),
226-
options.show_absolute_path)
226+
options.show_absolute_path,
227+
options.enabled_error_codes,
228+
options.disabled_error_codes)
227229
plugin, snapshot = load_plugins(options, errors, stdout, extra_plugins)
228230

229231
# Add catch-all .gitignore to cache dir if we created it

mypy/errorcodes.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@
33
These can be used for filtering specific errors.
44
"""
55

6-
from typing import List
6+
from typing import Dict, List
77
from typing_extensions import Final
88

99

1010
# All created error codes are implicitly stored in this list.
1111
all_error_codes = [] # type: List[ErrorCode]
1212

13+
error_codes = {} # type: Dict[str, ErrorCode]
14+
1315

1416
class ErrorCode:
15-
def __init__(self, code: str, description: str, category: str) -> None:
17+
def __init__(self, code: str,
18+
description: str,
19+
category: str,
20+
default_enabled: bool = True) -> None:
1621
self.code = code
1722
self.description = description
1823
self.category = category
24+
self.default_enabled = default_enabled
25+
error_codes[code] = self
1926

2027
def __str__(self) -> str:
2128
return '<ErrorCode {}>'.format(self.code)

mypy/errors.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,18 @@ def __init__(self,
164164
show_error_codes: bool = False,
165165
pretty: bool = False,
166166
read_source: Optional[Callable[[str], Optional[List[str]]]] = None,
167-
show_absolute_path: bool = False) -> None:
167+
show_absolute_path: bool = False,
168+
enabled_error_codes: Optional[Set[ErrorCode]] = None,
169+
disabled_error_codes: Optional[Set[ErrorCode]] = None) -> None:
168170
self.show_error_context = show_error_context
169171
self.show_column_numbers = show_column_numbers
170172
self.show_error_codes = show_error_codes
171173
self.show_absolute_path = show_absolute_path
172174
self.pretty = pretty
173175
# We use fscache to read source code when showing snippets.
174176
self.read_source = read_source
177+
self.enabled_error_codes = enabled_error_codes or set()
178+
self.disabled_error_codes = disabled_error_codes or set()
175179
self.initialize()
176180

177181
def initialize(self) -> None:
@@ -195,7 +199,9 @@ def copy(self) -> 'Errors':
195199
self.show_error_codes,
196200
self.pretty,
197201
self.read_source,
198-
self.show_absolute_path)
202+
self.show_absolute_path,
203+
self.enabled_error_codes,
204+
self.disabled_error_codes)
199205
new.file = self.file
200206
new.import_ctx = self.import_ctx[:]
201207
new.function_or_member = self.function_or_member[:]
@@ -351,15 +357,25 @@ def add_error_info(self, info: ErrorInfo) -> None:
351357
self._add_error_info(file, info)
352358

353359
def is_ignored_error(self, line: int, info: ErrorInfo, ignores: Dict[int, List[str]]) -> bool:
354-
if line not in ignores:
360+
if info.code and self.is_error_code_enabled(info.code) is False:
361+
return True
362+
elif line not in ignores:
355363
return False
356364
elif not ignores[line]:
357365
# Empty list means that we ignore all errors
358366
return True
359-
elif info.code:
367+
elif info.code and self.is_error_code_enabled(info.code) is True:
360368
return info.code.code in ignores[line]
361369
return False
362370

371+
def is_error_code_enabled(self, error_code: ErrorCode) -> bool:
372+
if error_code in self.disabled_error_codes:
373+
return False
374+
elif error_code in self.enabled_error_codes:
375+
return True
376+
else:
377+
return error_code.default_enabled
378+
363379
def clear_errors_in_targets(self, path: str, targets: Set[str]) -> None:
364380
"""Remove errors in specific fine-grained targets within a file."""
365381
if path in self.error_info_map:

mypy/main.py

+26
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from mypy.find_sources import create_source_list, InvalidSourceList
1919
from mypy.fscache import FileSystemCache
2020
from mypy.errors import CompileError
21+
from mypy.errorcodes import error_codes
2122
from mypy.options import Options, BuildType
2223
from mypy.config_parser import parse_version, parse_config_file
2324
from mypy.split_namespace import SplitNamespace
@@ -612,6 +613,14 @@ def add_invertible_flag(flag: str,
612613
'--strict', action='store_true', dest='special-opts:strict',
613614
help=strict_help)
614615

616+
strictness_group.add_argument(
617+
'--disable-error-code', metavar='NAME', action='append', default=[],
618+
help="Disable a specific error code")
619+
strictness_group.add_argument(
620+
'--enable-error-code', metavar='NAME', action='append', default=[],
621+
help="Enable a specific error code"
622+
)
623+
615624
error_group = parser.add_argument_group(
616625
title='Configuring error messages',
617626
description="Adjust the amount of detail shown in error messages.")
@@ -860,6 +869,23 @@ def set_strict_flags() -> None:
860869
parser.error("You can't make a variable always true and always false (%s)" %
861870
', '.join(sorted(overlap)))
862871

872+
# Process `--enable-error-code` and `--disable-error-code` flags
873+
disabled_codes = set(options.disable_error_code)
874+
enabled_codes = set(options.enable_error_code)
875+
876+
valid_error_codes = set(error_codes.keys())
877+
878+
invalid_codes = (enabled_codes | disabled_codes) - valid_error_codes
879+
if invalid_codes:
880+
parser.error("Invalid error code(s): %s" %
881+
', '.join(sorted(invalid_codes)))
882+
883+
options.disabled_error_codes |= {error_codes[code] for code in disabled_codes}
884+
options.enabled_error_codes |= {error_codes[code] for code in enabled_codes}
885+
886+
# Enabling an error code always overrides disabling
887+
options.disabled_error_codes -= options.enabled_error_codes
888+
863889
# Set build flags.
864890
if options.strict_optional_whitelist is not None:
865891
# TODO: Deprecate, then kill this flag

mypy/options.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
import pprint
44
import sys
55

6-
from typing_extensions import Final
6+
from typing_extensions import Final, TYPE_CHECKING
77
from typing import Dict, List, Mapping, Optional, Pattern, Set, Tuple, Callable, Any
88

99
from mypy import defaults
1010
from mypy.util import get_class_descriptors, replace_object_state
1111

12+
if TYPE_CHECKING:
13+
from mypy.errors import ErrorCode
14+
1215

1316
class BuildType:
1417
STANDARD = 0 # type: Final[int]
@@ -177,6 +180,14 @@ def __init__(self) -> None:
177180
# Variable names considered False
178181
self.always_false = [] # type: List[str]
179182

183+
# Error codes to disable
184+
self.disable_error_code = [] # type: List[str]
185+
self.disabled_error_codes = set() # type: Set[ErrorCode]
186+
187+
# Error codes to enable
188+
self.enable_error_code = [] # type: List[str]
189+
self.enabled_error_codes = set() # type: Set[ErrorCode]
190+
180191
# Use script name instead of __main__
181192
self.scripts_are_modules = False
182193

test-data/unit/check-flags.test

+45
Original file line numberDiff line numberDiff line change
@@ -1530,3 +1530,48 @@ def f(a = None):
15301530
no_implicit_optional = True
15311531
\[mypy-m]
15321532
no_implicit_optional = False
1533+
1534+
[case testDisableErrorCode]
1535+
# flags: --disable-error-code attr-defined
1536+
x = 'should be fine'
1537+
x.trim()
1538+
1539+
[case testDisableDifferentErrorCode]
1540+
# flags: --disable-error-code name-defined --show-error-code
1541+
x = 'should not be fine'
1542+
x.trim() # E: "str" has no attribute "trim" [attr-defined]
1543+
1544+
[case testDisableMultipleErrorCode]
1545+
# flags: --disable-error-code attr-defined --disable-error-code return-value --show-error-code
1546+
x = 'should be fine'
1547+
x.trim()
1548+
1549+
def bad_return_type() -> str:
1550+
return None
1551+
1552+
bad_return_type('no args taken!') # E: Too many arguments for "bad_return_type" [call-arg]
1553+
1554+
[case testEnableErrorCode]
1555+
# flags: --disable-error-code attr-defined --enable-error-code attr-defined --show-error-code
1556+
x = 'should be fine'
1557+
x.trim() # E: "str" has no attribute "trim" [attr-defined]
1558+
1559+
[case testEnableDifferentErrorCode]
1560+
# flags: --disable-error-code attr-defined --enable-error-code name-defined --show-error-code
1561+
x = 'should not be fine'
1562+
x.trim() # E: "str" has no attribute "trim" [attr-defined]
1563+
1564+
[case testEnableMultipleErrorCode]
1565+
# flags: \
1566+
--disable-error-code attr-defined \
1567+
--disable-error-code return-value \
1568+
--disable-error-code call-arg \
1569+
--enable-error-code attr-defined \
1570+
--enable-error-code return-value --show-error-code
1571+
x = 'should be fine'
1572+
x.trim() # E: "str" has no attribute "trim" [attr-defined]
1573+
1574+
def bad_return_type() -> str:
1575+
return None # E: Incompatible return value type (got "None", expected "str") [return-value]
1576+
1577+
bad_return_type('no args taken!')

test-data/unit/cmdline.test

+50
Original file line numberDiff line numberDiff line change
@@ -1123,3 +1123,53 @@ import foo.bar
11231123
[out]
11241124
src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar'
11251125
== Return code: 2
1126+
1127+
[case testEnableInvalidErrorCode]
1128+
# cmd: mypy --enable-error-code YOLO test.py
1129+
[file test.py]
1130+
x = 1
1131+
[out]
1132+
usage: mypy [-h] [-v] [-V] [more options; see below]
1133+
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
1134+
mypy: error: Invalid error code(s): YOLO
1135+
== Return code: 2
1136+
1137+
[case testDisableInvalidErrorCode]
1138+
# cmd: mypy --disable-error-code YOLO test.py
1139+
[file test.py]
1140+
x = 1
1141+
[out]
1142+
usage: mypy [-h] [-v] [-V] [more options; see below]
1143+
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
1144+
mypy: error: Invalid error code(s): YOLO
1145+
== Return code: 2
1146+
1147+
[case testEnableAndDisableInvalidErrorCode]
1148+
# cmd: mypy --disable-error-code YOLO --enable-error-code YOLO2 test.py
1149+
[file test.py]
1150+
x = 1
1151+
[out]
1152+
usage: mypy [-h] [-v] [-V] [more options; see below]
1153+
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
1154+
mypy: error: Invalid error code(s): YOLO, YOLO2
1155+
== Return code: 2
1156+
1157+
[case testEnableValidAndInvalidErrorCode]
1158+
# cmd: mypy --enable-error-code attr-defined --enable-error-code YOLO test.py
1159+
[file test.py]
1160+
x = 1
1161+
[out]
1162+
usage: mypy [-h] [-v] [-V] [more options; see below]
1163+
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
1164+
mypy: error: Invalid error code(s): YOLO
1165+
== Return code: 2
1166+
1167+
[case testDisableValidAndInvalidErrorCode]
1168+
# cmd: mypy --disable-error-code attr-defined --disable-error-code YOLO test.py
1169+
[file test.py]
1170+
x = 1
1171+
[out]
1172+
usage: mypy [-h] [-v] [-V] [more options; see below]
1173+
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
1174+
mypy: error: Invalid error code(s): YOLO
1175+
== Return code: 2

0 commit comments

Comments
 (0)