CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-refurb

A tool for refurbishing and modernizing Python codebases

Pending
Overview
Eval results
Files

plugins.mddocs/

Plugin Development Framework

Complete framework for developing custom refurb checks with AST analysis utilities, type checking helpers, and visitor patterns.

Capabilities

Core Plugin Architecture

The visitor system that enables custom checks to be seamlessly integrated into refurb's analysis pipeline.

class RefurbVisitor:
    """
    Main visitor class that traverses AST nodes and runs applicable checks.
    
    The visitor implements the standard visitor pattern for Mypy AST nodes,
    dispatching to registered checks based on node type.
    
    Attributes:
    - checks: defaultdict[type[Node], list[Check]] - Checks organized by AST node type
    - settings: Settings - Configuration controlling analysis behavior
    - errors: list[Error] - Accumulated errors found during traversal
    """
    
    def __init__(self, checks: defaultdict[type[Node], list[Check]], settings: Settings):
        """
        Initialize visitor with checks and settings.
        
        Parameters:
        - checks: Dictionary mapping AST node types to lists of applicable checks
        - settings: Configuration object controlling analysis behavior
        """

    def run_check(self, node: Node, check: Check) -> None:
        """
        Execute a single check on an AST node.
        
        Handles both 2-parameter and 3-parameter check function signatures,
        manages error collection, and provides error context.
        
        Parameters:
        - node: AST node to analyze
        - check: Check function to execute
        """

    def visit_call_expr(self, o: CallExpr) -> None:
        """
        Visit function call expressions and run applicable checks.
        
        Example of node-specific visitor method. Similar methods exist for
        all supported AST node types.
        
        Parameters:
        - o: CallExpr AST node to analyze
        """

class TraverserVisitor:
    """
    Base AST traversal visitor providing infrastructure for walking AST trees.
    
    Provides the foundation for RefurbVisitor with automatic traversal
    and node dispatch capabilities.
    """

Check Loading System

Functions that discover, load, and organize checks from built-in modules and plugins.

def load_checks(settings: Settings) -> defaultdict[type[Node], list[Check]]:
    """
    Load and filter checks based on settings configuration.
    
    Discovers checks from:
    1. Built-in check modules in refurb.checks.*
    2. Plugin entry points (refurb.plugins group)
    3. Additional modules specified via --load
    
    Applies enable/disable filtering based on settings.
    
    Parameters:
    - settings: Configuration specifying which checks to load and filter rules
    
    Returns:
    Dictionary mapping AST node types to lists of applicable check functions
    """

def get_modules(paths: list[str]) -> Generator[ModuleType, None, None]:
    """
    Load Python modules from specified paths.
    
    Searches for check modules in:
    - Built-in refurb.checks package hierarchy
    - Plugin entry points
    - Custom paths specified via --load
    
    Parameters:
    - paths: List of module paths or directory paths to search
    
    Yields:
    Python module objects containing check definitions
    """

def get_error_class(module: ModuleType) -> type[Error] | None:
    """
    Extract Error class from a check module.
    
    Looks for classes inheriting from Error that define check metadata
    like error codes, categories, and default enabled status.
    
    Parameters:
    - module: Python module to examine
    
    Returns:
    Error class if found, None if module doesn't define checks
    """

def should_load_check(settings: Settings, error: type[Error]) -> bool:
    """
    Determine if a check should be enabled based on settings.
    
    Applies filtering logic considering:
    - Default enabled status of the check
    - Explicit enable/disable settings
    - enable_all/disable_all global flags
    - Category-based filtering rules
    
    Parameters:
    - settings: Configuration containing enable/disable rules
    - error: Error class representing the check
    
    Returns:
    True if check should be loaded and enabled, False otherwise
    """

Plugin Entry Points

Refurb discovers plugins through Python entry points:

# setup.py or pyproject.toml entry point definition
[project.entry-points."refurb.plugins"]
my_plugin = "my_plugin.checks"

# Plugin module structure
# my_plugin/checks.py
from refurb.error import Error

class MyCustomError(Error):
    enabled = True
    name = "Use better pattern"
    prefix = "PLUG"  # Custom prefix  
    categories = {"readability"}
    code = 1

def check(node: CallExpr, errors: list[Error]) -> None:
    """Check function that will be automatically discovered."""
    if should_flag_pattern(node):
        errors.append(MyCustomError(
            line=node.line,
            column=node.column,
            msg="Consider using better_function() instead"
        ))

Check Function Signatures

Refurb supports multiple check function signatures for flexibility:

# Type definitions for check functions
Check = Callable[[Node, list[Error]], None] | Callable[[Node, list[Error], Settings], None]

# Two-parameter signature (most common)
def check_pattern(node: Node, errors: list[Error]) -> None:
    """Simple check function."""

# Three-parameter signature (access to settings)
def check_with_settings(node: Node, errors: list[Error], settings: Settings) -> None:
    """Check function with access to configuration."""
    if settings.verbose:
        # More detailed analysis in verbose mode
        pass

Visitor Node Mappings

Comprehensive mapping of visitor methods to AST node types for precise targeting of checks.

