Skip to content

Instantly share code, notes, and snippets.

@elbakramer
Last active September 24, 2022 17:19
Show Gist options
  • Save elbakramer/231e565dc65aecca311fba7b3f71479a to your computer and use it in GitHub Desktop.
Save elbakramer/231e565dc65aecca311fba7b3f71479a to your computer and use it in GitHub Desktop.
[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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment