Files
dss/demo/tools/analyze/base.py
Digital Production Factory 276ed71f31 Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm

Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)

Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability

Migration completed: $(date)
🤖 Clean migration with full functionality preserved
2025-12-09 18:45:48 -03:00

299 lines
9.9 KiB
Python

"""
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)