METHOD_NODE_MAPPINGS: dict[str, type[Node]]
"""
Dictionary mapping visitor method names to AST node types.

Contains 89 different AST node type mappings, enabling checks to target
specific language constructs:

- visit_call_expr -> CallExpr (function calls)
- visit_name_expr -> NameExpr (variable references)  
- visit_member_expr -> MemberExpr (attribute access)
- visit_op_expr -> OpExpr (binary operations)
- visit_comparison_expr -> ComparisonExpr (comparisons)
- visit_if_stmt -> IfStmt (if statements)
- visit_for_stmt -> ForStmt (for loops)
- visit_while_stmt -> WhileStmt (while loops)
- visit_try_stmt -> TryStmt (try/except blocks)
- visit_with_stmt -> WithStmt (with statements)
- ... and many more
"""

Plugin Development Examples

# Example: Custom check for deprecated function usage
from mypy.nodes import CallExpr, MemberExpr, NameExpr
from refurb.error import Error

class DeprecatedFunctionError(Error):
    """Error for deprecated function usage."""
    enabled = True
    name = "Avoid deprecated function"
    prefix = "PLUG"
    categories = {"modernization"}
    code = 100

def check_deprecated_calls(node: CallExpr, errors: list[Error]) -> None:
    """Check for calls to deprecated functions."""
    if isinstance(node.callee, NameExpr):
        if node.callee.name in {"deprecated_func", "old_api"}:
            errors.append(DeprecatedFunctionError(
                line=node.line,
                column=node.column,
                msg=f"Replace `{node.callee.name}()` with modern alternative"
            ))

# Example: Check with settings access
def check_with_config(node: Node, errors: list[Error], settings: Settings) -> None:
    """Check that can access configuration settings."""
    if settings.python_version and settings.python_version >= (3, 10):
        # Only apply this check for Python 3.10+
        check_modern_syntax(node, errors)

# Example: Multi-node type check
def check_multiple_patterns(node: Node, errors: list[Error]) -> None:
    """Check that handles multiple AST node types."""
    if isinstance(node, (CallExpr, MemberExpr)):
        # Handle both function calls and attribute access
        analyze_pattern(node, errors)

Check Generation Utilities

Refurb provides utilities for generating boilerplate code for new checks, accessed via the gen subcommand.

def main() -> None:
    """
    Interactive generator for creating new check boilerplate.
    
    This function:
    1. Prompts user to select AST node types to handle
    2. Prompts for target file path and error prefix
    3. Generates complete check file with proper imports and structure
    4. Creates necessary __init__.py files in parent directories
    5. Automatically assigns next available error ID
    
    Accessed via: refurb gen
    """

def get_next_error_id(prefix: str) -> int:
    """
    Find the next available error ID for a given prefix.
    
    Parameters:
    - prefix: Error code prefix (e.g., "FURB", "PLUG")
    
    Returns:
    Next unused error ID number for the prefix
    """

def build_imports(names: list[str]) -> str:
    """
    Generate import statements for selected AST node types.
    
    Parameters:
    - names: List of AST node type names (e.g., ["CallExpr", "NameExpr"])
    
    Returns:
    Formatted import statements organized by module
    """

Plugin Testing

# Test infrastructure for plugin development
from refurb.main import run_refurb
from refurb.settings import Settings

def test_my_check():
    """Test custom check behavior."""
    settings = Settings(
        files=["test_file.py"],
        load=["my_plugin"]  # Load custom plugin
    )
    
    errors = run_refurb(settings)
    
    # Verify expected errors are found
    assert any(error.prefix == "PLUG" and error.code == 100 for error in errors)

Advanced Plugin Features

# Plugin with custom categories
class AdvancedError(Error):
    enabled = False  # Disabled by default
    name = "Advanced pattern check"
    prefix = "ADV"
    categories = {"performance", "security"}  # Multiple categories
    code = 1

# Plugin with path-specific behavior
def check_with_path_logic(node: Node, errors: list[Error], settings: Settings) -> None:
    """Check that behaves differently based on file path."""
    if node.get_line() and "test" in (node.get_line().source_file or ""):
        # Different behavior in test files
        return
    
    # Normal checking logic
    standard_analysis(node, errors)

# Plugin integration with amendment rules
# In pyproject.toml:
# [[tool.refurb.amend]]
# path = "legacy/"
# ignore = ["ADV001"]  # Ignore advanced checks in legacy code

Node Type Coverage

Refurb supports checks for all major Python language constructs:

  • Expressions: CallExpr, NameExpr, MemberExpr, OpExpr, ComparisonExpr, etc.
  • Statements: IfStmt, ForStmt, WhileStmt, TryStmt, WithStmt, etc.
  • Definitions: FuncDef, ClassDef, VarDef, etc.
  • Literals: IntExpr, StrExpr, FloatExpr, etc.
  • Complex constructs: ListExpr, DictExpr, SetExpr, TupleExpr, etc.

This comprehensive coverage enables plugins to analyze virtually any Python code pattern.

Install with Tessl CLI

npx tessl i tessl/pypi-refurb

docs

analysis.md

ast-utilities.md

cli.md

configuration.md

errors.md

index.md

plugins.md

tile.json