""" Base classes and data structures for code analysis. """ from dataclasses import dataclass, field from datetime import datetime from enum import Enum from typing import List, Dict, Any, Optional, Set from pathlib import Path class QuickWinType(str, Enum): """Types of quick-win improvements.""" INLINE_STYLE = "inline_style" # Inline styles that can be extracted DUPLICATE_VALUE = "duplicate_value" # Duplicate color/spacing values UNUSED_STYLE = "unused_style" # Unused CSS/SCSS HARDCODED_VALUE = "hardcoded_value" # Hardcoded values that should be tokens NAMING_INCONSISTENCY = "naming" # Inconsistent naming patterns DEPRECATED_PATTERN = "deprecated" # Deprecated styling patterns ACCESSIBILITY = "accessibility" # A11y improvements PERFORMANCE = "performance" # Performance improvements class QuickWinPriority(str, Enum): """Priority levels for quick-wins.""" CRITICAL = "critical" # Must fix - breaking issues HIGH = "high" # Should fix - significant improvement MEDIUM = "medium" # Nice to fix - moderate improvement LOW = "low" # Optional - minor improvement class StylingApproach(str, Enum): """Detected styling approaches in a project.""" CSS_MODULES = "css-modules" STYLED_COMPONENTS = "styled-components" EMOTION = "emotion" TAILWIND = "tailwind" INLINE_STYLES = "inline-styles" CSS_IN_JS = "css-in-js" SASS_SCSS = "sass-scss" LESS = "less" VANILLA_CSS = "vanilla-css" CSS_VARIABLES = "css-variables" class Framework(str, Enum): """Detected UI frameworks.""" REACT = "react" NEXT = "next" VUE = "vue" NUXT = "nuxt" ANGULAR = "angular" SVELTE = "svelte" SOLID = "solid" UNKNOWN = "unknown" @dataclass class Location: """Represents a location in source code.""" file_path: str line: int column: int = 0 end_line: Optional[int] = None end_column: Optional[int] = None def __str__(self) -> str: return f"{self.file_path}:{self.line}" def to_dict(self) -> Dict[str, Any]: return { "file": self.file_path, "line": self.line, "column": self.column, "end_line": self.end_line, "end_column": self.end_column, } @dataclass class StyleFile: """Represents a style file in the project.""" path: str type: str # css, scss, less, styled, etc. size_bytes: int = 0 line_count: int = 0 variable_count: int = 0 selector_count: int = 0 imports: List[str] = field(default_factory=list) imported_by: List[str] = field(default_factory=list) def to_dict(self) -> Dict[str, Any]: return { "path": self.path, "type": self.type, "size_bytes": self.size_bytes, "line_count": self.line_count, "variable_count": self.variable_count, "selector_count": self.selector_count, "imports": self.imports, "imported_by": self.imported_by, } @dataclass class ComponentInfo: """Information about a React component.""" name: str path: str type: str = "functional" # functional, class, forwardRef, memo props: List[str] = field(default_factory=list) has_styles: bool = False style_files: List[str] = field(default_factory=list) inline_style_count: int = 0 imports: List[str] = field(default_factory=list) exports: List[str] = field(default_factory=list) children: List[str] = field(default_factory=list) # Child components used line_count: int = 0 def to_dict(self) -> Dict[str, Any]: return { "name": self.name, "path": self.path, "type": self.type, "props": self.props, "has_styles": self.has_styles, "style_files": self.style_files, "inline_style_count": self.inline_style_count, "imports": self.imports, "exports": self.exports, "children": self.children, "line_count": self.line_count, } @dataclass class StylePattern: """A detected style pattern in code.""" type: StylingApproach locations: List[Location] = field(default_factory=list) count: int = 0 examples: List[str] = field(default_factory=list) def to_dict(self) -> Dict[str, Any]: return { "type": self.type.value, "count": self.count, "locations": [loc.to_dict() for loc in self.locations[:10]], "examples": self.examples[:5], } @dataclass class TokenCandidate: """A value that could be extracted as a design token.""" value: str # The actual value (e.g., "#3B82F6") suggested_name: str # Suggested token name category: str # colors, spacing, typography, etc. occurrences: int = 1 # How many times it appears locations: List[Location] = field(default_factory=list) confidence: float = 0.0 # 0-1 confidence score def to_dict(self) -> Dict[str, Any]: return { "value": self.value, "suggested_name": self.suggested_name, "category": self.category, "occurrences": self.occurrences, "locations": [loc.to_dict() for loc in self.locations[:5]], "confidence": self.confidence, } @dataclass class QuickWin: """A quick improvement opportunity.""" type: QuickWinType priority: QuickWinPriority title: str description: str location: Optional[Location] = None affected_files: List[str] = field(default_factory=list) estimated_impact: str = "" # e.g., "Remove 50 lines of duplicate code" fix_suggestion: str = "" # Suggested fix auto_fixable: bool = False # Can be auto-fixed def to_dict(self) -> Dict[str, Any]: return { "type": self.type.value, "priority": self.priority.value, "title": self.title, "description": self.description, "location": self.location.to_dict() if self.location else None, "affected_files": self.affected_files, "estimated_impact": self.estimated_impact, "fix_suggestion": self.fix_suggestion, "auto_fixable": self.auto_fixable, } @dataclass class ProjectAnalysis: """Complete analysis result for a project.""" # Basic info project_path: str analyzed_at: datetime = field(default_factory=datetime.now) # Framework detection framework: Framework = Framework.UNKNOWN framework_version: str = "" # Styling detection styling_approaches: List[StylePattern] = field(default_factory=list) primary_styling: Optional[StylingApproach] = None # Components components: List[ComponentInfo] = field(default_factory=list) component_count: int = 0 # Style files style_files: List[StyleFile] = field(default_factory=list) style_file_count: int = 0 # Issues and opportunities inline_style_locations: List[Location] = field(default_factory=list) token_candidates: List[TokenCandidate] = field(default_factory=list) quick_wins: List[QuickWin] = field(default_factory=list) # Dependency graph dependency_graph: Dict[str, List[str]] = field(default_factory=dict) # Statistics stats: Dict[str, Any] = field(default_factory=dict) def __post_init__(self): if not self.stats: self.stats = { "total_files_scanned": 0, "total_lines": 0, "component_count": 0, "style_file_count": 0, "inline_style_count": 0, "token_candidates": 0, "quick_wins_count": 0, } def to_dict(self) -> Dict[str, Any]: return { "project_path": self.project_path, "analyzed_at": self.analyzed_at.isoformat(), "framework": self.framework.value, "framework_version": self.framework_version, "styling_approaches": [sp.to_dict() for sp in self.styling_approaches], "primary_styling": self.primary_styling.value if self.primary_styling else None, "component_count": self.component_count, "style_file_count": self.style_file_count, "inline_style_count": len(self.inline_style_locations), "token_candidates_count": len(self.token_candidates), "quick_wins_count": len(self.quick_wins), "stats": self.stats, } def summary(self) -> str: """Generate human-readable summary.""" lines = [ f"Project Analysis: {self.project_path}", "=" * 50, f"Framework: {self.framework.value} {self.framework_version}", f"Components: {self.component_count}", f"Style files: {self.style_file_count}", "", "Styling Approaches:", ] for sp in self.styling_approaches: lines.append(f" • {sp.type.value}: {sp.count} occurrences") lines.extend([ "", f"Inline styles found: {len(self.inline_style_locations)}", f"Token candidates: {len(self.token_candidates)}", f"Quick wins: {len(self.quick_wins)}", "", "Quick Wins by Priority:", ]) by_priority = {} for qw in self.quick_wins: if qw.priority not in by_priority: by_priority[qw.priority] = [] by_priority[qw.priority].append(qw) for priority in [QuickWinPriority.CRITICAL, QuickWinPriority.HIGH, QuickWinPriority.MEDIUM, QuickWinPriority.LOW]: if priority in by_priority: lines.append(f" [{priority.value.upper()}] {len(by_priority[priority])} items") return "\n".join(lines)