Skip to content

Commit

Permalink
Add support for --enable-error-code and --disable-error-code (#9172)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cph-w committed Aug 10, 2020
1 parent 88eb84e commit d0711bd
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 8 deletions.
30 changes: 30 additions & 0 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,36 @@ of the above sections.
Note: the exact list of flags enabled by running :option:`--strict` may change
over time.

.. option:: --disable-error-code

This flag allows disabling one or multiple error codes globally.

.. code-block:: python
# no flag
x = 'a string'
x.trim() # error: "str" has no attribute "trim" [attr-defined]
# --disable-error-code attr-defined
x = 'a string'
x.trim()
.. option:: --enable-error-code

This flag allows enabling one or multiple error codes globally.

Note: This flag will override disabled error codes from the --disable-error-code
flag

.. code-block:: python
# --disable-error-code attr-defined
x = 'a string'
x.trim()
# --disable-error-code attr-defined --enable-error-code attr-defined
x = 'a string'
x.trim() # error: "str" has no attribute "trim" [attr-defined]
.. _configuring-error-messages:

Expand Down
4 changes: 3 additions & 1 deletion mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ def _build(sources: List[BuildSource],
options.show_error_codes,
options.pretty,
lambda path: read_py_file(path, cached_read, options.python_version),
options.show_absolute_path)
options.show_absolute_path,
options.enabled_error_codes,
options.disabled_error_codes)
plugin, snapshot = load_plugins(options, errors, stdout, extra_plugins)

