1
+ const LOWER_A = 0x61
2
+ const LOWER_Z = 0x7a
3
+ const LOWER_E = 0x65
4
+ const UPPER_E = 0x45
5
+ const ZERO = 0x30
6
+ const NINE = 0x39
7
+ const ADD = 0x2b
8
+ const SUB = 0x2d
9
+ const MUL = 0x2a
10
+ const DIV = 0x2f
11
+ const OPEN_PAREN = 0x28
12
+ const CLOSE_PAREN = 0x29
13
+ const COMMA = 0x2c
14
+ const SPACE = 0x20
15
+
1
16
const MATH_FUNCTIONS = [
2
17
'calc' ,
3
18
'min' ,
@@ -20,9 +35,6 @@ const MATH_FUNCTIONS = [
20
35
'round' ,
21
36
]
22
37
23
- const KNOWN_DASHED_FUNCTIONS = [ 'anchor-size' ]
24
- const DASHED_FUNCTIONS_REGEX = new RegExp ( `(${ KNOWN_DASHED_FUNCTIONS . join ( '|' ) } )\\(` , 'g' )
25
-
26
38
export function hasMathFn ( input : string ) {
27
39
return input . indexOf ( '(' ) !== - 1 && MATH_FUNCTIONS . some ( ( fn ) => input . includes ( `${ fn } (` ) )
28
40
}
@@ -33,25 +45,36 @@ export function addWhitespaceAroundMathOperators(input: string) {
33
45
return input
34
46
}
35
47
36
- // Replace known functions with a placeholder
37
- let hasKnownFunctions = false
38
- if ( KNOWN_DASHED_FUNCTIONS . some ( ( fn ) => input . includes ( fn ) ) ) {
39
- DASHED_FUNCTIONS_REGEX . lastIndex = 0
40
- input = input . replace ( DASHED_FUNCTIONS_REGEX , ( _ , fn ) => {
41
- hasKnownFunctions = true
42
- return `$${ KNOWN_DASHED_FUNCTIONS . indexOf ( fn ) } $(`
43
- } )
44
- }
45
-
46
48
let result = ''
47
49
let formattable : boolean [ ] = [ ]
48
50
51
+ let valuePos = null
52
+ let lastValuePos = null
53
+
49
54
for ( let i = 0 ; i < input . length ; i ++ ) {
50
- let char = input [ i ]
55
+ let char = input . charCodeAt ( i )
56
+
57
+ // Track if we see a number followed by a unit, then we know for sure that
58
+ // this is not a function call.
59
+ if ( char >= ZERO && char <= NINE ) {
60
+ valuePos = i
61
+ }
62
+
63
+ // If we saw a number before, and we see normal a-z character, then we
64
+ // assume this is a value such as `123px`
65
+ else if ( valuePos !== null && char >= LOWER_A && char <= LOWER_Z ) {
66
+ valuePos = i
67
+ }
68
+
69
+ // Once we see something else, we reset the value position
70
+ else {
71
+ lastValuePos = valuePos
72
+ valuePos = null
73
+ }
51
74
52
75
// Determine if we're inside a math function
53
- if ( char === '(' ) {
54
- result += char
76
+ if ( char === OPEN_PAREN ) {
77
+ result += input [ i ]
55
78
56
79
// Scan backwards to determine the function name. This assumes math
57
80
// functions are named with lowercase alphanumeric characters.
@@ -60,9 +83,9 @@ export function addWhitespaceAroundMathOperators(input: string) {
60
83
for ( let j = i - 1 ; j >= 0 ; j -- ) {
61
84
let inner = input . charCodeAt ( j )
62
85
63
- if ( inner >= 48 && inner <= 57 ) {
86
+ if ( inner >= ZERO && inner <= NINE ) {
64
87
start = j // 0-9
65
- } else if ( inner >= 97 && inner <= 122 ) {
88
+ } else if ( inner >= LOWER_A && inner <= LOWER_Z ) {
66
89
start = j // a-z
67
90
} else {
68
91
break
@@ -91,76 +114,84 @@ export function addWhitespaceAroundMathOperators(input: string) {
91
114
92
115
// We've exited the function so format according to the parent function's
93
116
// type.
94
- else if ( char === ')' ) {
95
- result += char
117
+ else if ( char === CLOSE_PAREN ) {
118
+ result += input [ i ]
96
119
formattable . shift ( )
97
120
}
98
121
99
122
// Add spaces after commas in math functions
100
- else if ( char === ',' && formattable [ 0 ] ) {
123
+ else if ( char === COMMA && formattable [ 0 ] ) {
101
124
result += `, `
102
125
continue
103
126
}
104
127
105
128
// Skip over consecutive whitespace
106
- else if ( char === ' ' && formattable [ 0 ] && result [ result . length - 1 ] === ' ' ) {
129
+ else if ( char === SPACE && formattable [ 0 ] && result . charCodeAt ( result . length - 1 ) === SPACE ) {
107
130
continue
108
131
}
109
132
110
133
// Add whitespace around operators inside math functions
111
- else if ( ( char === '+' || char === '*' || char === '/' || char === '-' ) && formattable [ 0 ] ) {
134
+ else if ( ( char === ADD || char === MUL || char === DIV || char === SUB ) && formattable [ 0 ] ) {
112
135
let trimmed = result . trimEnd ( )
113
- let prev = trimmed [ trimmed . length - 1 ]
136
+ let prev = trimmed . charCodeAt ( trimmed . length - 1 )
137
+ let prevPrev = trimmed . charCodeAt ( trimmed . length - 2 )
138
+ let next = input . charCodeAt ( i + 1 )
139
+
140
+ // Do not add spaces for scientific notation, e.g.: `-3.4e-2`
141
+ if ( ( prev === LOWER_E || prev === UPPER_E ) && prevPrev >= ZERO && prevPrev <= NINE ) {
142
+ result += input [ i ]
143
+ continue
144
+ }
114
145
115
146
// If we're preceded by an operator don't add spaces
116
- if ( prev === '+' || prev === '*' || prev === '/' || prev === '-' ) {
117
- result += char
147
+ else if ( prev === ADD || prev === MUL || prev === DIV || prev === SUB ) {
148
+ result += input [ i ]
118
149
continue
119
150
}
120
151
121
152
// If we're at the beginning of an argument don't add spaces
122
- else if ( prev === '(' || prev === ',' ) {
123
- result += char
153
+ else if ( prev === OPEN_PAREN || prev === COMMA ) {
154
+ result += input [ i ]
124
155
continue
125
156
}
126
157
127
158
// Add spaces only after the operator if we already have spaces before it
128
- else if ( input [ i - 1 ] === ' ' ) {
129
- result += `${ char } `
159
+ else if ( input . charCodeAt ( i - 1 ) === SPACE ) {
160
+ result += `${ input [ i ] } `
130
161
}
131
162
132
- // Add spaces around the operator
133
- else {
134
- result += ` ${ char } `
163
+ // Add spaces around the operator, if...
164
+ else if (
165
+ // Previous is a digit
166
+ ( prev >= ZERO && prev <= NINE ) ||
167
+ // Next is a digit
168
+ ( next >= ZERO && next <= NINE ) ||
169
+ // Previous is end of a function call (or parenthesized expression)
170
+ prev === CLOSE_PAREN ||
171
+ // Next is start of a parenthesized expression
172
+ next === OPEN_PAREN ||
173
+ // Next is an operator
174
+ next === ADD ||
175
+ next === MUL ||
176
+ next === DIV ||
177
+ next === SUB ||
178
+ // Previous position was a value (+ unit)
179
+ ( lastValuePos !== null && lastValuePos === i - 1 )
180
+ ) {
181
+ result += ` ${ input [ i ] } `
135
182
}
136
- }
137
183
138
- // Skip over `to-zero` when in a math function.
139
- //
140
- // This is specifically to handle this value in the round(…) function:
141
- //
142
- // ```
143
- // round(to-zero, 1px)
144
- // ^^^^^^^
145
- // ```
146
- //
147
- // This is because the first argument is optionally a keyword and `to-zero`
148
- // contains a hyphen and we want to avoid adding spaces inside it.
149
- else if ( formattable [ 0 ] && input . startsWith ( 'to-zero' , i ) ) {
150
- let start = i
151
- i += 7
152
- result += input . slice ( start , i + 1 )
184
+ // Everything else
185
+ else {
186
+ result += input [ i ]
187
+ }
153
188
}
154
189
155
190
// Handle all other characters
156
191
else {
157
- result += char
192
+ result += input [ i ]
158
193
}
159
194
}
160
195
161
- if ( hasKnownFunctions ) {
162
- return result . replace ( / \$ ( \d + ) \$ / g, ( fn , idx ) => KNOWN_DASHED_FUNCTIONS [ idx ] ?? fn )
163
- }
164
-
165
196
return result
166
197
}
0 commit comments