cqas/doc/md/developer-guide.md
Arija A. 49b592f8a7
Documentation hotfix
Signed-off-by: Arija A. <ari@ari.lt>
2025-09-21 00:44:43 +03:00

3.7 KiB

Developer Guide

CQaS follows a modular, extensible architecture based on the Abstract Syntax Tree visitor pattern, which allows for easy extensibility and separation of concerns.

Design Patterns

1. Visitor Pattern

All analysers inherit from BaseAnalyser and use the AST visitor pattern:

class BaseAnalyser(ast.NodeVisitor, ABC):
    """Base class for AST analysers"""

    def __init__(self, file_path: str, content: str):
        self.file_path = file_path
        self.content = content
        self.lines = content.splitlines()
        self.tree = None
        try:
            self.tree = ast.parse(content, filename=file_path)
        except SyntaxError as e:
            err_msg: str = (
                f"SyntaxError in file '{e.filename}', line {e.lineno}, offset {e.offset}:\n"
                f"    {e.text.strip() if e.text else ''}\n"
                f"    {' ' * ((e.offset or 1) - 1)}^\n"
                f"{e.msg}"
            )
            print(err_msg, file=sys.stderr)
            sys.exit(1)

    ...

2. Checker Pattern

A checker is a simple class that works on given inputs to analyse given code, often including a check() method.

Extension Points

1. Adding New Metrics

To add new metrics to existing analysers, extend the relevant data classes:

# In constructs/complexity.py
@dataclass
class ComplexityMetrics:
    # Existing fields...
    new_metric: float = 0.0  # Add your new metric

    def calculate_new_metric(self) -> float:
        # Implementation for new metric
        pass

2. Custom Analysis Engines

Create custom analysis by extending the base analyser:

class ComplexityAnalyser(BaseAnalyser):
    def analyse_with_custom_logic(self, file_path: str) -> CustomResult:
        # Custom analysis implementation
        pass

Adding New Analysers

1. Create the Data Structure

First, define the data structures in the constructs package:

# constructs/my_analyser.py
import typing as t

from dataclasses import dataclass

__all__: t.Tuple[str, ...] = ("MyAnalysisResult",)

@dataclass
class MyAnalysisResult:
    """Custom analysis result"""
    metric_value: float
    issues_found: t.List[str]
    recommendations: t.List[str]

2. Add Your Construct Module To __init__.py

Simply add from . import my_analyser in constructs/__init__.py and extend __all__.

3. Implement the Analyser

Create the analyser class inheriting from BaseAnalyser:

# analysers/my_analyser.py
import ast
from typing import List
from .base import BaseAnalyser
from csqa.constructs.my_analyzer import MyAnalysisResult

class MyAnalyser(BaseAnalyser):
    """Custom analyzer implementation"""

    def __init__(self, filepath: str, content: str):
        super().__init__(filepath, content)
        self.custom_data = []

    def analyse(self) -> MyAnalysisResult:
        """Perform custom analysis"""
        if self.tree:
            self.visit(self.tree)
        return self._calculate_results()

    def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
        """Visit function definitions"""
        # Custom analysis logic
        self.custom_data.append(node.name)
        self.generic_visit(node)

    def _calculate_results(self) -> MyAnalysisResult:
        """Calculate final results"""
        return MyAnalysisResult(
            metric_value=len(self.custom_data),
            issues_found=self._find_issues(),
            recommendations=self._generate_recommendations()
        )

4. Update Main Data Structures

Extend the main result classes to include your data:

# constructs/full.py
@dataclass
class FileAnalysisResult:
    # Existing fields...
    custom_analysis: MyAnalysisResult = field(default_factory=MyAnalysisResult)