Skip to content
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
78 changes: 4 additions & 74 deletions pylint/checkers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,93 +39,23 @@

"""

import sys
import tokenize
import warnings
from typing import Any

from pylint.config import OptionsProviderMixIn
from pylint.interfaces import UNDEFINED
from pylint.reporters import diff_string
from pylint.checkers.base_checker import BaseChecker, BaseTokenChecker
from pylint.utils import register_plugins


def table_lines_from_stats(stats, old_stats, columns):
def table_lines_from_stats(stats, _, columns):
"""get values listed in <columns> from <stats> and <old_stats>,
and return a formated list of values, designed to be given to a
ureport.Table object
"""
lines = []
for m_type in columns:
new = stats[m_type]
format = str # pylint: disable=redefined-builtin
if isinstance(new, float):
format = lambda num: "%.3f" % num
old = old_stats.get(m_type)
if old is not None:
diff_str = diff_string(old, new)
old = format(old)
else:
old, diff_str = "NC", "NC"
lines += (m_type.replace("_", " "), format(new), old, diff_str)
new = "%.3f" % new if isinstance(new, float) else str(new)
lines += (m_type.replace("_", " "), new, "NC", "NC")
return lines


class BaseChecker(OptionsProviderMixIn):
"""base class for checkers"""

# checker name (you may reuse an existing one)
name = None # type: str
# options level (0 will be displaying in --help, 1 in --long-help)
level = 1
# ordered list of options to control the ckecker behaviour
options = () # type: Any
# messages issued by this checker
msgs = {} # type: Any
# reports issued by this checker
reports = () # type: Any
# mark this checker as enabled or not.
enabled = True

def __init__(self, linter=None):
"""checker instances should have the linter as argument

linter is an object implementing ILinter
"""
if self.name is not None:
self.name = self.name.lower()
OptionsProviderMixIn.__init__(self)
self.linter = linter

def add_message(
self,
msg_id,
line=None,
node=None,
args=None,
confidence=UNDEFINED,
col_offset=None,
):
"""add a message of a given type"""
self.linter.add_message(msg_id, line, node, args, confidence, col_offset)

# dummy methods implementing the IChecker interface

def open(self):
"""called before visiting project (i.e set of modules)"""

def close(self):
"""called after visiting project (i.e set of modules)"""


class BaseTokenChecker(BaseChecker):
"""Base class for checkers that want to have access to the token stream."""

def process_tokens(self, tokens):
"""Should be overridden by subclasses."""
raise NotImplementedError()


def initialize(linter):
"""initialize linter with checkers in this package """
register_plugins(linter, __path__[0])
Expand Down
16 changes: 5 additions & 11 deletions pylint/checkers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@
import astroid.scoped_nodes

import pylint.utils as lint_utils
from pylint import checkers, exceptions, interfaces, reporters
from pylint import checkers, exceptions, interfaces
from pylint.checkers import utils
from pylint.checkers.utils import get_node_last_lineno
from pylint.reporters.ureports import nodes as reporter_nodes


Expand Down Expand Up @@ -350,7 +349,7 @@ def _has_abstract_methods(node):
return len(utils.unimplemented_abstract_methods(node)) > 0


def report_by_type_stats(sect, stats, old_stats):
def report_by_type_stats(sect, stats, _):
"""make a report of

