Skip to content

Add command line support to enable/disable error codes #9172

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
Show file tree
Hide file tree
Changes from all commits
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
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]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list seemed so far unused, and for my use case was not the ideal data structure, so I added the below dict. If it's preferred I can change to adding the error codes here and iterating through.


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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pass the enabled and disabled error codes to Errors, and we can use them together with the default_enabled attribute to decide whether to ignore them or not.

This way there will be less global mutable state, and it will be easier to have per-file enabled/disabled sets in the future.

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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests for invalid error codes?

# 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 @@ -1092,3 +1092,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