scss-variable-extractor 1.6.3 → 1.6.5
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/README.md +1015 -778
- package/THEME-GUIDE.md +289 -0
- package/bin/cli.js +807 -652
- package/jest.config.js +7 -12
- package/package.json +49 -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/ng-refactorer.js +654 -578
- package/src/parser.js +424 -421
- package/src/refactorer.js +329 -249
- package/src/scanner.js +63 -55
- package/src/style-organizer.js +500 -499
- 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/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 -0
- package/test/refactorer.test.js +277 -211
- package/test/scanner.test.js +34 -32
- package/test/style-organizer.test.js +106 -106
- package/test/theme-utils.test.js +140 -0
package/src/analyzer.js
CHANGED
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analyzes extracted values and identifies repeated ones
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Analyzes all extracted values from multiple files
|
|
7
|
-
* @param {Array<Object>} allExtracted - Array of extracted values from all files
|
|
8
|
-
* @param {Object} config - Configuration object
|
|
9
|
-
* @returns {Object} - Analysis results with repeated values
|
|
10
|
-
*/
|
|
11
|
-
function analyzeValues(allExtracted, config) {
|
|
12
|
-
const analysis = {
|
|
13
|
-
colors: {},
|
|
14
|
-
spacing: {},
|
|
15
|
-
fontSizes: {},
|
|
16
|
-
fontWeights: {},
|
|
17
|
-
fontFamilies: {},
|
|
18
|
-
borderRadius: {},
|
|
19
|
-
shadows: {},
|
|
20
|
-
zIndex: {},
|
|
21
|
-
sizing: {},
|
|
22
|
-
lineHeight: {},
|
|
23
|
-
opacity: {},
|
|
24
|
-
transitions: {}
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Count occurrences of each value
|
|
28
|
-
Object.keys(allExtracted).forEach(category => {
|
|
29
|
-
if (!config.categories[category]) return;
|
|
30
|
-
|
|
31
|
-
allExtracted[category].forEach(item => {
|
|
32
|
-
const normalizedValue = normalizeValue(item.value, category);
|
|
33
|
-
|
|
34
|
-
if (!analysis[category][normalizedValue]) {
|
|
35
|
-
analysis[category][normalizedValue] = {
|
|
36
|
-
value: item.value,
|
|
37
|
-
count: 0,
|
|
38
|
-
files: new Set(),
|
|
39
|
-
occurrences: []
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
analysis[category][normalizedValue].count++;
|
|
44
|
-
analysis[category][normalizedValue].files.add(item.file);
|
|
45
|
-
analysis[category][normalizedValue].occurrences.push({
|
|
46
|
-
file: item.file,
|
|
47
|
-
line: item.line,
|
|
48
|
-
context: item.context,
|
|
49
|
-
property: item.property
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Filter by threshold and generate variable names
|
|
55
|
-
const results = {};
|
|
56
|
-
|
|
57
|
-
Object.keys(analysis).forEach(category => {
|
|
58
|
-
results[category] = [];
|
|
59
|
-
|
|
60
|
-
Object.keys(analysis[category]).forEach(normalizedValue => {
|
|
61
|
-
const data = analysis[category][normalizedValue];
|
|
62
|
-
|
|
63
|
-
if (data.count >= config.threshold) {
|
|
64
|
-
results[category].push({
|
|
65
|
-
value: data.value,
|
|
66
|
-
normalizedValue,
|
|
67
|
-
count: data.count,
|
|
68
|
-
fileCount: data.files.size,
|
|
69
|
-
files: Array.from(data.files),
|
|
70
|
-
occurrences: data.occurrences,
|
|
71
|
-
suggestedName: generateVariableName(category, data.value, data.occurrences, config)
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Sort by count (descending)
|
|
77
|
-
results[category].sort((a, b) => b.count - a.count);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return results;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Normalizes value for comparison
|
|
85
|
-
*/
|
|
86
|
-
function normalizeValue(value, category) {
|
|
87
|
-
if (category === 'colors') {
|
|
88
|
-
return normalizeColor(value);
|
|
89
|
-
}
|
|
90
|
-
return value.toLowerCase().trim();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Normalizes color values for comparison
|
|
95
|
-
*/
|
|
96
|
-
function normalizeColor(color) {
|
|
97
|
-
color = color.toLowerCase().trim();
|
|
98
|
-
|
|
99
|
-
// Convert 3-digit hex to 6-digit
|
|
100
|
-
if (/^#[0-9a-f]{3}$/i.test(color)) {
|
|
101
|
-
color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Normalize rgb/rgba spacing
|
|
105
|
-
color = color.replace(/\s+/g, '');
|
|
106
|
-
|
|
107
|
-
return color;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Generates a variable name based on category and value
|
|
112
|
-
*/
|
|
113
|
-
function generateVariableName(category, value, occurrences, config) {
|
|
114
|
-
const prefix = getCategoryPrefix(category);
|
|
115
|
-
|
|
116
|
-
if (category === 'spacing') {
|
|
117
|
-
return generateSpacingVariableName(value, config);
|
|
118
|
-
} else if (category === 'colors') {
|
|
119
|
-
return generateColorVariableName(value, occurrences);
|
|
120
|
-
} else if (category === 'fontSizes') {
|
|
121
|
-
return generateFontSizeVariableName(value);
|
|
122
|
-
} else if (category === 'fontWeights') {
|
|
123
|
-
return generateFontWeightVariableName(value);
|
|
124
|
-
} else if (category === 'fontFamilies') {
|
|
125
|
-
return prefix + sanitizeName(value);
|
|
126
|
-
} else if (category === 'borderRadius') {
|
|
127
|
-
return generateBorderRadiusVariableName(value);
|
|
128
|
-
} else if (category === 'shadows') {
|
|
129
|
-
return prefix + 'default';
|
|
130
|
-
} else if (category === 'zIndex') {
|
|
131
|
-
return prefix + value;
|
|
132
|
-
} else if (category === 'sizing') {
|
|
133
|
-
return prefix + value.replace('px', '');
|
|
134
|
-
} else if (category === 'lineHeight') {
|
|
135
|
-
return prefix + sanitizeName(value);
|
|
136
|
-
} else if (category === 'opacity') {
|
|
137
|
-
return prefix + sanitizeName(value);
|
|
138
|
-
} else if (category === 'transitions') {
|
|
139
|
-
return prefix + 'default';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return prefix + 'value';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Gets prefix for a category
|
|
147
|
-
*/
|
|
148
|
-
function getCategoryPrefix(category) {
|
|
149
|
-
const prefixes = {
|
|
150
|
-
colors: '$color-',
|
|
151
|
-
spacing: '$spacing-',
|
|
152
|
-
fontSizes: '$font-size-',
|
|
153
|
-
fontWeights: '$font-weight-',
|
|
154
|
-
fontFamilies: '$font-family-',
|
|
155
|
-
borderRadius: '$border-radius-',
|
|
156
|
-
shadows: '$shadow-',
|
|
157
|
-
zIndex: '$z-index-',
|
|
158
|
-
sizing: '$size-',
|
|
159
|
-
lineHeight: '$line-height-',
|
|
160
|
-
opacity: '$opacity-',
|
|
161
|
-
transitions: '$transition-'
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
return prefixes[category] || '$';
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Generates spacing variable name using t-shirt sizing
|
|
169
|
-
*/
|
|
170
|
-
function generateSpacingVariableName(value, config) {
|
|
171
|
-
const spacingScale = config.spacingScale;
|
|
172
|
-
|
|
173
|
-
// Try to match to spacing scale
|
|
174
|
-
for (const [size, scaleValue] of Object.entries(spacingScale)) {
|
|
175
|
-
if (scaleValue === value) {
|
|
176
|
-
return `$spacing-${size}`;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Fallback to pixel value
|
|
181
|
-
return `$spacing-${value.replace('px', '')}`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Generates color variable name
|
|
186
|
-
*/
|
|
187
|
-
function generateColorVariableName(value, occurrences) {
|
|
188
|
-
// Try to detect common color patterns
|
|
189
|
-
const normalizedColor = normalizeColor(value);
|
|
190
|
-
|
|
191
|
-
// Check for common Material colors
|
|
192
|
-
if (normalizedColor === '#1976d2' || normalizedColor === 'rgb(25,118,210)') {
|
|
193
|
-
return '$color-brand-primary';
|
|
194
|
-
}
|
|
195
|
-
if (normalizedColor === 'rgba(0,0,0,0.87)') {
|
|
196
|
-
return '$color-text-primary';
|
|
197
|
-
}
|
|
198
|
-
if (normalizedColor === 'rgba(0,0,0,0.54)') {
|
|
199
|
-
return '$color-text-secondary';
|
|
200
|
-
}
|
|
201
|
-
if (normalizedColor === '#ffffff' || normalizedColor === 'white') {
|
|
202
|
-
return '$color-white';
|
|
203
|
-
}
|
|
204
|
-
if (normalizedColor === '#000000' || normalizedColor === 'black') {
|
|
205
|
-
return '$color-black';
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Try to derive from context
|
|
209
|
-
if (occurrences && occurrences.length > 0) {
|
|
210
|
-
const context = occurrences[0].context || '';
|
|
211
|
-
if (context.includes('background')) return '$color-background';
|
|
212
|
-
if (context.includes('border')) return '$color-border';
|
|
213
|
-
if (context.includes('text') || context.includes('color:')) return '$color-text';
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Fallback to hex value
|
|
217
|
-
return '$color-' + sanitizeName(value);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Generates font size variable name
|
|
222
|
-
*/
|
|
223
|
-
function generateFontSizeVariableName(value) {
|
|
224
|
-
const sizeMap = {
|
|
225
|
-
'12px': 'xs',
|
|
226
|
-
'14px': 'sm',
|
|
227
|
-
'16px': 'md',
|
|
228
|
-
'18px': 'lg',
|
|
229
|
-
'20px': 'xl',
|
|
230
|
-
'24px': 'xxl'
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
return `$font-size-${sizeMap[value] || value.replace('px', '')}`;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Generates font weight variable name
|
|
238
|
-
*/
|
|
239
|
-
function generateFontWeightVariableName(value) {
|
|
240
|
-
const weightMap = {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
return `$font-weight-${weightMap[value] || value}`;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Generates border radius variable name
|
|
255
|
-
*/
|
|
256
|
-
function generateBorderRadiusVariableName(value) {
|
|
257
|
-
if (value === '50%') return '$border-radius-circle';
|
|
258
|
-
|
|
259
|
-
const radiusMap = {
|
|
260
|
-
'2px': 'xs',
|
|
261
|
-
'4px': 'sm',
|
|
262
|
-
'8px': 'md',
|
|
263
|
-
'12px': 'lg',
|
|
264
|
-
'16px': 'xl'
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
return `$border-radius-${radiusMap[value] || value.replace(/px|%/, '')}`;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Sanitizes a value to be a valid variable name
|
|
272
|
-
*/
|
|
273
|
-
function sanitizeName(value) {
|
|
274
|
-
return value
|
|
275
|
-
.replace(/[^a-zA-Z0-9-]/g, '-')
|
|
276
|
-
.replace(/-+/g, '-')
|
|
277
|
-
.replace(/^-|-$/g, '')
|
|
278
|
-
.toLowerCase();
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
module.exports = {
|
|
282
|
-
analyzeValues,
|
|
283
|
-
normalizeValue,
|
|
284
|
-
generateVariableName
|
|
285
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Analyzes extracted values and identifies repeated ones
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Analyzes all extracted values from multiple files
|
|
7
|
+
* @param {Array<Object>} allExtracted - Array of extracted values from all files
|
|
8
|
+
* @param {Object} config - Configuration object
|
|
9
|
+
* @returns {Object} - Analysis results with repeated values
|
|
10
|
+
*/
|
|
11
|
+
function analyzeValues(allExtracted, config) {
|
|
12
|
+
const analysis = {
|
|
13
|
+
colors: {},
|
|
14
|
+
spacing: {},
|
|
15
|
+
fontSizes: {},
|
|
16
|
+
fontWeights: {},
|
|
17
|
+
fontFamilies: {},
|
|
18
|
+
borderRadius: {},
|
|
19
|
+
shadows: {},
|
|
20
|
+
zIndex: {},
|
|
21
|
+
sizing: {},
|
|
22
|
+
lineHeight: {},
|
|
23
|
+
opacity: {},
|
|
24
|
+
transitions: {},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Count occurrences of each value
|
|
28
|
+
Object.keys(allExtracted).forEach(category => {
|
|
29
|
+
if (!config.categories[category]) return;
|
|
30
|
+
|
|
31
|
+
allExtracted[category].forEach(item => {
|
|
32
|
+
const normalizedValue = normalizeValue(item.value, category);
|
|
33
|
+
|
|
34
|
+
if (!analysis[category][normalizedValue]) {
|
|
35
|
+
analysis[category][normalizedValue] = {
|
|
36
|
+
value: item.value,
|
|
37
|
+
count: 0,
|
|
38
|
+
files: new Set(),
|
|
39
|
+
occurrences: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
analysis[category][normalizedValue].count++;
|
|
44
|
+
analysis[category][normalizedValue].files.add(item.file);
|
|
45
|
+
analysis[category][normalizedValue].occurrences.push({
|
|
46
|
+
file: item.file,
|
|
47
|
+
line: item.line,
|
|
48
|
+
context: item.context,
|
|
49
|
+
property: item.property,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Filter by threshold and generate variable names
|
|
55
|
+
const results = {};
|
|
56
|
+
|
|
57
|
+
Object.keys(analysis).forEach(category => {
|
|
58
|
+
results[category] = [];
|
|
59
|
+
|
|
60
|
+
Object.keys(analysis[category]).forEach(normalizedValue => {
|
|
61
|
+
const data = analysis[category][normalizedValue];
|
|
62
|
+
|
|
63
|
+
if (data.count >= config.threshold) {
|
|
64
|
+
results[category].push({
|
|
65
|
+
value: data.value,
|
|
66
|
+
normalizedValue,
|
|
67
|
+
count: data.count,
|
|
68
|
+
fileCount: data.files.size,
|
|
69
|
+
files: Array.from(data.files),
|
|
70
|
+
occurrences: data.occurrences,
|
|
71
|
+
suggestedName: generateVariableName(category, data.value, data.occurrences, config),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Sort by count (descending)
|
|
77
|
+
results[category].sort((a, b) => b.count - a.count);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Normalizes value for comparison
|
|
85
|
+
*/
|
|
86
|
+
function normalizeValue(value, category) {
|
|
87
|
+
if (category === 'colors') {
|
|
88
|
+
return normalizeColor(value);
|
|
89
|
+
}
|
|
90
|
+
return value.toLowerCase().trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Normalizes color values for comparison
|
|
95
|
+
*/
|
|
96
|
+
function normalizeColor(color) {
|
|
97
|
+
color = color.toLowerCase().trim();
|
|
98
|
+
|
|
99
|
+
// Convert 3-digit hex to 6-digit
|
|
100
|
+
if (/^#[0-9a-f]{3}$/i.test(color)) {
|
|
101
|
+
color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Normalize rgb/rgba spacing
|
|
105
|
+
color = color.replace(/\s+/g, '');
|
|
106
|
+
|
|
107
|
+
return color;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generates a variable name based on category and value
|
|
112
|
+
*/
|
|
113
|
+
function generateVariableName(category, value, occurrences, config) {
|
|
114
|
+
const prefix = getCategoryPrefix(category);
|
|
115
|
+
|
|
116
|
+
if (category === 'spacing') {
|
|
117
|
+
return generateSpacingVariableName(value, config);
|
|
118
|
+
} else if (category === 'colors') {
|
|
119
|
+
return generateColorVariableName(value, occurrences);
|
|
120
|
+
} else if (category === 'fontSizes') {
|
|
121
|
+
return generateFontSizeVariableName(value);
|
|
122
|
+
} else if (category === 'fontWeights') {
|
|
123
|
+
return generateFontWeightVariableName(value);
|
|
124
|
+
} else if (category === 'fontFamilies') {
|
|
125
|
+
return prefix + sanitizeName(value);
|
|
126
|
+
} else if (category === 'borderRadius') {
|
|
127
|
+
return generateBorderRadiusVariableName(value);
|
|
128
|
+
} else if (category === 'shadows') {
|
|
129
|
+
return prefix + 'default';
|
|
130
|
+
} else if (category === 'zIndex') {
|
|
131
|
+
return prefix + value;
|
|
132
|
+
} else if (category === 'sizing') {
|
|
133
|
+
return prefix + value.replace('px', '');
|
|
134
|
+
} else if (category === 'lineHeight') {
|
|
135
|
+
return prefix + sanitizeName(value);
|
|
136
|
+
} else if (category === 'opacity') {
|
|
137
|
+
return prefix + sanitizeName(value);
|
|
138
|
+
} else if (category === 'transitions') {
|
|
139
|
+
return prefix + 'default';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return prefix + 'value';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gets prefix for a category
|
|
147
|
+
*/
|
|
148
|
+
function getCategoryPrefix(category) {
|
|
149
|
+
const prefixes = {
|
|
150
|
+
colors: '$color-',
|
|
151
|
+
spacing: '$spacing-',
|
|
152
|
+
fontSizes: '$font-size-',
|
|
153
|
+
fontWeights: '$font-weight-',
|
|
154
|
+
fontFamilies: '$font-family-',
|
|
155
|
+
borderRadius: '$border-radius-',
|
|
156
|
+
shadows: '$shadow-',
|
|
157
|
+
zIndex: '$z-index-',
|
|
158
|
+
sizing: '$size-',
|
|
159
|
+
lineHeight: '$line-height-',
|
|
160
|
+
opacity: '$opacity-',
|
|
161
|
+
transitions: '$transition-',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return prefixes[category] || '$';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generates spacing variable name using t-shirt sizing
|
|
169
|
+
*/
|
|
170
|
+
function generateSpacingVariableName(value, config) {
|
|
171
|
+
const spacingScale = config.spacingScale;
|
|
172
|
+
|
|
173
|
+
// Try to match to spacing scale
|
|
174
|
+
for (const [size, scaleValue] of Object.entries(spacingScale)) {
|
|
175
|
+
if (scaleValue === value) {
|
|
176
|
+
return `$spacing-${size}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Fallback to pixel value
|
|
181
|
+
return `$spacing-${value.replace('px', '')}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Generates color variable name
|
|
186
|
+
*/
|
|
187
|
+
function generateColorVariableName(value, occurrences) {
|
|
188
|
+
// Try to detect common color patterns
|
|
189
|
+
const normalizedColor = normalizeColor(value);
|
|
190
|
+
|
|
191
|
+
// Check for common Material colors
|
|
192
|
+
if (normalizedColor === '#1976d2' || normalizedColor === 'rgb(25,118,210)') {
|
|
193
|
+
return '$color-brand-primary';
|
|
194
|
+
}
|
|
195
|
+
if (normalizedColor === 'rgba(0,0,0,0.87)') {
|
|
196
|
+
return '$color-text-primary';
|
|
197
|
+
}
|
|
198
|
+
if (normalizedColor === 'rgba(0,0,0,0.54)') {
|
|
199
|
+
return '$color-text-secondary';
|
|
200
|
+
}
|
|
201
|
+
if (normalizedColor === '#ffffff' || normalizedColor === 'white') {
|
|
202
|
+
return '$color-white';
|
|
203
|
+
}
|
|
204
|
+
if (normalizedColor === '#000000' || normalizedColor === 'black') {
|
|
205
|
+
return '$color-black';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Try to derive from context
|
|
209
|
+
if (occurrences && occurrences.length > 0) {
|
|
210
|
+
const context = occurrences[0].context || '';
|
|
211
|
+
if (context.includes('background')) return '$color-background';
|
|
212
|
+
if (context.includes('border')) return '$color-border';
|
|
213
|
+
if (context.includes('text') || context.includes('color:')) return '$color-text';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Fallback to hex value
|
|
217
|
+
return '$color-' + sanitizeName(value);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Generates font size variable name
|
|
222
|
+
*/
|
|
223
|
+
function generateFontSizeVariableName(value) {
|
|
224
|
+
const sizeMap = {
|
|
225
|
+
'12px': 'xs',
|
|
226
|
+
'14px': 'sm',
|
|
227
|
+
'16px': 'md',
|
|
228
|
+
'18px': 'lg',
|
|
229
|
+
'20px': 'xl',
|
|
230
|
+
'24px': 'xxl',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return `$font-size-${sizeMap[value] || value.replace('px', '')}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Generates font weight variable name
|
|
238
|
+
*/
|
|
239
|
+
function generateFontWeightVariableName(value) {
|
|
240
|
+
const weightMap = {
|
|
241
|
+
300: 'light',
|
|
242
|
+
400: 'normal',
|
|
243
|
+
500: 'medium',
|
|
244
|
+
600: 'semibold',
|
|
245
|
+
700: 'bold',
|
|
246
|
+
bold: 'bold',
|
|
247
|
+
normal: 'normal',
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return `$font-weight-${weightMap[value] || value}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generates border radius variable name
|
|
255
|
+
*/
|
|
256
|
+
function generateBorderRadiusVariableName(value) {
|
|
257
|
+
if (value === '50%') return '$border-radius-circle';
|
|
258
|
+
|
|
259
|
+
const radiusMap = {
|
|
260
|
+
'2px': 'xs',
|
|
261
|
+
'4px': 'sm',
|
|
262
|
+
'8px': 'md',
|
|
263
|
+
'12px': 'lg',
|
|
264
|
+
'16px': 'xl',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
return `$border-radius-${radiusMap[value] || value.replace(/px|%/, '')}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Sanitizes a value to be a valid variable name
|
|
272
|
+
*/
|
|
273
|
+
function sanitizeName(value) {
|
|
274
|
+
return value
|
|
275
|
+
.replace(/[^a-zA-Z0-9-]/g, '-')
|
|
276
|
+
.replace(/-+/g, '-')
|
|
277
|
+
.replace(/^-|-$/g, '')
|
|
278
|
+
.toLowerCase();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = {
|
|
282
|
+
analyzeValues,
|
|
283
|
+
normalizeValue,
|
|
284
|
+
generateVariableName,
|
|
285
|
+
};
|