import type { StyleEntry } from '../types/index.js';
// CSS property categories
const COLOR_PROPERTIES = [
'color', 'background', 'background-color', 'border-color',
'outline-color', 'box-shadow', 'text-shadow', 'fill', 'stroke',
'caret-color', 'accent-color', 'text-decoration-color'
];
const SPACING_PROPERTIES = [
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
'margin-inline', 'margin-block', 'margin-inline-start', 'margin-inline-end',
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
'padding-inline', 'padding-block', 'padding-inline-start', 'padding-inline-end',
'gap', 'row-gap', 'column-gap', 'grid-gap',
'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
'top', 'right', 'bottom', 'left', 'inset'
];
const TYPOGRAPHY_PROPERTIES = [
'font', 'font-family', 'font-size', 'font-weight', 'font-style',
'line-height', 'letter-spacing', 'text-align', 'text-transform',
'text-decoration', 'text-indent', 'white-space', 'word-spacing',
'word-break', 'overflow-wrap', 'hyphens'
];
function categorizeProperty(property: string): 'colors' | 'spacing' | 'typography' | 'other' {
const normalizedProperty = property.toLowerCase().trim();
if (COLOR_PROPERTIES.includes(normalizedProperty)) {
return 'colors';
}
if (SPACING_PROPERTIES.includes(normalizedProperty)) {
return 'spacing';
}
if (TYPOGRAPHY_PROPERTIES.includes(normalizedProperty)) {
return 'typography';
}
// Check for color values in the property name
if (normalizedProperty.includes('color')) {
return 'colors';
}
return 'other';
}
interface CSSDeclaration {
property: string;
value: string;
}
function parseCSS(cssString: string): CSSDeclaration[] {
const declarations: CSSDeclaration[] = [];
// Remove comments
const noComments = cssString.replace(/\/\*[\s\S]*?\*\//g, '');
// Split by semicolons, handling nested values
const lines = noComments.split(';').filter(line => line.trim());
for (const line of lines) {
const colonIndex = line.indexOf(':');
if (colonIndex === -1) continue;
const property = line.substring(0, colonIndex).trim();
const value = line.substring(colonIndex + 1).trim();
if (property && value) {
declarations.push({ property, value });
}
}
return declarations;
}
export function extractStyledComponentStyles(code: string): StyleEntry[] {
const styles: StyleEntry[] = [];
// Match styled.xxx`...` or styled(Component)`...`
const styledRegex = /styled(?:\.(\w+)|\((\w+)\))`([^`]*)`/g;
let match;
while ((match = styledRegex.exec(code)) !== null) {
const cssContent = match[3];
const declarations = parseCSS(cssContent);
for (const decl of declarations) {
styles.push({
property: decl.property,
value: decl.value,
source: 'styled-components',
raw: `${decl.property}: ${decl.value}`
});
}
}
// Match css`...` helper
const cssHelperRegex = /css`([^`]*)`/g;
while ((match = cssHelperRegex.exec(code)) !== null) {
const cssContent = match[1];
const declarations = parseCSS(cssContent);
for (const decl of declarations) {
styles.push({
property: decl.property,
value: decl.value,
source: 'styled-components',
raw: `${decl.property}: ${decl.value}`
});
}
}
return styles;
}
export function categorizeStyledStyles(code: string): {
colors: StyleEntry[];
spacing: StyleEntry[];
typography: StyleEntry[];
other: StyleEntry[];
} {
const styles = extractStyledComponentStyles(code);
const result = {
colors: [] as StyleEntry[],
spacing: [] as StyleEntry[],
typography: [] as StyleEntry[],
other: [] as StyleEntry[]
};
for (const style of styles) {
const category = categorizeProperty(style.property);
result[category].push(style);
}
return result;
}
export function detectStyledComponents(code: string): boolean {
// Check for styled-components import
const hasImport = /import\s+(?:styled|{[^}]*styled[^}]*})\s+from\s+['"]styled-components['"]/.test(code);
// Check for styled usage
const hasUsage = /styled(?:\.(\w+)|\((\w+)\))`/.test(code) || /css`/.test(code);
return hasImport || hasUsage;
}
export function extractThemeUsage(code: string): string[] {
const themeUsages: string[] = [];
// Match ${props => props.theme.xxx} or ${({ theme }) => theme.xxx}
const themeRegex = /\$\{[^}]*theme\.([a-zA-Z0-9.]+)[^}]*\}/g;
let match;
while ((match = themeRegex.exec(code)) !== null) {
themeUsages.push(match[1]);
}
return [...new Set(themeUsages)];
}