CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-interrogate

Interrogate a codebase for docstring coverage.

Pending
Overview
Eval results
Files

ast-visitor.mddocs/

AST Visitor

AST (Abstract Syntax Tree) visitor functionality for traversing Python source code and collecting docstring coverage information. The visitor pattern is used to examine Python code structure and identify missing docstrings across modules, classes, functions, and methods.

Capabilities

Coverage Node Data Structure

Data structure representing coverage information for individual code elements in the AST.

class CovNode:
    """Coverage information for an AST node."""
    
    name: str                    # Name of the code element
    path: str                    # File path containing the element
    level: int                   # Nesting level (0 = module, 1 = class/function, etc.)
    lineno: int                  # Line number in source file
    covered: bool                # Whether element has docstring
    node_type: str               # Type of AST node ("module", "class", "function", "method")
    is_nested_func: bool         # Whether function is nested inside another function
    is_nested_cls: bool          # Whether class is nested inside another class
    parent: object               # Parent node (for nested elements)

AST Coverage Visitor

The main visitor class that traverses Python AST to collect docstring coverage information.

class CoverageVisitor:
    """AST visitor for collecting docstring coverage data."""
    
    def __init__(self, filename, config):
        """
        Initialize AST visitor.
        
        Args:
            filename: Path to file being analyzed
            config: InterrogateConfig with analysis options
        """
    
    def visit_Module(self, node):
        """Visit module-level node."""
    
    def visit_ClassDef(self, node):
        """Visit class definition node."""
    
    def visit_FunctionDef(self, node):
        """Visit function definition node."""
    
    def visit_AsyncFunctionDef(self, node):
        """Visit async function definition node."""

Node Analysis Helpers

Static and helper methods for analyzing AST nodes and determining coverage rules.

class CoverageVisitor:
    @staticmethod
    def _has_doc(node):
        """
        Check if an AST node has a docstring.
        
        Args:
            node: AST node to check
            
        Returns:
            bool: True if node has docstring
        """
    
    def _is_nested_func(self, parent, node_type):
        """
        Determine if function is nested inside another function.
        
        Args:
            parent: Parent AST node
            node_type: Type of current node
            
        Returns:
            bool: True if function is nested
        """
    
    def _is_nested_cls(self, parent, node_type):
        """
        Determine if class is nested inside another class.
        
        Args:
            parent: Parent AST node
            node_type: Type of current node
            
        Returns:
            bool: True if class is nested
        """
    
    def _is_private(self, node):
        """
        Check if node represents a private method/function.
        
        Args:
            node: AST node to check
            
        Returns:
            bool: True if node is private (starts with __)
        """
    
    def _is_semiprivate(self, node):
        """
        Check if node represents a semiprivate method/function.
        
        Args:
            node: AST node to check
            
        Returns:
            bool: True if node is semiprivate (starts with single _)
        """
    
    def _has_property_decorators(self, node):
        """
        Check if function has property decorators.
        
        Args:
            node: Function AST node
            
        Returns:
            bool: True if has @property, @setter, @getter, or @deleter
        """
    
    def _has_setters(self, node):
        """
        Check if function has property setter decorators.
        
        Args:
            node: Function AST node
            
        Returns:
            bool: True if has @property.setter decorator
        """
    
    def _has_overload_decorator(self, node):
        """
        Check if function has @typing.overload decorator.
        
        Args:
            node: Function AST node
            
        Returns:
            bool: True if has @overload decorator
        """

Usage Examples

Basic AST Analysis

import ast
from interrogate.visit import CoverageVisitor
from interrogate.config import InterrogateConfig

# Parse Python source code
source_code = '''
class MyClass:
    """Class docstring."""
    
    def method_with_doc(self):
        """Method with docstring."""
        pass
    
    def method_without_doc(self):
        pass
'''

# Create AST and visitor
tree = ast.parse(source_code)
config = InterrogateConfig()
visitor = CoverageVisitor("example.py", config)

# Visit all nodes
visitor.visit(tree)

# Access collected coverage nodes
for node in visitor.covered_nodes:
    print(f"{node.name} ({node.node_type}): {'COVERED' if node.covered else 'MISSING'}")

Custom Configuration Analysis

import ast
from interrogate.visit import CoverageVisitor  
from interrogate.config import InterrogateConfig

# Configure analysis options
config = InterrogateConfig(
    ignore_private=True,
    ignore_magic=True,
    ignore_init_method=True
)

source = '''
class Example:
    def __init__(self):
        pass
        
    def _private_method(self):
        pass
        
    def __magic_method__(self):
        pass
        
    def public_method(self):
        pass
'''

tree = ast.parse(source)
visitor = CoverageVisitor("example.py", config)
visitor.visit(tree)

