@thejaredwilcurt/csslop 0.0.38 → 0.0.40

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.
@@ -2,40 +2,16 @@
2
2
  * @file Parses and minifies CSS gradient function calls by splitting arguments, normalizing default directions, and removing redundant stop positions.
3
3
  */
4
4
 
5
+ import {
6
+ findMatchingParenthesis,
7
+ splitTopLevelCommaList
8
+ } from '../parser/source-search.js';
9
+
5
10
  import {
6
11
  parseHex,
7
12
  shortestColor
8
13
  } from './colors.js';
9
14
 
10
- /**
11
- * Splits a gradient function's argument string at top-level commas, correctly handling nested parentheses.
12
- *
13
- * @param {string} argumentString The raw gradient arguments string.
14
- * @return {Array} An array of trimmed argument strings.
15
- */
16
- function splitGradientArgs (argumentString) {
17
- const parts = [];
18
- let depth = 0;
19
- let current = '';
20
- for (const character of argumentString) {
21
- if (character === '(') {
22
- depth++;
23
- } else if (character === ')') {
24
- depth--;
25
- }
26
- if (character === ',' && depth === 0) {
27
- parts.push(current.trim());
28
- current = '';
29
- } else {
30
- current += character;
31
- }
32
- }
33
- if (current.trim() || parts.length) {
34
- parts.push(current.trim());
35
- }
36
- return parts;
37
- }
38
-
39
15
  /**
40
16
  * Checks whether a string is a valid gradient stop position consisting of one
41
17
  * or two numeric tokens with optional CSS units.
@@ -435,45 +411,45 @@ function normalizeRadialGradientShape (args) {
435
411
  /**
436
412
  * Optimizes gradient arguments by removing default direction or shape keywords, combining adjacent identical color stops, and trimming redundant 0% or 100% stop positions from the first and last stops.
437
413
  *
438
- * @param {string} func The gradient function name (e.g. "linear-gradient").
439
- * @param {string} argsStr The raw comma-separated gradient arguments string.
440
- * @return {string} The optimized gradient arguments string.
414
+ * @param {string} functionName The gradient function name (e.g. "linear-gradient").
415
+ * @param {string} argumentText The raw comma-separated gradient arguments string.
416
+ * @return {string} The optimized gradient arguments string.
441
417
  */
