diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md index 9959c6b83..603abeb19 100644 --- a/docs/usage/configuration.md +++ b/docs/usage/configuration.md @@ -54,9 +54,11 @@ python_ta.check_all(..., load_default_config=False) ## Custom Error Messages -PythonTA allows for pylint error messages to be overridden with more user friendly messages. +PythonTA allows for pylint error messages to be overridden with more user-friendly messages. These messages are specified in `config/messages_config.toml` in the source code. The user can provide their own messages configuration file by specifying `messages-config-path` in their `.pylintrc` file. +Note that the users' custom messages have priority over both pylint's and PythonTA's messages; see the `use-pyta-error-messages` +option below for more info on this. ## Reporters @@ -94,19 +96,20 @@ python_ta.check_all(..., output='pyta_output.txt') This options is compatible with all of PythonTA's reporter types, but we do not recommend its use with ColorReporter, as this reporter uses terminal-specific characters to colourize text displayed on your screen. -## Overwrite Error Messages +## Use PythonTA's Error Messages -By default, PythonTA overwrites Pylint's error messages with its own to make them more reader-friendly. You can specify whether -you want Pylint's default error messages instead using the `overwrite-error-messages` option. For example, use the following configuration to -see Pylint's error messages: +By default, PythonTA overwrites some of pylint's error messages with its own to make them more beginner-friendly. You can +prevent this by setting the `use-pyta-error-messages` option to `False`. ```python import python_ta python_ta.check_all(config={ - "overwrite-error-messages": False + "use-pyta-error-messages": False }) ``` +Note that any custom messages set using the `messages-config-path` option will not be affected by this configuration. + ## Forbidden Imports By default, PythonTA has a list of modules that are allowed to be imported. You can specify any additional modules using the `extra-imports` configuration option, which you can set in a call to `python_ta.check_all` or in a configuration file. diff --git a/python_ta/__init__.py b/python_ta/__init__.py index 0cba14ba2..bfb5f62e1 100644 --- a/python_ta/__init__.py +++ b/python_ta/__init__.py @@ -120,15 +120,14 @@ def _check( current_reporter.set_output(output) messages_config_path = linter.config.messages_config_path messages_config_default_path = linter._option_dicts["messages-config-path"]["default"] - - messages_config = load_messages_config(messages_config_path, messages_config_default_path) - overwrite_error_messages = linter.config.overwrite_error_messages + use_pyta_error_messages = linter.config.use_pyta_error_messages + messages_config = load_messages_config( + messages_config_path, messages_config_default_path, use_pyta_error_messages + ) global PYLINT_PATCHED if not PYLINT_PATCHED: - patch_all( - messages_config, overwrite_error_messages - ) # Monkeypatch pylint (override certain methods) + patch_all(messages_config) # Monkeypatch pylint (override certain methods) PYLINT_PATCHED = True # Try to check file, issue error message for invalid files. @@ -291,12 +290,12 @@ def reset_linter( }, ), ( - "overwrite-error-messages", + "use-pyta-error-messages", { "default": True, "type": "yn", "metavar": "", - "help": "Overwrite the default pylint error messages", + "help": "Overwrite the default pylint error messages with PythonTA's messages", }, ), ) diff --git a/python_ta/config/.pylintrc b/python_ta/config/.pylintrc index 6c9756c69..991b1c135 100644 --- a/python_ta/config/.pylintrc +++ b/python_ta/config/.pylintrc @@ -23,8 +23,8 @@ pyta-server-address = http://127.0.0.1:5000 # Path to messages_config toml file # messages-config-path = messages.toml -# Set whether the default error messages by pylint should be overwritten by python TA's custom messages -overwrite-error-messages = yes +# Set whether the default error messages by pylint should be overwritten by PythonTA's custom messages +use-pyta-error-messages = yes [REPORTS] # The type of reporter to use to display results. Available PyTA classes are diff --git a/python_ta/config/__init__.py b/python_ta/config/__init__.py index 1402b3317..59e007e39 100644 --- a/python_ta/config/__init__.py +++ b/python_ta/config/__init__.py @@ -64,30 +64,33 @@ def override_config(linter: PyLinter, config_location: AnyStr) -> None: linter.config_file = config_location -def load_messages_config(path: str, default_path: str) -> dict: +def load_messages_config(path: str, default_path: str, use_pyta_error_messages: bool) -> dict: """Given path (potentially) specified by user and default default_path - of messages config file, merge the config files.""" + of messages config file, merge the config files. We will only add the + PythonTA error messages if use_pyta_error_messages is True""" merge_into = toml.load(default_path) + # assume the user is not going to provide a path which is the same as the default if Path(default_path).resolve() == Path(path).resolve(): + merge_from = {} + else: + try: + merge_from = toml.load(path) + except FileNotFoundError: + print(f"[WARNING] Could not find messages config file at {str(Path(path).resolve())}.") + merge_from = {} + + if use_pyta_error_messages: + for category in merge_from: + if category not in merge_into: + merge_into[category] = {} + for checker in merge_from[category]: + if checker not in merge_into[category]: + merge_into[category][checker] = {} + for error_code in merge_from[category][checker]: + merge_into[category][checker][error_code] = merge_from[category][checker][ + error_code + ] return merge_into - - try: - merge_from = toml.load(path) - except FileNotFoundError: - print( - f"[WARNING] Could not find messages config file at {str(Path(path).resolve())}. Using default messages config file at {str(Path(default_path).resolve())}." - ) - return merge_into - - for category in merge_from: - if category not in merge_into: - merge_into[category] = {} - for checker in merge_from[category]: - if checker not in merge_into[category]: - merge_into[category][checker] = {} - for error_code in merge_from[category][checker]: - merge_into[category][checker][error_code] = merge_from[category][checker][ - error_code - ] - return merge_into + else: + return merge_from diff --git a/python_ta/patches/__init__.py b/python_ta/patches/__init__.py index 266b5897b..ba8689ab8 100644 --- a/python_ta/patches/__init__.py +++ b/python_ta/patches/__init__.py @@ -6,10 +6,9 @@ from .transforms import patch_ast_transforms -def patch_all(messages_config: dict, overwrite_error_messages: bool): +def patch_all(messages_config: dict): """Execute all patches defined in this module.""" patch_checkers() patch_ast_transforms() patch_messages() - if overwrite_error_messages: - patch_error_messages(messages_config) + patch_error_messages(messages_config) diff --git a/tests/test_config/test_config.py b/tests/test_config/test_config.py index 0e21ddfc2..178a72dea 100644 --- a/tests/test_config/test_config.py +++ b/tests/test_config/test_config.py @@ -12,7 +12,6 @@ "pyta-number-of-messages": 10, "max-nested-blocks": 5, "max-line-length": 120, - "overwrite-error-messages": True, } # Non-fatal config errors. Fatal errors will be checked individually. diff --git a/tests/test_messages_config/test.messages_config.toml b/tests/test_messages_config/test.messages_config.toml index 9045d4eb8..75d726096 100644 --- a/tests/test_messages_config/test.messages_config.toml +++ b/tests/test_messages_config/test.messages_config.toml @@ -1,2 +1,2 @@ ["pylint.checkers.base".BasicChecker] -E0111 = "This custom error message is modified." +W0101 = "This custom error message is modified." diff --git a/tests/test_messages_config/test.pylintrc b/tests/test_messages_config/test.pylintrc deleted file mode 100644 index 1eb5ffd19..000000000 --- a/tests/test_messages_config/test.pylintrc +++ /dev/null @@ -1,131 +0,0 @@ -[CUSTOM PYTA OPTIONS] - -# Make sure to register custom options tuple first in `python_ta/__init__.py` -# =========================================================== -# Default max amount of messages for reporter to display. -pyta-number-of-messages = 0 - -# (DEPRECATED: Use output-format option below.) Set the [REPORTS] output-format option instead. -# pyta-reporter = HTMLReporter - -# Set the location of the template for HTMLreporter. -pyta-template-file = template.html.jinja - -#Set whether you wish to opt-in to anonymous data collection of errors reported when you run PyTA. -pyta-error-permission = no - -#Set whether you wish to opt-in to anonymous data collection of the files you run PyTA on. -pyta-file-permission = no - -# Server address for data submission participation. -pyta-server-address = http://127.0.0.1:5000 - -# Path to messages_config toml file -messages-config-path = tests/test_messages_config/test.messages_config.toml - -[REPORTS] -# The type of reporter to use to display results. Available PyTA classes are -# PlainReporter, ColorReporter, HTMLReporter, JSONReporter. -# Replaces the pyta-reporter option. -output-format = python_ta.reporters.PlainReporter - -[ELIF] - -# Set maximum allowed if nesting. -max-nested-blocks = 3 - -[FORMAT] - -# Set the maximum line length. The maximum line length in pep8 is 80 characters. -max-line-length = 80 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines = ^\s*((# )??)|(>>>.*)$ - -[FORBIDDEN IMPORT] - -# Set the whitelist of modules that are allowed to be imported -allowed-import-modules = dataclasses, doctest, unittest, hypothesis, pytest, python_ta, python_ta.contracts, - timeit, typing, __future__ -extra-imports = -[FORBIDDEN IO] - -# Comma-separated names of functions that are allowed to contain IO actions -allowed-io = - -[MESSAGES CONTROL] - -# Disable the message, report, category or checker with the given id(s). -disable= - E0100, E0105, E0106, E0110, E0112, E0113, E0114, E0115, E0116, E0117, E0118, - E0236, E0237, E0238, E0240, E0242, E0243, E0244, E0305, E0308, E0309, E0310, E0311, E0312, E0313, - E0402, - E0603, E0604, E0605, - E0703, W0707, - E1124, E1125, E1132, E1139, E1142, - E1200, E1201, E1205, E1206, - E1300, E1301, E1302, E1303, E1304, - W1406, - E1507, E1519, - E1700, E1701, - E9900, - W0120, W0131, W0150, W0177, - W0213, W0235, W0236, W0238, W0244, W0246, - W0601, W0602, W0614, W0640, - W1113, W1115, - W1300, W1301, W1302, W1306, W1307, - W1502, W1503, W1505, W1506, W1507, W1508, W1509, W1510, W1511, W1512, W1513, W1514, W1518, - C0105, C0131, C0132, - C0200, C0202, C0203, C0204, C0205, C0206, C0207, C0208, - C0327, C0328, - R0202, R0203, R0206, - R0901, R0903, R0904, R0911, - R1703, R1705, R1706, R1708, R1709, R1711, R1717, R1718, R1719, R1720, R1722, R1723, R1724, - R1728, R1729, R1730, R1731, - C1803, - F0202, - W0402, W0407, - W0603, - W1201, W1202, - I0023, - I1101, - lambda-expressions, - similarities, - spelling, - threading, - unnecessary-dunder-call, - unsupported_version, - E2502, E2510, E2511, E2512, E2513, E2514, E2515, - missing-timeout, positional-only-arguments-expected - - -# Enable single-letter identifiers -function-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -variable-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*)|([A-Z_][A-Z0-9_]*))$ -const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__))$ -attr-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -argument-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -class-rgx = (([A-Z][a-zA-Z0-9]{0,30})|(_[A-Z][a-zA-Z0-9]*))$ -method-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -class-attribute-rgx = ([A-Za-z_][A-Za-z0-9_]{0,30}|(__.*__))$ - -[PYCODESTYLE] -# Ignored pycodestyle checks -pycodestyle-ignore = - E111, # pylint W0311 - E114, # pylint W0311 - E117, # pylint W0311 - E401, # pylint C0410 - E402, # pylint C0413 - E501, # pylint C0301 - E701, # pylint C0321 - E702, # pylint C0321 - E703, # pylint W0301 - E711, # pylint C0121 - E712, # pylint C0121 - E722, # pylint W0702 - W291, # pylint C0303 - W292, # pylint C0304 - W293, # pylint C0303 - W391, # pylint C0305 - W503 # this one just conflicts with pycodestyle W504 diff --git a/tests/test_messages_config/test_messages_config.py b/tests/test_messages_config/test_messages_config.py index e4c0c4adf..b1c4117e5 100644 --- a/tests/test_messages_config/test_messages_config.py +++ b/tests/test_messages_config/test_messages_config.py @@ -2,79 +2,85 @@ import sys -def test_modified_message(): - """Test the presence of modified custom message.""" +def test_valid_message_config_and_pyta_overwrite(): + """Test the error messages given a valid messages-config-path with use-pyta-error-messages as True""" output = subprocess.run( [ sys.executable, "-m", "python_ta", "--config", - "tests/test_messages_config/test.pylintrc", - "examples/pylint/e0111_bad_reversed_sequence.py", + "tests/test_messages_config/test1.pylintrc", + "tests/test_messages_config/testing_code.py", ], capture_output=True, text=True, ) - assert "This custom error message is modified." in output.stdout + assert ( + "This custom error message is modified." in output.stdout + and "The first reversed() argument is not a sequence" not in output.stdout + ) -def test_default_message(): - """Test the presence of default custom message not present in new configuration file.""" +def test_no_message_config_and_pyta_overwrite(): + """Test the error messages without a messages-config-path with use-pyta-error-messages as True""" output = subprocess.run( [ sys.executable, "-m", "python_ta", "--config", - "tests/test_messages_config/test.pylintrc", - "examples/pylint/w0101_unreachable.py", + "tests/test_messages_config/test2.pylintrc", + "tests/test_messages_config/testing_code.py", ], capture_output=True, text=True, ) assert ( - "Code after a return or raise statement will never be run, so you should either remove this code or review the above return/raise statement(s)." - in output.stdout + "This custom error message is modified." not in output.stdout + and "The first reversed() argument is not a sequence" not in output.stdout ) -def test_not_modified_message(): - """Test the modified custom message is not present.""" +def test_valid_message_config_and_no_pyta_overwrite(): + """Test the error messages given a valid messages-config-path with use-pyta-error-messages as False""" output = subprocess.run( [ sys.executable, "-m", "python_ta", "--config", - "tests/test_messages_config/test_not_overwritten.pylintrc", - "examples/pylint/e0111_bad_reversed_sequence.py", + "tests/test_messages_config/test3.pylintrc", + "tests/test_messages_config/testing_code.py", ], capture_output=True, text=True, ) - assert "This custom error message is modified." not in output.stdout + assert ( + "This custom error message is modified." in output.stdout + and "The first reversed() argument is not a sequence" in output.stdout + ) -def test_not_default_message(): - """Test the presence of default custom message is present in new configuration file.""" +def test_no_message_config_and_no_pyta_overwrite(): + """Test the error messages without a messages-config-path with use-pyta-error-messages as False""" output = subprocess.run( [ sys.executable, "-m", "python_ta", "--config", - "tests/test_messages_config/test_not_overwritten.pylintrc", - "examples/pylint/w0101_unreachable.py", + "tests/test_messages_config/test4.pylintrc", + "tests/test_messages_config/testing_code.py", ], capture_output=True, text=True, ) assert ( - "Code after a return or raise statement will never be run, so you should either remove this code or review the above return/raise statement(s)." - not in output.stdout + "This custom error message is modified." not in output.stdout + and "The first reversed() argument is not a sequence" in output.stdout ) diff --git a/tests/test_messages_config/test_not_overwritten.pylintrc b/tests/test_messages_config/test_not_overwritten.pylintrc deleted file mode 100644 index 290e9c9f9..000000000 --- a/tests/test_messages_config/test_not_overwritten.pylintrc +++ /dev/null @@ -1,134 +0,0 @@ -[CUSTOM PYTA OPTIONS] - -# Make sure to register custom options tuple first in `python_ta/__init__.py` -# =========================================================== -# Default max amount of messages for reporter to display. -pyta-number-of-messages = 0 - -# (DEPRECATED: Use output-format option below.) Set the [REPORTS] output-format option instead. -# pyta-reporter = HTMLReporter - -# Set the location of the template for HTMLreporter. -pyta-template-file = template.html.jinja - -#Set whether you wish to opt-in to anonymous data collection of errors reported when you run PyTA. -pyta-error-permission = no - -#Set whether you wish to opt-in to anonymous data collection of the files you run PyTA on. -pyta-file-permission = no - -# Server address for data submission participation. -pyta-server-address = http://127.0.0.1:5000 - -# Path to messages_config toml file -messages-config-path = tests/test_messages_config/test.messages_config.toml - -# Set whether the default error messages by pylint should be overwritten by python TA's custom messages -overwrite-error-messages = no - -[REPORTS] -# The type of reporter to use to display results. Available PyTA classes are -# PlainReporter, ColorReporter, HTMLReporter, JSONReporter. -# Replaces the pyta-reporter option. -output-format = python_ta.reporters.PlainReporter - -[ELIF] - -# Set maximum allowed if nesting. -max-nested-blocks = 3 - -[FORMAT] - -# Set the maximum line length. The maximum line length in pep8 is 80 characters. -max-line-length = 80 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines = ^\s*((# )??)|(>>>.*)$ - -[FORBIDDEN IMPORT] - -# Set the whitelist of modules that are allowed to be imported -allowed-import-modules = dataclasses, doctest, unittest, hypothesis, pytest, python_ta, python_ta.contracts, - timeit, typing, __future__ -extra-imports = -[FORBIDDEN IO] - -# Comma-separated names of functions that are allowed to contain IO actions -allowed-io = - -[MESSAGES CONTROL] - -# Disable the message, report, category or checker with the given id(s). -disable= - E0100, E0105, E0106, E0110, E0112, E0113, E0114, E0115, E0116, E0117, E0118, - E0236, E0237, E0238, E0240, E0242, E0243, E0244, E0305, E0308, E0309, E0310, E0311, E0312, E0313, - E0402, - E0603, E0604, E0605, - E0703, W0707, - E1124, E1125, E1132, E1139, E1142, - E1200, E1201, E1205, E1206, - E1300, E1301, E1302, E1303, E1304, - W1406, - E1507, E1519, - E1700, E1701, - E9900, - W0120, W0131, W0150, W0177, - W0213, W0235, W0236, W0238, W0244, W0246, - W0601, W0602, W0614, W0640, - W1113, W1115, - W1300, W1301, W1302, W1306, W1307, - W1502, W1503, W1505, W1506, W1507, W1508, W1509, W1510, W1511, W1512, W1513, W1514, W1518, - C0105, C0131, C0132, - C0200, C0202, C0203, C0204, C0205, C0206, C0207, C0208, - C0327, C0328, - R0202, R0203, R0206, - R0901, R0903, R0904, R0911, - R1703, R1705, R1706, R1708, R1709, R1711, R1717, R1718, R1719, R1720, R1722, R1723, R1724, - R1728, R1729, R1730, R1731, - C1803, - F0202, - W0402, W0407, - W0603, - W1201, W1202, - I0023, - I1101, - lambda-expressions, - similarities, - spelling, - threading, - unnecessary-dunder-call, - unsupported_version, - E2502, E2510, E2511, E2512, E2513, E2514, E2515, - missing-timeout, positional-only-arguments-expected - - -# Enable single-letter identifiers -function-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -variable-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*)|([A-Z_][A-Z0-9_]*))$ -const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__))$ -attr-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -argument-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -class-rgx = (([A-Z][a-zA-Z0-9]{0,30})|(_[A-Z][a-zA-Z0-9]*))$ -method-rgx = (([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ -class-attribute-rgx = ([A-Za-z_][A-Za-z0-9_]{0,30}|(__.*__))$ - -[PYCODESTYLE] -# Ignored pycodestyle checks -pycodestyle-ignore = - E111, # pylint W0311 - E114, # pylint W0311 - E117, # pylint W0311 - E401, # pylint C0410 - E402, # pylint C0413 - E501, # pylint C0301 - E701, # pylint C0321 - E702, # pylint C0321 - E703, # pylint W0301 - E711, # pylint C0121 - E712, # pylint C0121 - E722, # pylint W0702 - W291, # pylint C0303 - W292, # pylint C0304 - W293, # pylint C0303 - W391, # pylint C0305 - W503 # this one just conflicts with pycodestyle W504