Comprehensive static code analysis tool for Python that performs deep code inspection without executing the program
—
Structured message definitions with categories, confidence levels, and detailed location information for precise error reporting and filtering. Pylint's message system provides comprehensive information about code issues with standardized formatting and metadata.
Core classes for representing individual messages and their metadata.
class Message:
"""
Individual message/warning representation.
Attributes:
msg_id (str): Message identifier (e.g., 'C0111')
symbol (str): Message symbol (e.g., 'missing-docstring')
msg (str): Formatted message text
C (str): Message category letter (C/R/W/E/F/I)
category (str): Full category name
confidence (str): Confidence level
abspath (str): Absolute file path
path (str): Relative file path
module (str): Module name
obj (str): Object name (function, class, etc.)
line (int): Line number
column (int): Column number
end_line (int): End line number (optional)
end_column (int): End column number (optional)
"""
def __init__(self, msg_id, line=None, node=None, args=None,
confidence=None, col_offset=None, end_lineno=None,
end_col_offset=None):
"""
Initialize message instance.
Args:
msg_id (str): Message identifier
line (int): Line number
node: AST node
args (tuple): Message arguments
confidence (str): Confidence level
col_offset (int): Column offset
end_lineno (int): End line number
end_col_offset (int): End column offset
"""
def format(self, template=None):
"""
Format message according to template.
Args:
template (str): Format template
Returns:
str: Formatted message
"""Classes for defining and managing message types and their properties.
class MessageDefinition:
"""
Message type definition.
Defines the structure and properties of a specific message type
including its identifier, description, and formatting template.
"""
def __init__(self, checker, msgid, msg, description, symbol=None,
scope=None, minversion=None, maxversion=None,
old_names=None):
"""
Initialize message definition.
Args:
checker: Checker that defines this message
msgid (str): Unique message identifier (e.g., 'C0111')
msg (str): Message template with placeholders
description (str): Detailed description
symbol (str): Symbolic name (e.g., 'missing-docstring')
scope (str): Scope where message applies
minversion (tuple): Minimum Python version
maxversion (tuple): Maximum Python version
old_names (list): Previous names for this message
"""
@property
def msgid(self):
"""Message identifier."""
return self._msgid
@property
def symbol(self):
"""Message symbol."""
return self._symbol
def format_help(self, checkerref=False):
"""
Format help text for this message.
Args:
checkerref (bool): Include checker reference
Returns:
str: Formatted help text
"""
class MessageDefinitionStore:
"""
Storage for message definitions.
Manages the collection of all message definitions from
all checkers, providing lookup and validation capabilities.
"""
def __init__(self):
"""Initialize empty message definition store."""
self._messages_definitions = {}
self._msgs_by_category = {}
def register_message(self, message_definition):
"""
Register a message definition.
Args:
message_definition: MessageDefinition instance
"""
def get_message_definitions(self, msgid_or_symbol):
"""
Get message definition by ID or symbol.
Args:
msgid_or_symbol (str): Message ID or symbol
Returns:
MessageDefinition: Message definition if found
"""
def get_msg_display_string(self, msgid):
"""
Get display string for message.
Args:
msgid (str): Message identifier
Returns:
str: Display string combining ID and symbol
"""Classes for managing message identifiers and symbolic names.
class MessageIdStore:
"""
Message ID management and lookup.
Provides mapping between message IDs, symbols, and their
definitions, including support for deprecated message names.
"""
def __init__(self):
"""Initialize message ID store."""
self._msgid_to_symbol = {}
self._symbol_to_msgid = {}
self._old_names = {}
def register_message_definition(self, msgid, symbol, old_names=None):
"""
Register message ID and symbol mapping.
Args:
msgid (str): Message identifier
symbol (str): Message symbol
old_names (list): List of deprecated names
"""
def get_active_msgids(self, msgid_or_symbol):
"""
Get active message IDs for given ID or symbol.
Args:
msgid_or_symbol (str): Message ID or symbol
Returns:
list: Active message IDs
"""
def get_msgid(self, symbol):
"""
Get message ID from symbol.
Args:
symbol (str): Message symbol
Returns:
str: Message ID
"""
def get_symbol(self, msgid):
"""
Get symbol from message ID.
Args:
msgid (str): Message ID
Returns:
str: Message symbol
"""# Message category mappings
MSG_TYPES = {
'I': 'info', # Informational messages
'C': 'convention', # Coding standard violations
'R': 'refactor', # Refactoring suggestions
'W': 'warning', # Potential issues
'E': 'error', # Probable bugs
'F': 'fatal' # Errors preventing further processing
}
# Category status codes for exit codes
MSG_TYPES_STATUS = {
'I': 0, # Info - no impact on exit code
'C': 2, # Convention violation
'R': 4, # Refactor suggestion
'W': 8, # Warning
'E': 16, # Error
'F': 32 # Fatal error
}# Confidence level constants
HIGH = "HIGH" # High confidence in the issue
CONTROL_FLOW = "CONTROL_FLOW" # Based on control flow analysis
INFERENCE = "INFERENCE" # Based on type inference
INFERENCE_FAILURE = "INFERENCE_FAILURE" # Failed type inference
UNDEFINED = "UNDEFINED" # No confidence level specifiedfrom pylint.checkers import BaseChecker
from pylint.interfaces import HIGH, INFERENCE
class CustomChecker(BaseChecker):
"""Example checker with custom messages."""
name = 'custom'
msgs = {
'W9001': (
'Function "%s" has too many parameters (%d > %d)',
'too-many-params-custom',
'Function should have fewer parameters for better maintainability',
),
'C9001': (
'Variable name "%s" should use snake_case',
'invalid-variable-name-custom',
'Variable names should follow snake_case convention',
),
'E9001': (
'Undefined variable "%s" used in function "%s"',
'undefined-variable-custom',
'Variable must be defined before use',
)
}
def visit_functiondef(self, node):
"""Check function parameters."""
max_params = 5
param_count = len(node.args.args)
if param_count > max_params:
self.add_message(
'too-many-params-custom',
node=node,
args=(node.name, param_count, max_params),
confidence=HIGH
)
def visit_name(self, node):
"""Check variable naming."""
import re
if not re.match(r'^[a-z_][a-z0-9_]*$', node.name):
self.add_message(
'invalid-variable-name-custom',
node=node,
args=(node.name,),
confidence=INFERENCE
)from pylint.lint import PyLinter
from pylint.reporters import CollectingReporter
# Collect messages for processing
collector = CollectingReporter()
linter = PyLinter()
linter.set_reporter(collector)
linter.check(['mymodule.py'])
messages = collector.finalize()
# Filter messages by category
errors = [msg for msg in messages if msg.category == 'error']
warnings = [msg for msg in messages if msg.category == 'warning']
# Filter by confidence level
high_confidence = [msg for msg in messages if msg.confidence == 'HIGH']
# Filter by message type
missing_docstrings = [msg for msg in messages
if msg.symbol == 'missing-docstring']
# Group messages by file
from collections import defaultdict
by_file = defaultdict(list)
for msg in messages:
by_file[msg.path].append(msg)
# Process each message
for msg in messages:
print(f"{msg.path}:{msg.line}:{msg.column} - {msg.msg_id}: {msg.msg}")
print(f" Category: {msg.category}")
print(f" Confidence: {msg.confidence}")
print(f" Symbol: {msg.symbol}")def format_message_custom(msg):
"""Custom message formatting function."""
severity_map = {
'error': '❌',
'warning': '⚠️ ',
'convention': '📋',
'refactor': '♻️ ',
'info': 'ℹ️ '
}
icon = severity_map.get(msg.category, '?')
return f"{icon} {msg.path}:{msg.line} - {msg.msg} ({msg.symbol})"
# Apply custom formatting
for msg in messages:
formatted = format_message_custom(msg)
print(formatted)# Configure message template
linter = PyLinter()
# Standard templates
linter.config.msg_template = '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
linter.config.msg_template = '{path}:{line}:{column}: {category}: {msg} ({symbol})'
# Custom template with all fields
template = (
'{path}:{line}:{column}: {msg_id}: {msg}\n'
' Category: {category} | Confidence: {confidence}\n'
' Object: {obj} | Symbol: {symbol}'
)
linter.config.msg_template = template# C0103: invalid-name
# "Name doesn't conform to naming convention"
# C0111: missing-docstring
# "Missing module/class/function docstring"
# C0301: line-too-long
# "Line too long (%d/%d characters)"
# C0326: bad-whitespace
# "Wrong hanging indentation"# R0201: no-self-use
# "Method could be a function"
# R0903: too-few-public-methods
# "Too few public methods (%d/%d)"
# R0913: too-many-arguments
# "Too many arguments (%d/%d)"
# R1705: no-else-return
# "Unnecessary else after return statement"# W0613: unused-argument
# "Unused argument '%s'"
# W0622: redefined-builtin
# "Redefining built-in '%s'"
# W0703: broad-except
# "Catching too general exception %s"
# W1203: logging-fstring-interpolation
# "Use lazy % formatting in logging functions"# E0601: used-before-assignment
# "Using variable '%s' before assignment"
# E1101: no-member
# "%s %r has no %r member"
# E1120: no-value-for-parameter
# "No value provided for parameter %s in function call"
# E1136: unsubscriptable-object
# "Value '%s' is unsubscriptable"# F0001: fatal
# "Error occurred preventing further processing: %s"
# F0010: error
# "Error while code parsing: %s"
# F0202: method-check-failed
# "Unable to check methods signature (%s / %s)"# Disable globally in configuration
linter.config.disable = ['missing-docstring', 'invalid-name']
# Disable via command line
# pylint --disable=missing-docstring,invalid-name mymodule.py
# Disable in code with comments
def my_function(): # pylint: disable=missing-docstring
pass
class MyClass: # pylint: disable=too-few-public-methods
pass
# Disable for entire file
# pylint: disable=missing-docstring
# Disable for code block
# pylint: disable-next=unused-variable
x = get_value()# Enable specific messages
linter.config.enable = ['unused-variable']
# Enable message categories
linter.config.enable = ['warning', 'error']
# Enable all messages then disable specific ones
linter.config.disable = 'all'
linter.config.enable = ['error', 'fatal']Install with Tessl CLI
npx tessl i tessl/pypi-pylint