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

143 lines
3.7 KiB
Markdown

# 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:
```py
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:
```py
# 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:
```py
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:
```py
# 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`:
```py
# 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:
```python
# constructs/full.py
@dataclass
class FileAnalysisResult:
# Existing fields...
custom_analysis: MyAnalysisResult = field(default_factory=MyAnalysisResult)
```