# Only public_method should be analyzed due to ignore settings
for node in visitor.covered_nodes:
    if not node.covered:
        print(f"Missing docstring: {node.name}")

Nested Structure Analysis

import ast
from interrogate.visit import CoverageVisitor, CovNode
from interrogate.config import InterrogateConfig

source = '''
class OuterClass:
    """Outer class docstring."""
    
    class InnerClass:
        def inner_method(self):
            def nested_function():
                pass
            pass
    
    def outer_method(self):
        def local_function():
            pass
        pass
'''

tree = ast.parse(source)
config = InterrogateConfig()
visitor = CoverageVisitor("nested_example.py", config)
visitor.visit(tree)

# Analyze nested structure
for node in visitor.covered_nodes:
    indent = "  " * node.level
    nested_info = []
    if node.is_nested_cls:
        nested_info.append("nested class")
    if node.is_nested_func:
        nested_info.append("nested function")
    
    nested_str = f" ({', '.join(nested_info)})" if nested_info else ""
    print(f"{indent}{node.name} ({node.node_type}){nested_str}: line {node.lineno}")

Decorator Detection

import ast
from interrogate.visit import CoverageVisitor
from interrogate.config import InterrogateConfig

source = '''
from typing import overload

class Properties:
    @property
    def value(self):
        return self._value
    
    @value.setter  
    def value(self, val):
        self._value = val
    
    @overload
    def process(self, x: int) -> int: ...
    
    @overload
    def process(self, x: str) -> str: ...
    
    def process(self, x):
        return x
'''

tree = ast.parse(source)
visitor = CoverageVisitor("decorators.py", InterrogateConfig())
visitor.visit(tree)

# Check decorator detection
for node in visitor.covered_nodes:
    if node.node_type in ("function", "method"):
        # Access private methods to check decorator status
        ast_node = node._ast_node  # Hypothetical access to original AST node
        
        if visitor._has_property_decorators(ast_node):
            print(f"{node.name}: Has property decorators")
        if visitor._has_setters(ast_node):
            print(f"{node.name}: Has setter decorators")  
        if visitor._has_overload_decorator(ast_node):
            print(f"{node.name}: Has overload decorator")

Integration with Coverage Analysis

import ast
from interrogate.visit import CoverageVisitor
from interrogate.config import InterrogateConfig
from interrogate.coverage import InterrogateFileResult

def analyze_file_ast(filename, config):
    """Analyze a Python file's AST for docstring coverage."""
    
    with open(filename, 'r') as f:
        source = f.read()
    
    try:
        tree = ast.parse(source)
    except SyntaxError as e:
        print(f"Syntax error in {filename}: {e}")
        return None
    
    # Create visitor and analyze
    visitor = CoverageVisitor(filename, config)
    visitor.visit(tree)
    
    # Create file result
    file_result = InterrogateFileResult(
        filename=filename,
        nodes=visitor.covered_nodes
    )
    file_result.combine()  # Calculate totals
    
    return file_result

# Usage
config = InterrogateConfig(fail_under=80.0)
result = analyze_file_ast("my_module.py", config)

if result:
    print(f"Coverage: {result.perc_covered:.1f}%")
    print(f"Missing: {result.missing}/{result.total}")

Custom Node Filtering

from interrogate.visit import CoverageVisitor
from interrogate.config import InterrogateConfig

class CustomCoverageVisitor(CoverageVisitor):
    """Extended visitor with custom filtering logic."""
    
    def _is_ignored_common(self, node):
        """Override common ignore logic."""
        # Call parent implementation
        if super()._is_ignored_common(node):
            return True
            
        # Add custom logic - ignore test methods
        if hasattr(node, 'name') and node.name.startswith('test_'):
            return True
            
        # Ignore methods with specific decorators
        if hasattr(node, 'decorator_list'):
            decorator_names = [d.id for d in node.decorator_list if hasattr(d, 'id')]
            if 'skip_coverage' in decorator_names:
                return True
        
        return False

# Usage with custom visitor
source = '''
class TestClass:
    def test_something(self):
        """This will be ignored due to test_ prefix."""
        pass
    
    @skip_coverage
    def skip_this(self):
        """This will be ignored due to decorator."""
        pass
    
    def normal_method(self):
        """This will be analyzed normally."""
        pass
'''

import ast
tree = ast.parse(source)
config = InterrogateConfig()
visitor = CustomCoverageVisitor("test.py", config)
visitor.visit(tree)

# Only normal_method should be in results
for node in visitor.covered_nodes:
    print(f"Analyzed: {node.name}")

Install with Tessl CLI

npx tessl i tessl/pypi-interrogate

docs

ast-visitor.md

badge-generation.md

cli-interface.md

configuration.md

coverage-analysis.md

index.md

utilities.md

tile.json