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/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
- '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
- };
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
+ };