scss-variable-extractor 1.6.4 → 1.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +7 -0
- package/.prettierrc.json +18 -0
- package/.scssextractrc.example.json +31 -35
- package/MULTI-APP-GUIDE.md +262 -0
- package/README.md +1283 -778
- package/THEME-GUIDE.md +289 -0
- package/bin/cli.js +1002 -652
- package/jest.config.js +7 -12
- package/multi-app.example.json +44 -0
- package/package.json +50 -45
- package/src/analyzer.js +285 -285
- package/src/angular-parser.js +383 -381
- package/src/bootstrap-migrator.js +694 -661
- package/src/config.js +87 -91
- package/src/generator.js +220 -219
- package/src/index.js +37 -29
- package/src/multi-app-analyzer.js +612 -0
- package/src/ng-refactorer.js +654 -578
- package/src/parser.js +424 -421
- package/src/refactorer.js +329 -322
- package/src/scanner.js +63 -55
- package/src/style-organizer.js +500 -504
- package/src/theme-utils.js +432 -0
- package/test/analyzer.test.js +107 -107
- package/test/angular-parser.test.js +230 -230
- package/test/bootstrap-migrator.test.js +230 -213
- package/test/generator.test.js +139 -149
- package/test/multi-app-analyzer.test.js +216 -0
- package/test/ng-refactorer-global.test.js +140 -0
- package/test/ng-refactorer.test.js +191 -184
- package/test/parser.test.js +131 -131
- package/test/refactorer-edge-cases.test.js +385 -353
- package/test/refactorer.test.js +277 -257
- package/test/scanner.test.js +34 -32
- package/test/style-organizer.test.js +106 -106
- package/test/theme-utils.test.js +140 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates SCSS theme structure for Angular Material applications
|
|
6
|
+
* Supports dark and light themes with proper variable organization
|
|
7
|
+
* @param {Object} options - Configuration options
|
|
8
|
+
* @returns {Object} - Theme files content and recommendations
|
|
9
|
+
*/
|
|
10
|
+
function generateThemeStructure(options = {}) {
|
|
11
|
+
const {
|
|
12
|
+
outputDir = './src/styles',
|
|
13
|
+
includeComponents = true,
|
|
14
|
+
themePalettes = {
|
|
15
|
+
primary: { light: '#1976d2', dark: '#90caf9' },
|
|
16
|
+
accent: { light: '#ff4081', dark: '#ff4081' },
|
|
17
|
+
warn: { light: '#f44336', dark: '#f44336' },
|
|
18
|
+
},
|
|
19
|
+
} = options;
|
|
20
|
+
|
|
21
|
+
const themeFiles = {
|
|
22
|
+
base: generateBaseTheme(),
|
|
23
|
+
lightTheme: generateLightTheme(themePalettes),
|
|
24
|
+
darkTheme: generateDarkTheme(themePalettes),
|
|
25
|
+
themeLoader: generateThemeLoader(),
|
|
26
|
+
componentThemes: includeComponents ? generateComponentThemeTemplate() : null,
|
|
27
|
+
cssVariables: generateCSSVariables(themePalettes),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
files: themeFiles,
|
|
32
|
+
recommendations: getThemeRecommendations(),
|
|
33
|
+
structure: getRecommendedStructure(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generates base theme file with shared variables
|
|
39
|
+
*/
|
|
40
|
+
function generateBaseTheme() {
|
|
41
|
+
return `// Base theme configuration
|
|
42
|
+
// Shared variables used across all themes
|
|
43
|
+
|
|
44
|
+
// Spacing scale (t-shirt sizing)
|
|
45
|
+
$spacing-xs: 4px;
|
|
46
|
+
$spacing-sm: 8px;
|
|
47
|
+
$spacing-md: 16px;
|
|
48
|
+
$spacing-lg: 24px;
|
|
49
|
+
$spacing-xl: 32px;
|
|
50
|
+
$spacing-xxl: 48px;
|
|
51
|
+
|
|
52
|
+
// Typography scale
|
|
53
|
+
$font-size-xs: 12px;
|
|
54
|
+
$font-size-sm: 14px;
|
|
55
|
+
$font-size-md: 16px;
|
|
56
|
+
$font-size-lg: 18px;
|
|
57
|
+
$font-size-xl: 24px;
|
|
58
|
+
$font-size-xxl: 32px;
|
|
59
|
+
|
|
60
|
+
// Font weights
|
|
61
|
+
$font-weight-light: 300;
|
|
62
|
+
$font-weight-regular: 400;
|
|
63
|
+
$font-weight-medium: 500;
|
|
64
|
+
$font-weight-semibold: 600;
|
|
65
|
+
$font-weight-bold: 700;
|
|
66
|
+
|
|
67
|
+
// Border radius
|
|
68
|
+
$border-radius-sm: 4px;
|
|
69
|
+
$border-radius-md: 8px;
|
|
70
|
+
$border-radius-lg: 16px;
|
|
71
|
+
$border-radius-round: 50%;
|
|
72
|
+
|
|
73
|
+
// Z-index layers
|
|
74
|
+
$z-index-dropdown: 1000;
|
|
75
|
+
$z-index-sticky: 1020;
|
|
76
|
+
$z-index-fixed: 1030;
|
|
77
|
+
$z-index-modal-backdrop: 1040;
|
|
78
|
+
$z-index-modal: 1050;
|
|
79
|
+
$z-index-popover: 1060;
|
|
80
|
+
$z-index-tooltip: 1070;
|
|
81
|
+
|
|
82
|
+
// Transitions
|
|
83
|
+
$transition-fast: 150ms ease-in-out;
|
|
84
|
+
$transition-normal: 300ms ease-in-out;
|
|
85
|
+
$transition-slow: 500ms ease-in-out;
|
|
86
|
+
|
|
87
|
+
// Box shadows
|
|
88
|
+
$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
89
|
+
$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
90
|
+
$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
91
|
+
$shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generates light theme variables
|
|
97
|
+
*/
|
|
98
|
+
function generateLightTheme(palettes) {
|
|
99
|
+
return `// Light theme colors
|
|
100
|
+
// These colors are optimized for light backgrounds
|
|
101
|
+
|
|
102
|
+
// Primary colors
|
|
103
|
+
$primary-light: ${palettes.primary.light};
|
|
104
|
+
$primary-light-contrast: #ffffff;
|
|
105
|
+
|
|
106
|
+
// Accent colors
|
|
107
|
+
$accent-light: ${palettes.accent.light};
|
|
108
|
+
$accent-light-contrast: #ffffff;
|
|
109
|
+
|
|
110
|
+
// Warn colors
|
|
111
|
+
$warn-light: ${palettes.warn.light};
|
|
112
|
+
$warn-light-contrast: #ffffff;
|
|
113
|
+
|
|
114
|
+
// Background colors
|
|
115
|
+
$background-light: #fafafa;
|
|
116
|
+
$surface-light: #ffffff;
|
|
117
|
+
$card-light: #ffffff;
|
|
118
|
+
|
|
119
|
+
// Text colors
|
|
120
|
+
$text-primary-light: rgba(0, 0, 0, 0.87);
|
|
121
|
+
$text-secondary-light: rgba(0, 0, 0, 0.60);
|
|
122
|
+
$text-disabled-light: rgba(0, 0, 0, 0.38);
|
|
123
|
+
$text-hint-light: rgba(0, 0, 0, 0.38);
|
|
124
|
+
|
|
125
|
+
// Divider colors
|
|
126
|
+
$divider-light: rgba(0, 0, 0, 0.12);
|
|
127
|
+
$border-light: rgba(0, 0, 0, 0.12);
|
|
128
|
+
|
|
129
|
+
// State colors
|
|
130
|
+
$hover-light: rgba(0, 0, 0, 0.04);
|
|
131
|
+
$selected-light: rgba(0, 0, 0, 0.08);
|
|
132
|
+
$focused-light: rgba(0, 0, 0, 0.12);
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generates dark theme variables
|
|
138
|
+
*/
|
|
139
|
+
function generateDarkTheme(palettes) {
|
|
140
|
+
return `// Dark theme colors
|
|
141
|
+
// These colors are optimized for dark backgrounds
|
|
142
|
+
|
|
143
|
+
// Primary colors
|
|
144
|
+
$primary-dark: ${palettes.primary.dark};
|
|
145
|
+
$primary-dark-contrast: rgba(0, 0, 0, 0.87);
|
|
146
|
+
|
|
147
|
+
// Accent colors
|
|
148
|
+
$accent-dark: ${palettes.accent.dark};
|
|
149
|
+
$accent-dark-contrast: rgba(0, 0, 0, 0.87);
|
|
150
|
+
|
|
151
|
+
// Warn colors
|
|
152
|
+
$warn-dark: ${palettes.warn.dark};
|
|
153
|
+
$warn-dark-contrast: #ffffff;
|
|
154
|
+
|
|
155
|
+
// Background colors
|
|
156
|
+
$background-dark: #303030;
|
|
157
|
+
$surface-dark: #424242;
|
|
158
|
+
$card-dark: #424242;
|
|
159
|
+
|
|
160
|
+
// Text colors
|
|
161
|
+
$text-primary-dark: rgba(255, 255, 255, 0.87);
|
|
162
|
+
$text-secondary-dark: rgba(255, 255, 255, 0.70);
|
|
163
|
+
$text-disabled-dark: rgba(255, 255, 255, 0.50);
|
|
164
|
+
$text-hint-dark: rgba(255, 255, 255, 0.50);
|
|
165
|
+
|
|
166
|
+
// Divider colors
|
|
167
|
+
$divider-dark: rgba(255, 255, 255, 0.12);
|
|
168
|
+
$border-dark: rgba(255, 255, 255, 0.12);
|
|
169
|
+
|
|
170
|
+
// State colors
|
|
171
|
+
$hover-dark: rgba(255, 255, 255, 0.04);
|
|
172
|
+
$selected-dark: rgba(255, 255, 255, 0.08);
|
|
173
|
+
$focused-dark: rgba(255, 255, 255, 0.12);
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generates theme loader that switches between light and dark
|
|
179
|
+
*/
|
|
180
|
+
function generateThemeLoader() {
|
|
181
|
+
return `@use '@angular/material' as mat;
|
|
182
|
+
@use './theme-base' as base;
|
|
183
|
+
@use './theme-light' as light;
|
|
184
|
+
@use './theme-dark' as dark;
|
|
185
|
+
|
|
186
|
+
// Include the common styles for Angular Material
|
|
187
|
+
@include mat.core();
|
|
188
|
+
|
|
189
|
+
// Define the light theme
|
|
190
|
+
$light-theme: mat.define-light-theme((
|
|
191
|
+
color: (
|
|
192
|
+
primary: mat.define-palette(mat.$indigo-palette),
|
|
193
|
+
accent: mat.define-palette(mat.$pink-palette),
|
|
194
|
+
warn: mat.define-palette(mat.$red-palette),
|
|
195
|
+
),
|
|
196
|
+
typography: mat.define-typography-config(),
|
|
197
|
+
density: 0,
|
|
198
|
+
));
|
|
199
|
+
|
|
200
|
+
// Define the dark theme
|
|
201
|
+
$dark-theme: mat.define-dark-theme((
|
|
202
|
+
color: (
|
|
203
|
+
primary: mat.define-palette(mat.$blue-palette),
|
|
204
|
+
accent: mat.define-palette(mat.$pink-palette),
|
|
205
|
+
warn: mat.define-palette(mat.$red-palette),
|
|
206
|
+
),
|
|
207
|
+
typography: mat.define-typography-config(),
|
|
208
|
+
density: 0,
|
|
209
|
+
));
|
|
210
|
+
|
|
211
|
+
// Apply the light theme by default
|
|
212
|
+
@include mat.all-component-themes($light-theme);
|
|
213
|
+
|
|
214
|
+
// Apply the dark theme when the 'dark-theme' class is present
|
|
215
|
+
.dark-theme {
|
|
216
|
+
@include mat.all-component-colors($dark-theme);
|
|
217
|
+
|
|
218
|
+
// Custom dark theme variables
|
|
219
|
+
--background-color: #{dark.$background-dark};
|
|
220
|
+
--surface-color: #{dark.$surface-dark};
|
|
221
|
+
--text-primary: #{dark.$text-primary-dark};
|
|
222
|
+
--text-secondary: #{dark.$text-secondary-dark};
|
|
223
|
+
--divider-color: #{dark.$divider-dark};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Light theme custom variables (default)
|
|
227
|
+
:root {
|
|
228
|
+
--background-color: #{light.$background-light};
|
|
229
|
+
--surface-color: #{light.$surface-light};
|
|
230
|
+
--text-primary: #{light.$text-primary-light};
|
|
231
|
+
--text-secondary: #{light.$text-secondary-light};
|
|
232
|
+
--divider-color: #{light.$divider-light};
|
|
233
|
+
}
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Generates component theme template
|
|
239
|
+
*/
|
|
240
|
+
function generateComponentThemeTemplate() {
|
|
241
|
+
return `// Component-specific theme mixin
|
|
242
|
+
// Use this pattern for components that need theme-aware styles
|
|
243
|
+
|
|
244
|
+
@mixin component-theme($theme) {
|
|
245
|
+
$color-config: mat.get-color-config($theme);
|
|
246
|
+
|
|
247
|
+
@if $color-config != null {
|
|
248
|
+
$primary: map.get($color-config, 'primary');
|
|
249
|
+
$accent: map.get($color-config, 'accent');
|
|
250
|
+
$warn: map.get($color-config, 'warn');
|
|
251
|
+
$foreground: map.get($color-config, 'foreground');
|
|
252
|
+
$background: map.get($color-config, 'background');
|
|
253
|
+
|
|
254
|
+
.my-component {
|
|
255
|
+
background-color: mat.get-color-from-palette($background, 'card');
|
|
256
|
+
color: mat.get-color-from-palette($foreground, 'text');
|
|
257
|
+
|
|
258
|
+
&__header {
|
|
259
|
+
background-color: mat.get-color-from-palette($primary);
|
|
260
|
+
color: mat.get-color-from-palette($primary, 'default-contrast');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
&__accent {
|
|
264
|
+
color: mat.get-color-from-palette($accent);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Include this mixin in your theme files:
|
|
271
|
+
// @include component-theme($light-theme);
|
|
272
|
+
// .dark-theme { @include component-theme($dark-theme); }
|
|
273
|
+
`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Generates CSS custom properties for runtime theme switching
|
|
278
|
+
*/
|
|
279
|
+
function generateCSSVariables(palettes) {
|
|
280
|
+
return `// CSS Custom Properties for runtime theme switching
|
|
281
|
+
// These can be changed dynamically with JavaScript
|
|
282
|
+
|
|
283
|
+
:root {
|
|
284
|
+
// Colors
|
|
285
|
+
--color-primary: ${palettes.primary.light};
|
|
286
|
+
--color-accent: ${palettes.accent.light};
|
|
287
|
+
--color-warn: ${palettes.warn.light};
|
|
288
|
+
|
|
289
|
+
// Usage in components:
|
|
290
|
+
// color: var(--color-primary);
|
|
291
|
+
// background-color: var(--color-accent);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.dark-theme {
|
|
295
|
+
--color-primary: ${palettes.primary.dark};
|
|
296
|
+
--color-accent: ${palettes.accent.dark};
|
|
297
|
+
--color-warn: ${palettes.warn.dark};
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Provides recommendations for theme organization
|
|
304
|
+
*/
|
|
305
|
+
function getThemeRecommendations() {
|
|
306
|
+
return [
|
|
307
|
+
{
|
|
308
|
+
category: 'Structure',
|
|
309
|
+
recommendation:
|
|
310
|
+
'Organize themes in src/styles/ directory with separate files for base, light, and dark themes',
|
|
311
|
+
priority: 'high',
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
category: 'Variables',
|
|
315
|
+
recommendation:
|
|
316
|
+
'Use SCSS variables for build-time values and CSS custom properties for runtime theme switching',
|
|
317
|
+
priority: 'high',
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
category: 'Component Styles',
|
|
321
|
+
recommendation:
|
|
322
|
+
'Create theme mixins for components that need theme-aware styles instead of using ::ng-deep',
|
|
323
|
+
priority: 'high',
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
category: 'Global Styles',
|
|
327
|
+
recommendation:
|
|
328
|
+
'Move cross-cutting styles that affect multiple components to global theme files',
|
|
329
|
+
priority: 'medium',
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
category: 'Encapsulation',
|
|
333
|
+
recommendation:
|
|
334
|
+
'Avoid ViewEncapsulation.None unless absolutely necessary; use theme mixins instead',
|
|
335
|
+
priority: 'medium',
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
category: 'Performance',
|
|
339
|
+
recommendation:
|
|
340
|
+
'Use CSS custom properties sparingly; SCSS variables are optimized at build time',
|
|
341
|
+
priority: 'low',
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Returns recommended directory structure
|
|
348
|
+
*/
|
|
349
|
+
function getRecommendedStructure() {
|
|
350
|
+
return {
|
|
351
|
+
'src/styles/': {
|
|
352
|
+
'_theme-base.scss': 'Shared variables (spacing, typography, shadows, etc.)',
|
|
353
|
+
'_theme-light.scss': 'Light theme color variables',
|
|
354
|
+
'_theme-dark.scss': 'Dark theme color variables',
|
|
355
|
+
'themes.scss': 'Theme loader and Material theme configuration',
|
|
356
|
+
'_variables.scss': 'Extracted hardcoded values (generated)',
|
|
357
|
+
'global-overrides.scss': 'Global styles extracted from ::ng-deep usage',
|
|
358
|
+
'utilities.scss': 'Utility classes for common patterns',
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Analyzes existing styles and suggests theme organization
|
|
365
|
+
*/
|
|
366
|
+
function analyzeThemeReadiness(scssFiles) {
|
|
367
|
+
const analysis = {
|
|
368
|
+
hardcodedColors: [],
|
|
369
|
+
themeableComponents: [],
|
|
370
|
+
colorUsage: new Map(),
|
|
371
|
+
recommendations: [],
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
scssFiles.forEach(filePath => {
|
|
375
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
376
|
+
|
|
377
|
+
// Find hardcoded colors
|
|
378
|
+
const colorMatches = content.matchAll(/#[0-9a-fA-F]{3,6}\b|rgba?\([^)]+\)/g);
|
|
379
|
+
for (const match of colorMatches) {
|
|
380
|
+
const color = match[0];
|
|
381
|
+
const count = analysis.colorUsage.get(color) || 0;
|
|
382
|
+
analysis.colorUsage.set(color, count + 1);
|
|
383
|
+
|
|
384
|
+
analysis.hardcodedColors.push({
|
|
385
|
+
file: filePath,
|
|
386
|
+
color,
|
|
387
|
+
line: getLineNumber(content, match.index),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Find components that might benefit from theme mixins
|
|
392
|
+
if (content.includes('::ng-deep') || content.includes('!important')) {
|
|
393
|
+
analysis.themeableComponents.push(filePath);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Generate recommendations
|
|
398
|
+
analysis.colorUsage.forEach((count, color) => {
|
|
399
|
+
if (count > 3) {
|
|
400
|
+
analysis.recommendations.push({
|
|
401
|
+
type: 'extract-color',
|
|
402
|
+
message: `Color ${color} used ${count} times - consider extracting to theme variable`,
|
|
403
|
+
priority: 'high',
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (analysis.themeableComponents.length > 0) {
|
|
409
|
+
analysis.recommendations.push({
|
|
410
|
+
type: 'create-theme-mixins',
|
|
411
|
+
message: `${analysis.themeableComponents.length} component(s) could benefit from theme mixins`,
|
|
412
|
+
priority: 'medium',
|
|
413
|
+
files: analysis.themeableComponents,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return analysis;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Helper function to get line number from content and index
|
|
422
|
+
*/
|
|
423
|
+
function getLineNumber(content, index) {
|
|
424
|
+
return content.substring(0, index).split('\n').length;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
module.exports = {
|
|
428
|
+
generateThemeStructure,
|
|
429
|
+
analyzeThemeReadiness,
|
|
430
|
+
getThemeRecommendations,
|
|
431
|
+
getRecommendedStructure,
|
|
432
|
+
};
|
package/test/analyzer.test.js
CHANGED
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
const { analyzeValues, generateVariableName } = require('../src/analyzer');
|
|
2
|
-
const { DEFAULT_CONFIG } = require('../src/config');
|
|
3
|
-
|
|
4
|
-
describe('Analyzer', () => {
|
|
5
|
-
test('should count repeated values', () => {
|
|
6
|
-
const extracted = {
|
|
7
|
-
colors: [
|
|
8
|
-
{ value: '#1976d2', file: 'a.scss', line: 1 },
|
|
9
|
-
{ value: '#1976d2', file: 'b.scss', line: 1 },
|
|
10
|
-
{ value: '#fff', file: 'a.scss', line: 2 }
|
|
11
|
-
],
|
|
12
|
-
spacing: [],
|
|
13
|
-
fontSizes: [],
|
|
14
|
-
fontWeights: [],
|
|
15
|
-
fontFamilies: [],
|
|
16
|
-
borderRadius: [],
|
|
17
|
-
shadows: [],
|
|
18
|
-
zIndex: [],
|
|
19
|
-
sizing: [],
|
|
20
|
-
lineHeight: [],
|
|
21
|
-
opacity: [],
|
|
22
|
-
transitions: []
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const analysis = analyzeValues(extracted, DEFAULT_CONFIG);
|
|
26
|
-
|
|
27
|
-
expect(analysis.colors.length).toBeGreaterThan(0);
|
|
28
|
-
const primaryColor = analysis.colors.find(c => c.value === '#1976d2');
|
|
29
|
-
expect(primaryColor.count).toBe(2);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('should filter by threshold', () => {
|
|
33
|
-
const extracted = {
|
|
34
|
-
colors: [
|
|
35
|
-
{ value: '#1976d2', file: 'a.scss', line: 1 },
|
|
36
|
-
{ value: '#fff', file: 'a.scss', line: 2 }
|
|
37
|
-
],
|
|
38
|
-
spacing: [],
|
|
39
|
-
fontSizes: [],
|
|
40
|
-
fontWeights: [],
|
|
41
|
-
fontFamilies: [],
|
|
42
|
-
borderRadius: [],
|
|
43
|
-
shadows: [],
|
|
44
|
-
zIndex: [],
|
|
45
|
-
sizing: [],
|
|
46
|
-
lineHeight: [],
|
|
47
|
-
opacity: [],
|
|
48
|
-
transitions: []
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const config = { ...DEFAULT_CONFIG, threshold: 2 };
|
|
52
|
-
const analysis = analyzeValues(extracted, config);
|
|
53
|
-
|
|
54
|
-
// No colors should pass threshold of 2
|
|
55
|
-
expect(analysis.colors.length).toBe(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('should generate spacing variable names with t-shirt sizing', () => {
|
|
59
|
-
const name = generateVariableName('spacing', '16px', [], DEFAULT_CONFIG);
|
|
60
|
-
|
|
61
|
-
expect(name).toBe('$spacing-md');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('should generate color variable names', () => {
|
|
65
|
-
const name = generateVariableName('colors', '#1976d2', []);
|
|
66
|
-
|
|
67
|
-
expect(name).toContain('$color-');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('should generate font-size variable names', () => {
|
|
71
|
-
const name = generateVariableName('fontSizes', '14px', []);
|
|
72
|
-
|
|
73
|
-
expect(name).toBe('$font-size-sm');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('should generate font-weight variable names', () => {
|
|
77
|
-
const name = generateVariableName('fontWeights', '500', []);
|
|
78
|
-
|
|
79
|
-
expect(name).toBe('$font-weight-medium');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('should count unique files', () => {
|
|
83
|
-
const extracted = {
|
|
84
|
-
colors: [
|
|
85
|
-
{ value: '#1976d2', file: 'a.scss', line: 1 },
|
|
86
|
-
{ value: '#1976d2', file: 'a.scss', line: 5 },
|
|
87
|
-
{ value: '#1976d2', file: 'b.scss', line: 1 }
|
|
88
|
-
],
|
|
89
|
-
spacing: [],
|
|
90
|
-
fontSizes: [],
|
|
91
|
-
fontWeights: [],
|
|
92
|
-
fontFamilies: [],
|
|
93
|
-
borderRadius: [],
|
|
94
|
-
shadows: [],
|
|
95
|
-
zIndex: [],
|
|
96
|
-
sizing: [],
|
|
97
|
-
lineHeight: [],
|
|
98
|
-
opacity: [],
|
|
99
|
-
transitions: []
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const analysis = analyzeValues(extracted, DEFAULT_CONFIG);
|
|
103
|
-
|
|
104
|
-
const primaryColor = analysis.colors.find(c => c.value === '#1976d2');
|
|
105
|
-
expect(primaryColor.fileCount).toBe(2);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
1
|
+
const { analyzeValues, generateVariableName } = require('../src/analyzer');
|
|
2
|
+
const { DEFAULT_CONFIG } = require('../src/config');
|
|
3
|
+
|
|
4
|
+
describe('Analyzer', () => {
|
|
5
|
+
test('should count repeated values', () => {
|
|
6
|
+
const extracted = {
|
|
7
|
+
colors: [
|
|
8
|
+
{ value: '#1976d2', file: 'a.scss', line: 1 },
|
|
9
|
+
{ value: '#1976d2', file: 'b.scss', line: 1 },
|
|
10
|
+
{ value: '#fff', file: 'a.scss', line: 2 },
|
|
11
|
+
],
|
|
12
|
+
spacing: [],
|
|
13
|
+
fontSizes: [],
|
|
14
|
+
fontWeights: [],
|
|
15
|
+
fontFamilies: [],
|
|
16
|
+
borderRadius: [],
|
|
17
|
+
shadows: [],
|
|
18
|
+
zIndex: [],
|
|
19
|
+
sizing: [],
|
|
20
|
+
lineHeight: [],
|
|
21
|
+
opacity: [],
|
|
22
|
+
transitions: [],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const analysis = analyzeValues(extracted, DEFAULT_CONFIG);
|
|
26
|
+
|
|
27
|
+
expect(analysis.colors.length).toBeGreaterThan(0);
|
|
28
|
+
const primaryColor = analysis.colors.find(c => c.value === '#1976d2');
|
|
29
|
+
expect(primaryColor.count).toBe(2);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should filter by threshold', () => {
|
|
33
|
+
const extracted = {
|
|
34
|
+
colors: [
|
|
35
|
+
{ value: '#1976d2', file: 'a.scss', line: 1 },
|
|
36
|
+
{ value: '#fff', file: 'a.scss', line: 2 },
|
|
37
|
+
],
|
|
38
|
+
spacing: [],
|
|
39
|
+
fontSizes: [],
|
|
40
|
+
fontWeights: [],
|
|
41
|
+
fontFamilies: [],
|
|
42
|
+
borderRadius: [],
|
|
43
|
+
shadows: [],
|
|
44
|
+
zIndex: [],
|
|
45
|
+
sizing: [],
|
|
46
|
+
lineHeight: [],
|
|
47
|
+
opacity: [],
|
|
48
|
+
transitions: [],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const config = { ...DEFAULT_CONFIG, threshold: 2 };
|
|
52
|
+
const analysis = analyzeValues(extracted, config);
|
|
53
|
+
|
|
54
|
+
// No colors should pass threshold of 2
|
|
55
|
+
expect(analysis.colors.length).toBe(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should generate spacing variable names with t-shirt sizing', () => {
|
|
59
|
+
const name = generateVariableName('spacing', '16px', [], DEFAULT_CONFIG);
|
|
60
|
+
|
|
61
|
+
expect(name).toBe('$spacing-md');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should generate color variable names', () => {
|
|
65
|
+
const name = generateVariableName('colors', '#1976d2', []);
|
|
66
|
+
|
|
67
|
+
expect(name).toContain('$color-');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should generate font-size variable names', () => {
|
|
71
|
+
const name = generateVariableName('fontSizes', '14px', []);
|
|
72
|
+
|
|
73
|
+
expect(name).toBe('$font-size-sm');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should generate font-weight variable names', () => {
|
|
77
|
+
const name = generateVariableName('fontWeights', '500', []);
|
|
78
|
+
|
|
79
|
+
expect(name).toBe('$font-weight-medium');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should count unique files', () => {
|
|
83
|
+
const extracted = {
|
|
84
|
+
colors: [
|
|
85
|
+
{ value: '#1976d2', file: 'a.scss', line: 1 },
|
|
86
|
+
{ value: '#1976d2', file: 'a.scss', line: 5 },
|
|
87
|
+
{ value: '#1976d2', file: 'b.scss', line: 1 },
|
|
88
|
+
],
|
|
89
|
+
spacing: [],
|
|
90
|
+
fontSizes: [],
|
|
91
|
+
fontWeights: [],
|
|
92
|
+
fontFamilies: [],
|
|
93
|
+
borderRadius: [],
|
|
94
|
+
shadows: [],
|
|
95
|
+
zIndex: [],
|
|
96
|
+
sizing: [],
|
|
97
|
+
lineHeight: [],
|
|
98
|
+
opacity: [],
|
|
99
|
+
transitions: [],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const analysis = analyzeValues(extracted, DEFAULT_CONFIG);
|
|
103
|
+
|
|
104
|
+
const primaryColor = analysis.colors.find(c => c.value === '#1976d2');
|
|
105
|
+
expect(primaryColor.fileCount).toBe(2);
|
|
106
|
+
});
|
|
107
|
+
});
|