143 lines
3.7 KiB
Markdown
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)
|
|
```
|