* percentage of different types documented
Expand Down Expand Up @@ -379,16 +378,11 @@ def report_by_type_stats(sect, stats, old_stats):
lines = ("type", "number", "old number", "difference", "%documented", "%badname")
for node_type in ("module", "class", "method", "function"):
new = stats[node_type]
old = old_stats.get(node_type, None)
if old is not None:
diff_str = reporters.diff_string(old, new)
else:
old, diff_str = "NC", "NC"
lines += (
node_type,
str(new),
str(old),
diff_str,
"NC",
"NC",
nice_stats[node_type].get("percent_documented", "0"),
nice_stats[node_type].get("percent_badname", "0"),
)
Expand Down Expand Up @@ -1958,7 +1952,7 @@ def _check_docstring(
if docstring is None:
if not report_missing:
return
lines = get_node_last_lineno(node) - node.lineno
lines = utils.get_node_last_lineno(node) - node.lineno

if node_type == "module" and not lines:
# If the module has no body, there's no reason
Expand Down
102 changes: 102 additions & 0 deletions pylint/checkers/base_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <[email protected]>
# Copyright (c) 2013-2014 Google, Inc.
# Copyright (c) 2013 [email protected] <[email protected]>
# Copyright (c) 2014-2017 Claudiu Popa <[email protected]>
# Copyright (c) 2014 Brett Cannon <[email protected]>
# Copyright (c) 2014 Arun Persaud <[email protected]>
# Copyright (c) 2015 Ionel Cristian Maries <[email protected]>
# Copyright (c) 2016 Moises Lopez <[email protected]>
# Copyright (c) 2017-2018 Bryce Guinta <[email protected]>

# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/master/COPYING

from typing import Any

from pylint.config import OptionsProviderMixIn
from pylint.exceptions import InvalidMessageError
from pylint.interfaces import UNDEFINED
from pylint.message import build_message_definition


class BaseChecker(OptionsProviderMixIn):

# checker name (you may reuse an existing one)
name = None # type: str
# options level (0 will be displaying in --help, 1 in --long-help)
level = 1
# ordered list of options to control the ckecker behaviour
options = () # type: Any
# messages issued by this checker
msgs = {} # type: Any
# reports issued by this checker
reports = () # type: Any
# mark this checker as enabled or not.
enabled = True

def __init__(self, linter=None):
"""checker instances should have the linter as argument

:param ILinter linter: is an object implementing ILinter."""
if self.name is not None:
self.name = self.name.lower()
OptionsProviderMixIn.__init__(self)
self.linter = linter

def add_message(
self,
msgid,
line=None,
node=None,
args=None,
confidence=UNDEFINED,
col_offset=None,
):
self.linter.add_message(msgid, line, node, args, confidence, col_offset)

def check_consistency(self) -> None:
"""Check the consistency of msgid.

msg ids for a checker should be a string of len 4, where the two first
characters are the checker id and the two last the msg id in this
checker.

:raises InvalidMessageError: If the checker id in the messages are not
always the same. """
checker_id = None
existing_ids = []
for message in self.messages:
if checker_id is not None and checker_id != message.msgid[1:3]:
error_msg = "Inconsistent checker part in message id "
error_msg += "'{}' (expected 'x{checker_id}xx' ".format(
message.msgid, checker_id=checker_id
)
error_msg += "because we already had {existing_ids}).".format(
existing_ids=existing_ids
)
raise InvalidMessageError(error_msg)
checker_id = message.msgid[1:3]
existing_ids.append(message.msgid)

@property
def messages(self) -> list:
return [
build_message_definition(self, msgid, msg_tuple)
for msgid, msg_tuple in sorted(self.msgs.items())
]

# dummy methods implementing the IChecker interface

def open(self):
"""called before visiting project (i.e set of modules)"""

def close(self):
"""called after visiting project (i.e set of modules)"""


class BaseTokenChecker(BaseChecker):
"""Base class for checkers that want to have access to the token stream."""

def process_tokens(self, tokens):
"""Should be overridden by subclasses."""
raise NotImplementedError()
2 changes: 1 addition & 1 deletion pylint/checkers/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@

from pylint.checkers import BaseTokenChecker
from pylint.checkers.utils import check_messages
from pylint.constants import OPTION_RGX, WarningScope
from pylint.interfaces import IAstroidChecker, IRawChecker, ITokenChecker
from pylint.utils import OPTION_RGX, WarningScope

_ASYNC_TOKEN = "async"
_CONTINUATION_BLOCK_OPENERS = [
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
import tokenize

from pylint.checkers import BaseChecker
from pylint.constants import OPTION_RGX
from pylint.interfaces import IRawChecker, ITokenChecker
from pylint.message import MessagesHandlerMixIn
from pylint.utils import OPTION_RGX


class ByIdManagedMessagesChecker(BaseChecker):
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/python3.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
from pylint import checkers, interfaces
from pylint.checkers import utils
from pylint.checkers.utils import find_try_except_wrapper_node, node_ignores_exception
from pylint.constants import WarningScope
from pylint.interfaces import INFERENCE, INFERENCE_FAILURE
from pylint.utils import WarningScope

_ZERO = re.compile("^0+$")

Expand Down
10 changes: 2 additions & 8 deletions pylint/checkers/raw_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@
from pylint.checkers import BaseTokenChecker
from pylint.exceptions import EmptyReportError
from pylint.interfaces import ITokenChecker
from pylint.reporters import diff_string
from pylint.reporters.ureports.nodes import Table


def report_raw_stats(sect, stats, old_stats):
def report_raw_stats(sect, stats, _):
"""calculate percentage of code / doc / comment / empty
"""
total_lines = stats["total_lines"]
Expand All @@ -37,12 +36,7 @@ def report_raw_stats(sect, stats, old_stats):
key = node_type + "_lines"
total = stats[key]
percent = float(total * 100) / total_lines
old = old_stats.get(key, None)
if old is not None:
diff_str = diff_string(old, total)
else:
old, diff_str = "NC", "NC"
lines += (node_type, str(total), "%.2f" % percent, str(old), diff_str)
lines += (node_type, str(total), "%.2f" % percent, "NC", "NC")
sect.append(Table(children=lines, cols=5, rheaders=1))


Expand Down
14 changes: 14 additions & 0 deletions pylint/message/constants.py → pylint/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/master/COPYING

import re

# Allow stopping after the first semicolon/hash encountered,
# so that an option can be continued with the reasons
# why it is active or disabled.
OPTION_RGX = re.compile(r"\s*#.*\bpylint:\s*([^;#]+)[;#]{0,1}")

PY_EXTS = (".py", ".pyc", ".pyo", ".pyw", ".so", ".dll")

MSG_STATE_CONFIDENCE = 2
_MSG_ORDER = "EWRCIF"
MSG_STATE_SCOPE_CONFIG = 0
Expand All @@ -22,3 +31,8 @@
MSG_TYPES_LONG = {v: k for k, v in MSG_TYPES.items()}

MSG_TYPES_STATUS = {"I": 0, "C": 16, "R": 8, "W": 4, "E": 2, "F": 1}


class WarningScope:
LINE = "line-based-msg"
NODE = "node-based-msg"
9 changes: 5 additions & 4 deletions pylint/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@

from pylint import checkers, config, exceptions, interfaces, reporters
from pylint.__pkginfo__ import version
from pylint.message import MSG_TYPES, Message, MessagesHandlerMixIn, MessagesStore
from pylint.constants import MSG_TYPES, OPTION_RGX
from pylint.message import Message, MessagesHandlerMixIn, MessagesStore
from pylint.reporters.ureports import nodes as report_nodes
from pylint.utils import OPTION_RGX, ASTWalker, FileState, ReportsHandlerMixIn, utils
from pylint.utils import ASTWalker, FileState, utils

try:
import multiprocessing
Expand Down Expand Up @@ -307,7 +308,7 @@ def _run_linter(self, file_or_module):
class PyLinter(
config.OptionsManagerMixIn,
MessagesHandlerMixIn,
ReportsHandlerMixIn,
reporters.ReportsHandlerMixIn,
checkers.BaseTokenChecker,
):
"""lint Python modules using external checkers.
Expand Down Expand Up @@ -619,7 +620,7 @@ def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None):
sys.version,
)
MessagesHandlerMixIn.__init__(self)
ReportsHandlerMixIn.__init__(self)
reporters.ReportsHandlerMixIn.__init__(self)
super(PyLinter, self).__init__(
usage=__doc__, version=full_version, config_file=pylintrc or config.PYLINTRC
)
Expand Down
11 changes: 1 addition & 10 deletions pylint/message/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,7 @@

"""All the classes related to Message handling."""

from pylint.message.build_message_definition import build_message_def
from pylint.message.constants import (
_SCOPE_EXEMPT,
MSG_STATE_CONFIDENCE,
MSG_STATE_SCOPE_CONFIG,
MSG_STATE_SCOPE_MODULE,
MSG_TYPES,
MSG_TYPES_LONG,
MSG_TYPES_STATUS,
)
from pylint.message.build_message_definition import build_message_definition
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ I didn't notice this last time, is it possible to move build_message_definition into message_definition itself so we can squash that module with a single exported function?

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried but there definitely is a circular import here :

py36/lib/python3.6/site-packages/pylint/checkers/__init__.py:43: in <module>
    from pylint.checkers.base_checker import BaseChecker, BaseTokenChecker
py36/lib/python3.6/site-packages/pylint/checkers/base_checker.py:19: in <module>
    from pylint.message.message_definition import build_message_definition
py36/lib/python3.6/site-packages/pylint/message/__init__.py:43: in <module>
    from pylint.message.message_handler_mix_in import MessagesHandlerMixIn
py36/lib/python3.6/site-packages/pylint/message/message_handler_mix_in.py:24: in <module>
    from pylint.message.message_definition import build_message_definition
py36/lib/python3.6/site-packages/pylint/message/message_definition.py:12: in <module>
    from pylint.message.message_definition import MessageDefinition
E   ImportError: cannot import name 'MessageDefinition'

I wanted to move the message building in BaseChecker to fix this, but the refactor is humongous see : #2844

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh gotcha, thanks for the extra explanation.

from pylint.message.message import Message
from pylint.message.message_definition import MessageDefinition
from pylint.message.message_handler_mix_in import MessagesHandlerMixIn
Expand Down
4 changes: 2 additions & 2 deletions pylint/message/build_message_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

import warnings

from pylint.constants import WarningScope
from pylint.interfaces import IRawChecker, ITokenChecker, implements
from pylint.message.message_definition import MessageDefinition
from pylint.utils.warning_scope import WarningScope


def build_message_def(checker, msgid, msg_tuple):
def build_message_definition(checker, msgid, msg_tuple):
if implements(checker, (IRawChecker, ITokenChecker)):
default_scope = WarningScope.LINE
else:
Expand Down
Loading