cqas/doc/md/developer-guide.md
Arija A. dd206f6caa
Documentation + beta v1.0.0
Signed-off-by: Arija A. <ari@ari.lt>
2025-09-20 23:48:24 +03:00

5.3 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.

Core Components

cqas/
├── analysers/
│   ├── base.py              # Abstract base class
│   ├── complexity.py        # Complexity analysis
│   ├── security.py          # Security vulnerability detection
│   ├── dead_code.py         # Dead code identification
│   ├── duplication.py       # Code duplication analysis
│   ├── readability.py       # Code readability assessment
│   ├── halstead.py          # Halstead metrics calculation
│   ├── imports.py           # Import dependency analysis
│   └── pep8.py              # PEP8 style checking
├── constructs/
│   ├── classification.py    # Common enums and types
│   ├── complexity.py        # Complexity data structures
│   ├── security.py          # Security issue definitions
│   ├── dead_code.py         # Dead code information
│   ├── duplication.py       # Duplication results
│   ├── readability.py       # Readability metrics
│   ├── halstead.py          # Halstead metrics
│   ├── imports.py           # Import analysis structures
│   ├── pep8.py              # Style issue definitions
│   └── full.py              # Complete analysis results
├── engine/
│   ├── core.py              # Main analysis orchestration
│   ├── reporter.py          # Output formatting and reporting
│   └── config.py            # Configuration management
└── cli/
    └── main.py              # Command-line interface

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)