3.7 KiB
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)