Disable/enable pylint messages per file/directory level.
Related issue: pylint-dev/pylint#2712
Disable/enable pylint messages per file/directory level.
Related issue: pylint-dev/pylint#2712
| [MAIN] | |
| # Append sys.path for pylint loading the plugin without installation | |
| init-hook="sys.path.append('.');" | |
| # Load the plugin | |
| load-plugins=pylint_xable_matched_plugin | |
| [MESSAGES CONTROL] | |
| # Disable for paths | |
| # usage: disable-paths=<pattern>:<msg ids>[ <pattern>:<msg ids>...] | |
| disable-paths=pylint_*_plugin.py:invalid-name,missing-function-docstring | |
| ./noxfile.py:redefined-outer-name | |
| # Disable for patterns | |
| # usage: disable-patterns=<pattern>:<msg ids>[ <pattern>:<msg ids>...] | |
| disable-patterns=^pylint_:line-too-long |
| from __future__ import annotations | |
| import re | |
| from pathlib import PurePath | |
| from typing import Type, Optional, TYPE_CHECKING | |
| from pylint import exceptions | |
| from pylint.checkers import BaseChecker | |
| if TYPE_CHECKING: | |
| from tokenize import TokenInfo | |
| from pylint.lint import PyLinter | |
| class XableMatchedPluginChecker(BaseChecker): | |
| name = "xable-matched" | |
| msgs = { | |
| "C3111": ( | |
| "Matched filepath pattern for disabling rules", | |
| "matched-disable-paths", | |
| "Used when pylint found file that should disable some rules", | |
| ), | |
| "C3112": ( | |
| "Matched filepath pattern for enabling rules", | |
| "matched-enable-paths", | |
| "Used when pylint found file that should enable some rules", | |
| ), | |
| "C3113": ( | |
| "Matched filename pattern for disabling rules", | |
| "matched-disable-patterns", | |
| "Used when pylint found file that should disable some rules", | |
| ), | |
| "C3114": ( | |
| "Matched filename pattern for enabling rules", | |
| "matched-enable-patterns", | |
| "Used when pylint found file that should enable some rules", | |
| ), | |
| } | |
| options = ( | |
| ( | |
| "disable-paths", | |
| { | |
| "type": "non_empty_string", | |
| "metavar": "<pattern>:<msg ids>[ <pattern>:<msg ids>...]", | |
| "help": "Disable the message, report, category or checker " | |
| "for files or directories matching the glob or regex patterns " | |
| "with the given id(s). You can either give multiple identifiers " | |
| "separated by comma (,) or put this option multiple times " | |
| "(only on the command line, not in the configuration file " | |
| "where it should appear only once). " | |
| 'You can also use "--disable-paths=<pattern>:all" to disable everything first ' | |
| "and then re-enable specific checks. For example, if you want " | |
| "to run only the similarities checker, you can use " | |
| '"--disable-paths=<pattern>:all --enable-paths=<pattern>:similarities". ' | |
| "If you want to run only the classes checker, but have no " | |
| "Warning level messages displayed, use " | |
| '"--disable-paths=<pattern>:all --enable-paths=<pattern>:classes --disable-paths=<pattern>:W".', | |
| }, | |
| ), | |
| ( | |
| "enable-paths", | |
| { | |
| "type": "non_empty_string", | |
| "metavar": "<pattern>:<msg ids>[ <pattern>:<msg ids>...]", | |
| "help": "Enable the message, report, category or checker " | |
| "for files or directories matching the glob or regex patterns with the " | |
| "given id(s). You can either give multiple identifier " | |
| "separated by comma (,) or put this option multiple time " | |
| "(only on the command line, not in the configuration file " | |
| "where it should appear only once). " | |
| 'See also the "--disable-paths" option for examples.', | |
| }, | |
| ), | |
| ( | |
| "disable-patterns", | |
| { | |
| "type": "non_empty_string", | |
| "metavar": "<pattern>:<msg ids>[ <pattern>:<msg ids>...]", | |
| "help": "Disable the message, report, category or checker " | |
| "for files or directories matching the glob or regex patterns " | |
| "with the given id(s). You can either give multiple identifiers " | |
| "separated by comma (,) or put this option multiple times " | |
| "(only on the command line, not in the configuration file " | |
| "where it should appear only once). " | |
| 'You can also use "--disable-patterns=<pattern>:all" to disable everything first ' | |
| "and then re-enable specific checks. For example, if you want " | |
| "to run only the similarities checker, you can use " | |
| '"--disable-patterns=<pattern>:all --enable-patterns=<pattern>:similarities". ' | |
| "If you want to run only the classes checker, but have no " | |
| "Warning level messages displayed, use " | |
| '"--disable-patterns=<pattern>:all --enable-patterns=<pattern>:classes --disable-patterns=<pattern>:W".', | |
| }, | |
| ), | |
| ( | |
| "enable-patterns", | |
| { | |
| "type": "non_empty_string", | |
| "metavar": "<pattern>:<msg ids>[ <pattern>:<msg ids>...]", | |
| "help": "Enable the message, report, category or checker " | |
| "for files or directories matching the glob or regex patterns with the " | |
| "given id(s). You can either give multiple identifier " | |
| "separated by comma (,) or put this option multiple time " | |
| "(only on the command line, not in the configuration file " | |
| "where it should appear only once). " | |
| 'See also the "--disable-patterns" option for examples.', | |
| }, | |
| ), | |
| ) | |
| @classmethod | |
| def match_glob( | |
| cls, | |
| pattern: str, | |
| filepath: str, | |
| is_basename: bool = False, | |
| ) -> bool: | |
| if is_basename: | |
| filepath = PurePath(filepath).name | |
| path = PurePath(filepath) | |
| matched = path.match(pattern) | |
| while not matched and path.parent != path: | |
| path = path.parent | |
| matched = path.match(pattern) | |
| return matched | |
| @classmethod | |
| def match_regex( | |
| cls, | |
| pattern: str, | |
| filepath: str, | |
| is_basename: bool = False, | |
| ) -> bool: | |
| if is_basename: | |
| filepath = PurePath(filepath).name | |
| return re.match(pattern, filepath) is not None | |
| @classmethod | |
| def match_path(cls, pattern: str, filepath: str) -> bool: | |
| return cls.match_glob(pattern, filepath) or cls.match_regex(pattern, filepath) | |
| @classmethod | |
| def match_pattern(cls, pattern: str, filepath: str) -> bool: | |
| return cls.match_glob(pattern, filepath, is_basename=True) or cls.match_regex( | |
| pattern, | |
| filepath, | |
| is_basename=True, | |
| ) | |
| @classmethod | |
| def parse_argument_value(cls, string: str): | |
| if string: | |
| string = string.strip() | |
| while string: | |
| pattern, string = string.split(":", 1) | |
| tokens = re.split(r"\s+", string, 1) | |
| if len(tokens) > 1: | |
| msgids, string = tokens | |
| else: | |
| msgids, string = tokens[0], "" | |
| msgids = [msgid.strip() for msgid in msgids.split(",") if msgid.strip()] | |
| yield pattern, msgids | |
| string = string.strip() | |
| def __init__(self, linter: PyLinter) -> None: | |
| super().__init__(linter) | |
| self.disable_entries = [] | |
| self.enable_entries = [] | |
| def load_configuration(self) -> None: | |
| for pattern, msgids in self.parse_argument_value( | |
| self.linter.config.disable_paths | |
| ): | |
| self.disable_entries.append( | |
| ( | |
| self.match_path, | |
| pattern, | |
| self.linter.disable, | |
| msgids, | |
| "--disable-paths", | |
| ) | |
| ) | |
| for pattern, msgids in self.parse_argument_value( | |
| self.linter.config.enable_paths | |
| ): | |
| self.enable_entries.append( | |
| ( | |
| self.match_path, | |
| pattern, | |
| self.linter.enable, | |
| msgids, | |
| "--enable-paths", | |
| ) | |
| ) | |
| for pattern, msgids in self.parse_argument_value( | |
| self.linter.config.disable_patterns | |
| ): | |
| self.disable_entries.append( | |
| ( | |
| self.match_pattern, | |
| pattern, | |
| self.linter.disable, | |
| msgids, | |
| "--disable-patterns", | |
| ) | |
| ) | |
| for pattern, msgids in self.parse_argument_value( | |
| self.linter.config.enable_paths | |
| ): | |
| self.enable_entries.append( | |
| ( | |
| self.match_pattern, | |
| pattern, | |
| self.linter.enable, | |
| msgids, | |
| "--enable-patterns", | |
| ) | |
| ) | |
| def process_tokens(self, tokens: list[TokenInfo]) -> None: | |
| for entries in [self.disable_entries, self.enable_entries]: | |
| for ( | |
| match_func, | |
| pattern, | |
| xable_func, | |
| msgids, | |
| option_string, | |
| ) in entries: | |
| if match_func(pattern, self.linter.current_file): | |
| lines = [ | |
| i for token in tokens for i in (token.start[0], token.end[0]) | |
| ] | |
| lines = set(lines) | |
| for msgid in msgids: | |
| for line in lines: | |
| try: | |
| xable_func(msgid, "line", line + 1) | |
| except ( | |
| exceptions.DeletedMessageError, | |
| exceptions.MessageBecameExtensionError, | |
| ) as e: | |
| self.linter.add_message( | |
| "useless-option-value", | |
| args=(option_string, e), | |
| line=line, | |
| ) | |
| except exceptions.UnknownMessageError: | |
| self.linter.add_message( | |
| "unknown-option-value", | |
| args=(option_string, msgid), | |
| line=line, | |
| ) | |
| def register_checker(linter: PyLinter, checker: XableMatchedPluginChecker) -> None: | |
| linter.register_checker(checker) | |
| original_process_tokens = linter.process_tokens | |
| def new_process_tokens(tokens: list[TokenInfo]): | |
| original_process_tokens(tokens) | |
| checker.process_tokens(tokens) | |
| linter.process_tokens = new_process_tokens | |
| def register(linter: PyLinter) -> None: | |
| checker = XableMatchedPluginChecker(linter) | |
| register_checker(linter, checker) | |
| def get_checker(linter: PyLinter, cls: Type[BaseChecker]) -> Optional[BaseChecker]: | |
| checkers = linter.get_checkers() | |
| checkers = filter(lambda checker: isinstance(checker, cls), checkers) | |
| checker = next(checkers, None) | |
| return checker | |
| def load_configuration(linter: PyLinter) -> None: | |
| checker = get_checker(linter, XableMatchedPluginChecker) | |
| if checker: | |
| checker.load_configuration() |