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