442
- function processGradientArgs (func, argsStr) {
443
- const args = splitGradientArgs(argsStr);
444
- const functionLower = func.toLowerCase();
418
+ function processGradientArguments (functionName, argumentText) {
419
+ const argumentList = splitTopLevelCommaList(argumentText);
420
+ const lowercaseName = functionName.toLowerCase();
445
421
 
446
- let directionArgCount = 0;
422
+ let directionArgumentCount = 0;
447
423
 
448
- if (args.length > 1) {
449
- if (functionLower.includes('linear')) {
450
- directionArgCount = normalizeLinearGradientDirection(args);
451
- } else if (functionLower.includes('radial')) {
452
- directionArgCount = normalizeRadialGradientShape(args);
424
+ if (argumentList.length > 1) {
425
+ if (lowercaseName.includes('linear')) {
426
+ directionArgumentCount = normalizeLinearGradientDirection(argumentList);
427
+ } else if (lowercaseName.includes('radial')) {
428
+ directionArgumentCount = normalizeRadialGradientShape(argumentList);
453
429
  }
454
430
  }
455
431
 
456
- // Extract color stop args (everything after the direction/shape argument)
457
- const colorStopArgs = args.slice(directionArgCount).map((arg) => {
458
- return normalizeColorStop(arg);
432
+ // Extract color stop arguments (everything after the direction/shape argument)
433
+ const colorStopArguments = argumentList.slice(directionArgumentCount).map((argument) => {
434
+ return normalizeColorStop(argument);
459
435
  });
460
- if (colorStopArgs.length > 0) {
461
- const normalizedStops = colorStopArgs.length >= 2 ?
462
- combineAdjacentIdenticalStops(colorStopArgs) :
463
- colorStopArgs;
464
- args.splice(directionArgCount, colorStopArgs.length, ...normalizedStops);
436
+ if (colorStopArguments.length > 0) {
437
+ const normalizedStops = colorStopArguments.length >= 2 ?
438
+ combineAdjacentIdenticalStops(colorStopArguments) :
439
+ colorStopArguments;
440
+ argumentList.splice(directionArgumentCount, colorStopArguments.length, ...normalizedStops);
465
441
  }
466
442
 
467
- if (args.length > directionArgCount) {
468
- const firstStopIndex = directionArgCount;
469
- const lastStopIndex = args.length - 1;
443
+ if (argumentList.length > directionArgumentCount) {
444
+ const firstStopIndex = directionArgumentCount;
445
+ const lastStopIndex = argumentList.length - 1;
470
446
  // Remove default 0% stop position from the first gradient stop
471
- args[firstStopIndex] = args[firstStopIndex].replace(/^(.*\S)\s+0%$/, '$1');
447
+ argumentList[firstStopIndex] = argumentList[firstStopIndex].replace(/^(.*\S)\s+0%$/, '$1');
472
448
  // Remove default 100% stop position from the last gradient stop
473
- args[lastStopIndex] = args[lastStopIndex].replace(/^(.*\S)\s+100%$/, '$1');
449
+ argumentList[lastStopIndex] = argumentList[lastStopIndex].replace(/^(.*\S)\s+100%$/, '$1');
474
450
  }
475
451
 
476
- return args.join(',');
452
+ return argumentList.join(',');
477
453
  }
478
454
 
479
455
  /**
@@ -490,24 +466,21 @@ function minifyGradients (value) {
490
466
  // Match gradient function names: linear-gradient, radial-gradient, conic-gradient, and their repeating- variants
491
467
  const gradientMatch = rest.match(/^((?:repeating-)?(?:linear|radial|conic)-gradient)\(/i);
492
468
  if (gradientMatch) {
493
- const func = gradientMatch[1];
494
- let depth = 1;
495
- let end = position + func.length + 1;
496
- while (end < value.length && depth > 0) {
497
- if (value[end] === '(') {
498
- depth++;
499
- } else if (value[end] === ')') {
500
- depth--;
501
- }
502
- end++;
469
+ const functionName = gradientMatch[1];
470
+ const openIndex = position + functionName.length;
471
+ const closeIndex = findMatchingParenthesis(value, openIndex);
472
+ if (closeIndex === -1) {
473
+ // A gradient whose arguments never close is left exactly as it was written
474
+ result += value.slice(position);
475
+ break;
503
476
  }
504
- const argsStr = value.slice(position + func.length + 1, end - 1);
505
- result += func + '(' + processGradientArgs(func, argsStr) + ')';
506
- position = end;
507
- } else {
508
- result += value[position];
509
- position++;
477
+ const argumentText = value.slice(openIndex + 1, closeIndex);
478
+ result += functionName + '(' + processGradientArguments(functionName, argumentText) + ')';
479
+ position = closeIndex + 1;
480
+ continue;
510
481
  }
482
+ result += value[position];
483
+ position++;
511
484
  }
512
485
  return result;
513
486
  }
@@ -2,7 +2,12 @@
2
2
  * @file Simplifies CSS light-dark functions whose light and dark values are equivalent.
3
3
  */
4
4
 
5
- import { findMatchingParenthesis } from './syntax.js';
5
+ import {
6
+ findMatchingParenthesis,
7
+ skipQuotedString,
8
+ splitTopLevelCommaList,
9
+ startsUrlToken
10
+ } from '../parser/source-search.js';
6
11
 
7
12
  /**
8
13
  * Determines whether a character can appear inside a CSS identifier.
@@ -35,75 +40,6 @@ function startsLightDarkFunction (value, index) {
35
40
  return !isIdentifierCharacter(value[index - 1]);
36
41
  }
37
42
 
38
- /**
39
- * Splits a CSS function argument list at top-level commas while respecting
40
- * nested parentheses and quoted strings.
41
- *
42
- * @param {string} argumentString The raw content between function parentheses.
43
- * @return {Array} The top-level argument strings.
44
- */
45
- function splitTopLevelFunctionArguments (argumentString) {
46
- const argumentsList = [];
47
- let currentArgument = '';
48
- let depth = 0;
49
- let index = 0;
50
- let activeQuote = '';
51
-
52
- while (index < argumentString.length) {
53
- const character = argumentString[index];
54
- if (activeQuote) {
55
- currentArgument += character;
56
- if (character === '\\') {
57
- if (index + 1 < argumentString.length) {
58
- currentArgument += argumentString[index + 1];
59
- index += 2;
60
- continue;
61
- }
62
- } else if (character === activeQuote) {
63
- activeQuote = '';
64
- }
65
- index++;
66
- continue;
67
- }
68
-
69
- if (character === '"' || character === '\'') {
70
- activeQuote = character;
71
- currentArgument += character;
72
- index++;
73
- continue;
74
- }
75
-
76
- if (character === '(') {
77
- depth++;
78
- currentArgument += character;
79
- index++;
80
- continue;
81
- }
82
-
83
- if (character === ')') {
84
- if (depth > 0) {
85
- depth--;
86
- }
87
- currentArgument += character;
88
- index++;
89
- continue;
90
- }
91
-
92
- if (character === ',' && depth === 0) {
93
- argumentsList.push(currentArgument.trim());
94
- currentArgument = '';
95
- index++;
96
- continue;
97
- }
98
-
99
- currentArgument += character;
100
- index++;
101
- }
102
-
103
- argumentsList.push(currentArgument.trim());
104
- return argumentsList;
105
- }
106
-
107
43
  /**
108
44
  * Simplifies `light-dark(a,b)` to `a` when both top-level arguments are identical
109
45
  * after prior minification has normalized them.
@@ -115,36 +51,15 @@ function simplifyEquivalentLightDarkFunctions (value) {
115
51
  let result = '';
116
52
  let index = 0;
117
53
 
118
- const consumeQuoted = (start) => {
119
- const quote = value[start];
120
- let end = start + 1;
121
- while (end < value.length) {
122
- if (value[end] === '\\') {
123
- end += 2;
124
- continue;
125
- }
126
- if (value[end] === quote) {
127
- end++;
128
- break;
129
- }
130
- end++;
131
- }
132
- return end;
133
- };
134
-
135
- const startsUrl = (start) => {
136
- return value.slice(start, start + 4).toLowerCase() === 'url(';
137
- };
138
-
139
54
  while (index < value.length) {
140
55
  if (value[index] === '"' || value[index] === '\'') {
141
- const end = consumeQuoted(index);
56
+ const end = skipQuotedString(value, index);
142
57
  result += value.slice(index, end);
143
58
  index = end;
144
59
  continue;
145
60
  }
146
61
 
147
- if (startsUrl(index)) {
62
+ if (startsUrlToken(value, index)) {
148
63
  const end = findMatchingParenthesis(value, index + 3);
149
64
  if (end === -1) {
150
65
  result += value.slice(index);
@@ -164,7 +79,7 @@ function simplifyEquivalentLightDarkFunctions (value) {
164
79
  }
165
80
 
166
81
  const argumentString = value.slice(openParenIndex + 1, closingParenIndex);
167
- const argumentsList = splitTopLevelFunctionArguments(argumentString);
82
+ const argumentsList = splitTopLevelCommaList(argumentString);
168
83
  if (argumentsList.length === 2) {
169
84
  const firstArgument = simplifyEquivalentLightDarkFunctions(argumentsList[0]);
170
85
  const secondArgument = simplifyEquivalentLightDarkFunctions(argumentsList[1]);