# Add catch-all .gitignore to cache dir if we created it
Expand Down
11 changes: 9 additions & 2 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@
These can be used for filtering specific errors.
"""

from typing import List
from typing import Dict, List
from typing_extensions import Final


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

error_codes = {} # type: Dict[str, ErrorCode]


class ErrorCode:
def __init__(self, code: str, description: str, category: str) -> None:
def __init__(self, code: str,
description: str,
category: str,
default_enabled: bool = True) -> None:
self.code = code
self.description = description
self.category = category
self.default_enabled = default_enabled
error_codes[code] = self

def __str__(self) -> str:
return '<ErrorCode {}>'.format(self.code)
Expand Down
24 changes: 20 additions & 4 deletions mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,18 @@ def __init__(self,
show_error_codes: bool = False,
pretty: bool = False,
read_source: Optional[Callable[[str], Optional[List[str]]]] = None,
show_absolute_path: bool = False) -> None:
show_absolute_path: bool = False,
enabled_error_codes: Optional[Set[ErrorCode]] = None,
disabled_error_codes: Optional[Set[ErrorCode]] = None) -> None:
self.show_error_context = show_error_context
self.show_column_numbers = show_column_numbers
self.show_error_codes = show_error_codes
self.show_absolute_path = show_absolute_path
self.pretty = pretty
# We use fscache to read source code when showing snippets.
self.read_source = read_source
self.enabled_error_codes = enabled_error_codes or set()
self.disabled_error_codes = disabled_error_codes or set()
self.initialize()

def initialize(self) -> None:
Expand All @@ -195,7 +199,9 @@ def copy(self) -> 'Errors':
self.show_error_codes,
self.pretty,
self.read_source,
self.show_absolute_path)
self.show_absolute_path,
self.enabled_error_codes,
self.disabled_error_codes)
new.file = self.file
new.import_ctx = self.import_ctx[:]
new.function_or_member = self.function_or_member[:]
Expand Down Expand Up @@ -351,15 +357,25 @@ def add_error_info(self, info: ErrorInfo) -> None:
self._add_error_info(file, info)

def is_ignored_error(self, line: int, info: ErrorInfo, ignores: Dict[int, List[str]]) -> bool:
if line not in ignores:
if info.code and self.is_error_code_enabled(info.code) is False:
return True
elif line not in ignores:
return False
elif not ignores[line]:
# Empty list means that we ignore all errors
return True
elif info.code:
elif info.code and self.is_error_code_enabled(info.code) is True:
return info.code.code in ignores[line]
return False

def is_error_code_enabled(self, error_code: ErrorCode) -> bool:
if error_code in self.disabled_error_codes:
return False
elif error_code in self.enabled_error_codes:
return True
else:
return error_code.default_enabled

def clear_errors_in_targets(self, path: str, targets: Set[str]) -> None:
"""Remove errors in specific fine-grained targets within a file."""
if path in self.error_info_map:
Expand Down
26 changes: 26 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from mypy.find_sources import create_source_list, InvalidSourceList
from mypy.fscache import FileSystemCache
from mypy.errors import CompileError
from mypy.errorcodes import error_codes
from mypy.options import Options, BuildType
from mypy.config_parser import parse_version, parse_config_file
from mypy.split_namespace import SplitNamespace
Expand Down Expand Up @@ -612,6 +613,14 @@ def add_invertible_flag(flag: str,
'--strict', action='store_true', dest='special-opts:strict',
help=strict_help)

strictness_group.add_argument(
'--disable-error-code', metavar='NAME', action='append', default=[],
help="Disable a specific error code")
strictness_group.add_argument(
'--enable-error-code', metavar='NAME', action='append', default=[],
help="Enable a specific error code"
)

error_group = parser.add_argument_group(
title='Configuring error messages',
description="Adjust the amount of detail shown in error messages.")
Expand Down Expand Up @@ -860,6 +869,23 @@ def set_strict_flags() -> None:
parser.error("You can't make a variable always true and always false (%s)" %
', '.join(sorted(overlap)))

# 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("Invalid error code(s): %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

# Set build flags.
if options.strict_optional_whitelist is not None:
# TODO: Deprecate, then kill this flag
Expand Down
13 changes: 12 additions & 1 deletion mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import pprint
import sys

from typing_extensions import Final
from typing_extensions import Final, TYPE_CHECKING
from typing import Dict, List, Mapping, Optional, Pattern, Set, Tuple, Callable, Any

from mypy import defaults
from mypy.util import get_class_descriptors, replace_object_state

if TYPE_CHECKING:
from mypy.errors import ErrorCode


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

# Error codes to disable
self.disable_error_code = [] # type: List[str]
self.disabled_error_codes = set() # type: Set[ErrorCode]

# Error codes to enable
self.enable_error_code = [] # type: List[str]
self.enabled_error_codes = set() # type: Set[ErrorCode]

# Use script name instead of __main__
self.scripts_are_modules = False

Expand Down
45 changes: 45 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -1530,3 +1530,48 @@ def f(a = None):
no_implicit_optional = True
\[mypy-m]
no_implicit_optional = False

[case testDisableErrorCode]
# flags: --disable-error-code attr-defined
x = 'should be fine'
x.trim()

[case testDisableDifferentErrorCode]
# flags: --disable-error-code name-defined --show-error-code
x = 'should not be fine'
x.trim() # E: "str" has no attribute "trim" [attr-defined]

[case testDisableMultipleErrorCode]
# flags: --disable-error-code attr-defined --disable-error-code return-value --show-error-code
x = 'should be fine'
x.trim()

def bad_return_type() -> str:
return None

bad_return_type('no args taken!') # E: Too many arguments for "bad_return_type" [call-arg]

[case testEnableErrorCode]
# flags: --disable-error-code attr-defined --enable-error-code attr-defined --show-error-code
x = 'should be fine'
x.trim() # E: "str" has no attribute "trim" [attr-defined]

[case testEnableDifferentErrorCode]
# flags: --disable-error-code attr-defined --enable-error-code name-defined --show-error-code
x = 'should not be fine'
x.trim() # E: "str" has no attribute "trim" [attr-defined]

[case testEnableMultipleErrorCode]
# flags: \
--disable-error-code attr-defined \
--disable-error-code return-value \
--disable-error-code call-arg \
--enable-error-code attr-defined \
--enable-error-code return-value --show-error-code
x = 'should be fine'
x.trim() # E: "str" has no attribute "trim" [attr-defined]

def bad_return_type() -> str:
return None # E: Incompatible return value type (got "None", expected "str") [return-value]

bad_return_type('no args taken!')
50 changes: 50 additions & 0 deletions test-data/unit/cmdline.test
Original file line number Diff line number Diff line change
Expand Up @@ -1123,3 +1123,53 @@ import foo.bar
[out]
src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar'
== Return code: 2

[case testEnableInvalidErrorCode]
# cmd: mypy --enable-error-code YOLO test.py
[file test.py]
x = 1
[out]
usage: mypy [-h] [-v] [-V] [more options; see below]
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
mypy: error: Invalid error code(s): YOLO
== Return code: 2

[case testDisableInvalidErrorCode]
# cmd: mypy --disable-error-code YOLO test.py
[file test.py]
x = 1
[out]
usage: mypy [-h] [-v] [-V] [more options; see below]
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
mypy: error: Invalid error code(s): YOLO
== Return code: 2

[case testEnableAndDisableInvalidErrorCode]
# cmd: mypy --disable-error-code YOLO --enable-error-code YOLO2 test.py
[file test.py]
x = 1
[out]
usage: mypy [-h] [-v] [-V] [more options; see below]
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
mypy: error: Invalid error code(s): YOLO, YOLO2
== Return code: 2

[case testEnableValidAndInvalidErrorCode]
# cmd: mypy --enable-error-code attr-defined --enable-error-code YOLO test.py
[file test.py]
x = 1
[out]
usage: mypy [-h] [-v] [-V] [more options; see below]
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
mypy: error: Invalid error code(s): YOLO
== Return code: 2

[case testDisableValidAndInvalidErrorCode]
# cmd: mypy --disable-error-code attr-defined --disable-error-code YOLO test.py
[file test.py]
x = 1
[out]
usage: mypy [-h] [-v] [-V] [more options; see below]
[-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
mypy: error: Invalid error code(s): YOLO
== Return code: 2

0 comments on commit d0711bd

Please sign in to comment.