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/parser.js CHANGED
@@ -1,421 +1,424 @@
1
- const fs = require('fs');
2
-
3
- /**
4
- * Regular expressions for matching various CSS/SCSS value types
5
- */
6
- const PATTERNS = {
7
- // Hex colors: #RGB, #RRGGBB, #RRGGBBAA
8
- hexColor: /#([0-9a-fA-F]{3,8})\b/g,
9
-
10
- // RGB/RGBA colors
11
- rgbColor: /rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/g,
12
-
13
- // Named colors (common ones)
14
- namedColor: /\b(red|blue|green|white|black|gray|grey|yellow|orange|purple|pink|brown|cyan|magenta|transparent)\b/g,
15
-
16
- // Pixel values
17
- pixelValue: /\b(\d+)px\b/g,
18
-
19
- // Percentage values
20
- percentValue: /\b(\d+)%\b/g,
21
-
22
- // Font weight values
23
- fontWeight: /\b(bold|normal|lighter|bolder|[1-9]00)\b/g,
24
-
25
- // Decimal values (for opacity, line-height)
26
- decimalValue: /\b(0?\.\d+|1\.\d+|[2-9]\.\d+)\b/g,
27
-
28
- // Box shadow
29
- boxShadow: /(-?\d+px\s+){2,4}(rgba?\([^)]+\)|#[0-9a-fA-F]{3,8}|\w+)/g,
30
-
31
- // Font family
32
- fontFamily: /(['"][^'"]+['"](?:\s*,\s*(?:sans-serif|serif|monospace|cursive|fantasy))?)/g,
33
-
34
- // Transition
35
- transition: /\b(all|none|[\w-]+)\s+[\d.]+m?s\s+(?:ease|linear|ease-in|ease-out|ease-in-out|cubic-bezier\([^)]+\))/g,
36
-
37
- // Variable declaration (to skip)
38
- variableDeclaration: /\$[\w-]+\s*:/g,
39
-
40
- // URL function (to skip)
41
- urlFunction: /url\([^)]+\)/g,
42
-
43
- // Content property (to skip string literals)
44
- contentProperty: /content\s*:\s*[^;]+;/g,
45
-
46
- // String interpolation (to skip)
47
- interpolation: /#\{[^}]+\}/g,
48
-
49
- // @use and @forward statements (to skip)
50
- useForward: /@(?:use|forward)\s+[^;]+;/g,
51
-
52
- // Material palette functions (to skip)
53
- materialFunctions: /mat\.(?:define-palette|get-color-from-palette|define-(?:light|dark)-theme|all-component-themes)\([^)]*\)/g
54
- };
55
-
56
- /**
57
- * Extracts hardcoded values from SCSS content
58
- * @param {string} content - SCSS file content
59
- * @param {string} filePath - Path to the file (for context)
60
- * @returns {Object} - Extracted values categorized by type
61
- */
62
- function parseScss(content, filePath) {
63
- const extracted = {
64
- colors: [],
65
- spacing: [],
66
- fontSizes: [],
67
- fontWeights: [],
68
- fontFamilies: [],
69
- borderRadius: [],
70
- shadows: [],
71
- zIndex: [],
72
- sizing: [],
73
- lineHeight: [],
74
- opacity: [],
75
- transitions: []
76
- };
77
-
78
- // Remove comments
79
- let cleanContent = content.replace(/\/\*[\s\S]*?\*\//g, '');
80
- cleanContent = cleanContent.replace(/\/\/.*/g, '');
81
-
82
- // Remove safe zones (values we should not extract)
83
- const safeZones = [];
84
-
85
- // Mark variable declarations - entire lines with $variable:
86
- let match;
87
- const varDeclPattern = /\$[\w-]+\s*:[^;]+;/g;
88
- while ((match = varDeclPattern.exec(cleanContent)) !== null) {
89
- safeZones.push({ start: match.index, end: match.index + match[0].length });
90
- }
91
-
92
- // Mark other safe zones
93
- const markers = [
94
- PATTERNS.urlFunction,
95
- PATTERNS.contentProperty,
96
- PATTERNS.interpolation,
97
- PATTERNS.useForward,
98
- PATTERNS.materialFunctions
99
- ];
100
-
101
- markers.forEach(pattern => {
102
- pattern.lastIndex = 0;
103
- while ((match = pattern.exec(cleanContent)) !== null) {
104
- safeZones.push({ start: match.index, end: match.index + match[0].length });
105
- }
106
- });
107
-
108
- // Helper to check if position is in safe zone
109
- const isInSafeZone = (pos) => {
110
- return safeZones.some(zone => pos >= zone.start && pos < zone.end);
111
- };
112
-
113
- // Extract colors (hex)
114
- PATTERNS.hexColor.lastIndex = 0;
115
- while ((match = PATTERNS.hexColor.exec(cleanContent)) !== null) {
116
- if (!isInSafeZone(match.index)) {
117
- extracted.colors.push({
118
- value: match[0],
119
- line: getLineNumber(cleanContent, match.index),
120
- file: filePath,
121
- context: getContext(cleanContent, match.index)
122
- });
123
- }
124
- }
125
-
126
- // Extract colors (rgb/rgba)
127
- PATTERNS.rgbColor.lastIndex = 0;
128
- while ((match = PATTERNS.rgbColor.exec(cleanContent)) !== null) {
129
- if (!isInSafeZone(match.index)) {
130
- extracted.colors.push({
131
- value: match[0],
132
- line: getLineNumber(cleanContent, match.index),
133
- file: filePath,
134
- context: getContext(cleanContent, match.index)
135
- });
136
- }
137
- }
138
-
139
- // Extract named colors
140
- PATTERNS.namedColor.lastIndex = 0;
141
- while ((match = PATTERNS.namedColor.exec(cleanContent)) !== null) {
142
- if (!isInSafeZone(match.index)) {
143
- const ctx = getContext(cleanContent, match.index);
144
- // Only extract if it looks like a color value (not in selectors)
145
- if (ctx.includes(':')) {
146
- extracted.colors.push({
147
- value: match[0],
148
- line: getLineNumber(cleanContent, match.index),
149
- file: filePath,
150
- context: ctx
151
- });
152
- }
153
- }
154
- }
155
-
156
- // Extract pixel values (categorize by property)
157
- PATTERNS.pixelValue.lastIndex = 0;
158
- while ((match = PATTERNS.pixelValue.exec(cleanContent)) !== null) {
159
- if (!isInSafeZone(match.index)) {
160
- const ctx = getContext(cleanContent, match.index);
161
- const property = extractPropertyAtPosition(cleanContent, match.index);
162
-
163
- if (isFontSizeProperty(property)) {
164
- extracted.fontSizes.push({
165
- value: match[0],
166
- line: getLineNumber(cleanContent, match.index),
167
- file: filePath,
168
- context: ctx,
169
- property
170
- });
171
- } else if (isSpacingProperty(property)) {
172
- extracted.spacing.push({
173
- value: match[0],
174
- line: getLineNumber(cleanContent, match.index),
175
- file: filePath,
176
- context: ctx,
177
- property
178
- });
179
- } else if (isSizingProperty(property)) {
180
- extracted.sizing.push({
181
- value: match[0],
182
- line: getLineNumber(cleanContent, match.index),
183
- file: filePath,
184
- context: ctx,
185
- property
186
- });
187
- } else if (isBorderRadiusProperty(property)) {
188
- extracted.borderRadius.push({
189
- value: match[0],
190
- line: getLineNumber(cleanContent, match.index),
191
- file: filePath,
192
- context: ctx,
193
- property
194
- });
195
- } else if (isLineHeightProperty(property)) {
196
- extracted.lineHeight.push({
197
- value: match[0],
198
- line: getLineNumber(cleanContent, match.index),
199
- file: filePath,
200
- context: ctx,
201
- property
202
- });
203
- }
204
- }
205
- }
206
-
207
- // Extract percentage values (for border-radius)
208
- PATTERNS.percentValue.lastIndex = 0;
209
- while ((match = PATTERNS.percentValue.exec(cleanContent)) !== null) {
210
- if (!isInSafeZone(match.index)) {
211
- const ctx = getContext(cleanContent, match.index);
212
- const property = extractPropertyAtPosition(cleanContent, match.index);
213
-
214
- if (isBorderRadiusProperty(property)) {
215
- extracted.borderRadius.push({
216
- value: match[0],
217
- line: getLineNumber(cleanContent, match.index),
218
- file: filePath,
219
- context: ctx,
220
- property
221
- });
222
- } else if (isOpacityProperty(property)) {
223
- extracted.opacity.push({
224
- value: match[0],
225
- line: getLineNumber(cleanContent, match.index),
226
- file: filePath,
227
- context: ctx,
228
- property
229
- });
230
- }
231
- }
232
- }
233
-
234
- // Extract font weights
235
- PATTERNS.fontWeight.lastIndex = 0;
236
- while ((match = PATTERNS.fontWeight.exec(cleanContent)) !== null) {
237
- if (!isInSafeZone(match.index)) {
238
- const ctx = getContext(cleanContent, match.index);
239
- const property = extractPropertyAtPosition(cleanContent, match.index);
240
-
241
- if (property && property.includes('font-weight')) {
242
- extracted.fontWeights.push({
243
- value: match[0],
244
- line: getLineNumber(cleanContent, match.index),
245
- file: filePath,
246
- context: ctx,
247
- property
248
- });
249
- }
250
- }
251
- }
252
-
253
- // Extract font families
254
- PATTERNS.fontFamily.lastIndex = 0;
255
- while ((match = PATTERNS.fontFamily.exec(cleanContent)) !== null) {
256
- if (!isInSafeZone(match.index)) {
257
- const ctx = getContext(cleanContent, match.index);
258
- const property = extractPropertyAtPosition(cleanContent, match.index);
259
-
260
- if (property && property.includes('font-family')) {
261
- extracted.fontFamilies.push({
262
- value: match[0],
263
- line: getLineNumber(cleanContent, match.index),
264
- file: filePath,
265
- context: ctx,
266
- property
267
- });
268
- }
269
- }
270
- }
271
-
272
- // Extract box shadows
273
- PATTERNS.boxShadow.lastIndex = 0;
274
- while ((match = PATTERNS.boxShadow.exec(cleanContent)) !== null) {
275
- if (!isInSafeZone(match.index)) {
276
- const ctx = getContext(cleanContent, match.index);
277
- const property = extractPropertyAtPosition(cleanContent, match.index);
278
-
279
- if (property && property.includes('box-shadow')) {
280
- extracted.shadows.push({
281
- value: match[0],
282
- line: getLineNumber(cleanContent, match.index),
283
- file: filePath,
284
- context: ctx,
285
- property
286
- });
287
- }
288
- }
289
- }
290
-
291
- // Extract decimal values (opacity, line-height)
292
- PATTERNS.decimalValue.lastIndex = 0;
293
- while ((match = PATTERNS.decimalValue.exec(cleanContent)) !== null) {
294
- if (!isInSafeZone(match.index)) {
295
- const ctx = getContext(cleanContent, match.index);
296
- const property = extractPropertyAtPosition(cleanContent, match.index);
297
-
298
- if (isOpacityProperty(property)) {
299
- extracted.opacity.push({
300
- value: match[0],
301
- line: getLineNumber(cleanContent, match.index),
302
- file: filePath,
303
- context: ctx,
304
- property
305
- });
306
- } else if (isLineHeightProperty(property)) {
307
- extracted.lineHeight.push({
308
- value: match[0],
309
- line: getLineNumber(cleanContent, match.index),
310
- file: filePath,
311
- context: ctx,
312
- property
313
- });
314
- }
315
- }
316
- }
317
-
318
- // Extract transitions
319
- PATTERNS.transition.lastIndex = 0;
320
- while ((match = PATTERNS.transition.exec(cleanContent)) !== null) {
321
- if (!isInSafeZone(match.index)) {
322
- extracted.transitions.push({
323
- value: match[0],
324
- line: getLineNumber(cleanContent, match.index),
325
- file: filePath,
326
- context: getContext(cleanContent, match.index)
327
- });
328
- }
329
- }
330
-
331
- // Extract z-index values (pure integers in z-index context)
332
- const zIndexRegex = /z-index\s*:\s*(\d+)/g;
333
- while ((match = zIndexRegex.exec(cleanContent)) !== null) {
334
- if (!isInSafeZone(match.index)) {
335
- extracted.zIndex.push({
336
- value: match[1],
337
- line: getLineNumber(cleanContent, match.index),
338
- file: filePath,
339
- context: getContext(cleanContent, match.index)
340
- });
341
- }
342
- }
343
-
344
- return extracted;
345
- }
346
-
347
- /**
348
- * Gets line number for a position in text
349
- */
350
- function getLineNumber(text, position) {
351
- return text.substring(0, position).split('\n').length;
352
- }
353
-
354
- /**
355
- * Gets surrounding context for a position
356
- */
357
- function getContext(text, position, contextSize = 50) {
358
- const start = Math.max(0, position - contextSize);
359
- const end = Math.min(text.length, position + contextSize);
360
- return text.substring(start, end).trim();
361
- }
362
-
363
- /**
364
- * Extracts property name at a specific position in the content
365
- * Looks backward from position to find the property name
366
- */
367
- function extractPropertyAtPosition(content, position) {
368
- // Find the start of the current line
369
- const lineStart = content.lastIndexOf('\n', position) + 1;
370
- const lineSegment = content.substring(lineStart, position);
371
-
372
- // Look for property: pattern before the position
373
- const match = lineSegment.match(/([\w-]+)\s*:\s*[^:;]*$/);
374
- return match ? match[1] : null;
375
- }
376
-
377
- /**
378
- * Extracts property name from context (legacy, for other uses)
379
- */
380
- function extractProperty(context) {
381
- const match = context.match(/([\w-]+)\s*:/);
382
- return match ? match[1] : null;
383
- }
384
-
385
- /**
386
- * Property type checkers
387
- */
388
- function isSpacingProperty(prop) {
389
- if (!prop) return false;
390
- return /^(padding|margin|gap|top|right|bottom|left)/.test(prop);
391
- }
392
-
393
- function isFontSizeProperty(prop) {
394
- if (!prop) return false;
395
- return prop === 'font-size';
396
- }
397
-
398
- function isSizingProperty(prop) {
399
- if (!prop) return false;
400
- return /^(width|height|min-width|max-width|min-height|max-height)/.test(prop);
401
- }
402
-
403
- function isBorderRadiusProperty(prop) {
404
- if (!prop) return false;
405
- return /border-radius/.test(prop);
406
- }
407
-
408
- function isLineHeightProperty(prop) {
409
- if (!prop) return false;
410
- return prop === 'line-height';
411
- }
412
-
413
- function isOpacityProperty(prop) {
414
- if (!prop) return false;
415
- return prop === 'opacity';
416
- }
417
-
418
- module.exports = {
419
- parseScss,
420
- PATTERNS
421
- };
1
+ const fs = require('fs');
2
+
3
+ /**
4
+ * Regular expressions for matching various CSS/SCSS value types
5
+ */
6
+ const PATTERNS = {
7
+ // Hex colors: #RGB, #RRGGBB, #RRGGBBAA
8
+ hexColor: /#([0-9a-fA-F]{3,8})\b/g,
9
+
10
+ // RGB/RGBA colors
11
+ rgbColor: /rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/g,
12
+
13
+ // Named colors (common ones)
14
+ namedColor:
15
+ /\b(red|blue|green|white|black|gray|grey|yellow|orange|purple|pink|brown|cyan|magenta|transparent)\b/g,
16
+
17
+ // Pixel values
18
+ pixelValue: /\b(\d+)px\b/g,
19
+
20
+ // Percentage values
21
+ percentValue: /\b(\d+)%\b/g,
22
+
23
+ // Font weight values
24
+ fontWeight: /\b(bold|normal|lighter|bolder|[1-9]00)\b/g,
25
+
26
+ // Decimal values (for opacity, line-height)
27
+ decimalValue: /\b(0?\.\d+|1\.\d+|[2-9]\.\d+)\b/g,
28
+
29
+ // Box shadow
30
+ boxShadow: /(-?\d+px\s+){2,4}(rgba?\([^)]+\)|#[0-9a-fA-F]{3,8}|\w+)/g,
31
+
32
+ // Font family
33
+ fontFamily: /(['"][^'"]+['"](?:\s*,\s*(?:sans-serif|serif|monospace|cursive|fantasy))?)/g,
34
+
35
+ // Transition
36
+ transition:
37
+ /\b(all|none|[\w-]+)\s+[\d.]+m?s\s+(?:ease|linear|ease-in|ease-out|ease-in-out|cubic-bezier\([^)]+\))/g,
38
+
39
+ // Variable declaration (to skip)
40
+ variableDeclaration: /\$[\w-]+\s*:/g,
41
+
42
+ // URL function (to skip)
43
+ urlFunction: /url\([^)]+\)/g,
44
+
45
+ // Content property (to skip string literals)
46
+ contentProperty: /content\s*:\s*[^;]+;/g,
47
+
48
+ // String interpolation (to skip)
49
+ interpolation: /#\{[^}]+\}/g,
50
+
51
+ // @use and @forward statements (to skip)
52
+ useForward: /@(?:use|forward)\s+[^;]+;/g,
53
+
54
+ // Material palette functions (to skip)
55
+ materialFunctions:
56
+ /mat\.(?:define-palette|get-color-from-palette|define-(?:light|dark)-theme|all-component-themes)\([^)]*\)/g,
57
+ };
58
+
59
+ /**
60
+ * Extracts hardcoded values from SCSS content
61
+ * @param {string} content - SCSS file content
62
+ * @param {string} filePath - Path to the file (for context)
63
+ * @returns {Object} - Extracted values categorized by type
64
+ */
65
+ function parseScss(content, filePath) {
66
+ const extracted = {
67
+ colors: [],
68
+ spacing: [],
69
+ fontSizes: [],
70
+ fontWeights: [],
71
+ fontFamilies: [],
72
+ borderRadius: [],
73
+ shadows: [],
74
+ zIndex: [],
75
+ sizing: [],
76
+ lineHeight: [],
77
+ opacity: [],
78
+ transitions: [],
79
+ };
80
+
81
+ // Remove comments
82
+ let cleanContent = content.replace(/\/\*[\s\S]*?\*\//g, '');
83
+ cleanContent = cleanContent.replace(/\/\/.*/g, '');
84
+
85
+ // Remove safe zones (values we should not extract)
86
+ const safeZones = [];
87
+
88
+ // Mark variable declarations - entire lines with $variable:
89
+ let match;
90
+ const varDeclPattern = /\$[\w-]+\s*:[^;]+;/g;
91
+ while ((match = varDeclPattern.exec(cleanContent)) !== null) {
92
+ safeZones.push({ start: match.index, end: match.index + match[0].length });
93
+ }
94
+
95
+ // Mark other safe zones
96
+ const markers = [
97
+ PATTERNS.urlFunction,
98
+ PATTERNS.contentProperty,
99
+ PATTERNS.interpolation,
100
+ PATTERNS.useForward,
101
+ PATTERNS.materialFunctions,
102
+ ];
103
+
104
+ markers.forEach(pattern => {
105
+ pattern.lastIndex = 0;
106
+ while ((match = pattern.exec(cleanContent)) !== null) {
107
+ safeZones.push({ start: match.index, end: match.index + match[0].length });
108
+ }
109
+ });
110
+
111
+ // Helper to check if position is in safe zone
112
+ const isInSafeZone = pos => {
113
+ return safeZones.some(zone => pos >= zone.start && pos < zone.end);
114
+ };
115
+
116
+ // Extract colors (hex)
117
+ PATTERNS.hexColor.lastIndex = 0;
118
+ while ((match = PATTERNS.hexColor.exec(cleanContent)) !== null) {
119
+ if (!isInSafeZone(match.index)) {
120
+ extracted.colors.push({
121
+ value: match[0],
122
+ line: getLineNumber(cleanContent, match.index),
123
+ file: filePath,
124
+ context: getContext(cleanContent, match.index),
125
+ });
126
+ }
127
+ }
128
+
129
+ // Extract colors (rgb/rgba)
130
+ PATTERNS.rgbColor.lastIndex = 0;
131
+ while ((match = PATTERNS.rgbColor.exec(cleanContent)) !== null) {
132
+ if (!isInSafeZone(match.index)) {
133
+ extracted.colors.push({
134
+ value: match[0],
135
+ line: getLineNumber(cleanContent, match.index),
136
+ file: filePath,
137
+ context: getContext(cleanContent, match.index),
138
+ });
139
+ }
140
+ }
141
+
142
+ // Extract named colors
143
+ PATTERNS.namedColor.lastIndex = 0;
144
+ while ((match = PATTERNS.namedColor.exec(cleanContent)) !== null) {
145
+ if (!isInSafeZone(match.index)) {
146
+ const ctx = getContext(cleanContent, match.index);
147
+ // Only extract if it looks like a color value (not in selectors)
148
+ if (ctx.includes(':')) {
149
+ extracted.colors.push({
150
+ value: match[0],
151
+ line: getLineNumber(cleanContent, match.index),
152
+ file: filePath,
153
+ context: ctx,
154
+ });
155
+ }
156
+ }
157
+ }
158
+
159
+ // Extract pixel values (categorize by property)
160
+ PATTERNS.pixelValue.lastIndex = 0;
161
+ while ((match = PATTERNS.pixelValue.exec(cleanContent)) !== null) {
162
+ if (!isInSafeZone(match.index)) {
163
+ const ctx = getContext(cleanContent, match.index);
164
+ const property = extractPropertyAtPosition(cleanContent, match.index);
165
+
166
+ if (isFontSizeProperty(property)) {
167
+ extracted.fontSizes.push({
168
+ value: match[0],
169
+ line: getLineNumber(cleanContent, match.index),
170
+ file: filePath,
171
+ context: ctx,
172
+ property,
173
+ });
174
+ } else if (isSpacingProperty(property)) {
175
+ extracted.spacing.push({
176
+ value: match[0],
177
+ line: getLineNumber(cleanContent, match.index),
178
+ file: filePath,
179
+ context: ctx,
180
+ property,
181
+ });
182
+ } else if (isSizingProperty(property)) {
183
+ extracted.sizing.push({
184
+ value: match[0],
185
+ line: getLineNumber(cleanContent, match.index),
186
+ file: filePath,
187
+ context: ctx,
188
+ property,
189
+ });
190
+ } else if (isBorderRadiusProperty(property)) {
191
+ extracted.borderRadius.push({
192
+ value: match[0],
193
+ line: getLineNumber(cleanContent, match.index),
194
+ file: filePath,
195
+ context: ctx,
196
+ property,
197
+ });
198
+ } else if (isLineHeightProperty(property)) {
199
+ extracted.lineHeight.push({
200
+ value: match[0],
201
+ line: getLineNumber(cleanContent, match.index),
202
+ file: filePath,
203
+ context: ctx,
204
+ property,
205
+ });
206
+ }
207
+ }
208
+ }
209
+
210
+ // Extract percentage values (for border-radius)
211
+ PATTERNS.percentValue.lastIndex = 0;
212
+ while ((match = PATTERNS.percentValue.exec(cleanContent)) !== null) {
213
+ if (!isInSafeZone(match.index)) {
214
+ const ctx = getContext(cleanContent, match.index);
215
+ const property = extractPropertyAtPosition(cleanContent, match.index);
216
+
217
+ if (isBorderRadiusProperty(property)) {
218
+ extracted.borderRadius.push({
219
+ value: match[0],
220
+ line: getLineNumber(cleanContent, match.index),
221
+ file: filePath,
222
+ context: ctx,
223
+ property,
224
+ });
225
+ } else if (isOpacityProperty(property)) {
226
+ extracted.opacity.push({
227
+ value: match[0],
228
+ line: getLineNumber(cleanContent, match.index),
229
+ file: filePath,
230
+ context: ctx,
231
+ property,
232
+ });
233
+ }
234
+ }
235
+ }
236
+
237
+ // Extract font weights
238
+ PATTERNS.fontWeight.lastIndex = 0;
239
+ while ((match = PATTERNS.fontWeight.exec(cleanContent)) !== null) {
240
+ if (!isInSafeZone(match.index)) {
241
+ const ctx = getContext(cleanContent, match.index);
242
+ const property = extractPropertyAtPosition(cleanContent, match.index);
243
+
244
+ if (property && property.includes('font-weight')) {
245
+ extracted.fontWeights.push({
246
+ value: match[0],
247
+ line: getLineNumber(cleanContent, match.index),
248
+ file: filePath,
249
+ context: ctx,
250
+ property,
251
+ });
252
+ }
253
+ }
254
+ }
255
+
256
+ // Extract font families
257
+ PATTERNS.fontFamily.lastIndex = 0;
258
+ while ((match = PATTERNS.fontFamily.exec(cleanContent)) !== null) {
259
+ if (!isInSafeZone(match.index)) {
260
+ const ctx = getContext(cleanContent, match.index);
261
+ const property = extractPropertyAtPosition(cleanContent, match.index);
262
+
263
+ if (property && property.includes('font-family')) {
264
+ extracted.fontFamilies.push({
265
+ value: match[0],
266
+ line: getLineNumber(cleanContent, match.index),
267
+ file: filePath,
268
+ context: ctx,
269
+ property,
270
+ });
271
+ }
272
+ }
273
+ }
274
+
275
+ // Extract box shadows
276
+ PATTERNS.boxShadow.lastIndex = 0;
277
+ while ((match = PATTERNS.boxShadow.exec(cleanContent)) !== null) {
278
+ if (!isInSafeZone(match.index)) {
279
+ const ctx = getContext(cleanContent, match.index);
280
+ const property = extractPropertyAtPosition(cleanContent, match.index);
281
+
282
+ if (property && property.includes('box-shadow')) {
283
+ extracted.shadows.push({
284
+ value: match[0],
285
+ line: getLineNumber(cleanContent, match.index),
286
+ file: filePath,
287
+ context: ctx,
288
+ property,
289
+ });
290
+ }
291
+ }
292
+ }
293
+
294
+ // Extract decimal values (opacity, line-height)
295
+ PATTERNS.decimalValue.lastIndex = 0;
296
+ while ((match = PATTERNS.decimalValue.exec(cleanContent)) !== null) {
297
+ if (!isInSafeZone(match.index)) {
298
+ const ctx = getContext(cleanContent, match.index);
299
+ const property = extractPropertyAtPosition(cleanContent, match.index);
300
+
301
+ if (isOpacityProperty(property)) {
302
+ extracted.opacity.push({
303
+ value: match[0],
304
+ line: getLineNumber(cleanContent, match.index),
305
+ file: filePath,
306
+ context: ctx,
307
+ property,
308
+ });
309
+ } else if (isLineHeightProperty(property)) {
310
+ extracted.lineHeight.push({
311
+ value: match[0],
312
+ line: getLineNumber(cleanContent, match.index),
313
+ file: filePath,
314
+ context: ctx,
315
+ property,
316
+ });
317
+ }
318
+ }
319
+ }
320
+
321
+ // Extract transitions
322
+ PATTERNS.transition.lastIndex = 0;
323
+ while ((match = PATTERNS.transition.exec(cleanContent)) !== null) {
324
+ if (!isInSafeZone(match.index)) {
325
+ extracted.transitions.push({
326
+ value: match[0],
327
+ line: getLineNumber(cleanContent, match.index),
328
+ file: filePath,
329
+ context: getContext(cleanContent, match.index),
330
+ });
331
+ }
332
+ }
333
+
334
+ // Extract z-index values (pure integers in z-index context)
335
+ const zIndexRegex = /z-index\s*:\s*(\d+)/g;
336
+ while ((match = zIndexRegex.exec(cleanContent)) !== null) {
337
+ if (!isInSafeZone(match.index)) {
338
+ extracted.zIndex.push({
339
+ value: match[1],
340
+ line: getLineNumber(cleanContent, match.index),
341
+ file: filePath,
342
+ context: getContext(cleanContent, match.index),
343
+ });
344
+ }
345
+ }
346
+
347
+ return extracted;
348
+ }
349
+
350
+ /**
351
+ * Gets line number for a position in text
352
+ */
353
+ function getLineNumber(text, position) {
354
+ return text.substring(0, position).split('\n').length;
355
+ }
356
+
357
+ /**
358
+ * Gets surrounding context for a position
359
+ */
360
+ function getContext(text, position, contextSize = 50) {
361
+ const start = Math.max(0, position - contextSize);
362
+ const end = Math.min(text.length, position + contextSize);
363
+ return text.substring(start, end).trim();
364
+ }
365
+
366
+ /**
367
+ * Extracts property name at a specific position in the content
368
+ * Looks backward from position to find the property name
369
+ */
370
+ function extractPropertyAtPosition(content, position) {
371
+ // Find the start of the current line
372
+ const lineStart = content.lastIndexOf('\n', position) + 1;
373
+ const lineSegment = content.substring(lineStart, position);
374
+
375
+ // Look for property: pattern before the position
376
+ const match = lineSegment.match(/([\w-]+)\s*:\s*[^:;]*$/);
377
+ return match ? match[1] : null;
378
+ }
379
+
380
+ /**
381
+ * Extracts property name from context (legacy, for other uses)
382
+ */
383
+ function extractProperty(context) {
384
+ const match = context.match(/([\w-]+)\s*:/);
385
+ return match ? match[1] : null;
386
+ }
387
+
388
+ /**
389
+ * Property type checkers
390
+ */
391
+ function isSpacingProperty(prop) {
392
+ if (!prop) return false;
393
+ return /^(padding|margin|gap|top|right|bottom|left)/.test(prop);
394
+ }
395
+
396
+ function isFontSizeProperty(prop) {
397
+ if (!prop) return false;
398
+ return prop === 'font-size';
399
+ }
400
+
401
+ function isSizingProperty(prop) {
402
+ if (!prop) return false;
403
+ return /^(width|height|min-width|max-width|min-height|max-height)/.test(prop);
404
+ }
405
+
406
+ function isBorderRadiusProperty(prop) {
407
+ if (!prop) return false;
408
+ return /border-radius/.test(prop);
409
+ }
410
+
411
+ function isLineHeightProperty(prop) {
412
+ if (!prop) return false;
413
+ return prop === 'line-height';
414
+ }
415
+
416
+ function isOpacityProperty(prop) {
417
+ if (!prop) return false;
418
+ return prop === 'opacity';
419
+ }
420
+
421
+ module.exports = {
422
+ parseScss,
423
+ PATTERNS,
424
+ };