@@ -77,12 +77,16 @@ const extractVarsFromCSS = (cssContent, options = {}) => {
77
77
const { allowPattern } = options ;
78
78
let list = { } ;
79
79
80
+ // retrieve the category names from the JSON config
81
+ const categories = Object . keys ( propTypes ) ;
82
+
80
83
rules . map ( ( rule ) => {
84
+ // Filter rule declarations to only those that contain at least one `var(...)` function
81
85
const filtered = rule . declarations . filter ( ( dec ) => {
82
86
if ( ! dec . value ) return false ;
83
87
84
- // match on var values that are not custom props
85
- if ( dec . value . match ( / v a r \( / ) && ! dec . property . match ( / ^ - - / ) ) {
88
+ // match on property values that contain at least one `var(...)` function
89
+ if ( dec . value . match ( / v a r \( / ) ) {
86
90
return true ;
87
91
}
88
92
@@ -111,23 +115,102 @@ const extractVarsFromCSS = (cssContent, options = {}) => {
111
115
// \) -> a closing parenthesis
112
116
const varRegex = / v a r \( (? = \S * [ ' - ] * ) ( [ a - z A - Z ' - ] + ) \s * [ , ] ? \s * ( .* ) \) / ;
113
117
118
+ // Sanitize a CSS property value
119
+ // - currently only removes extra content at the end of the value when closing parentheses aren't balanced
120
+ function sanitizeValue ( value ) {
121
+ let sanitizedValue = value ;
122
+ let parens = 0 ;
123
+ let startParens = 0 ;
124
+ let endParens = 0 ;
125
+
126
+ // check each character of the value string to count opening & closing parentheses
127
+ for ( let pos = 0 ; pos <= value . length ; pos ++ ) {
128
+ let char = value . charAt ( pos ) ;
129
+ switch ( char ) {
130
+ // increment parenthesis counter and startParens counter
131
+ case '(' :
132
+ parens ++ ;
133
+ startParens ++ ;
134
+ break ;
135
+ // decrement parenthesis counter and increment endParens counter
136
+ case ')' :
137
+ parens -- ;
138
+ endParens ++ ;
139
+ break ;
140
+ }
141
+
142
+ // if we have unbalanced parentheses then sanitize
143
+ if ( parens !== 0 ) {
144
+ // if we have too many closing parentheses then sanitize things at the end
145
+ if ( endParens > startParens ) {
146
+ // find the index of the last closing balanced parenthesis
147
+ const closingParens = value . matchAll ( / \) / g) ;
148
+
149
+ // convert iterator object to an array
150
+ const closingParensArray = [ ] ;
151
+ for ( const match of closingParens ) {
152
+ closingParensArray . push ( match ) ;
153
+ }
154
+
155
+ // sort the array by ascending match index/position
156
+ closingParensArray . sort ( ( a , b ) => {
157
+ if ( a . index < b . index ) {
158
+ return - 1 ;
159
+ }
160
+ if ( a . index > b . index ) {
161
+ return 1 ;
162
+ }
163
+ return 0 ;
164
+ } ) ;
165
+
166
+ // get the index of the balanced closing parenthesis we want to keep
167
+ const indexOfBalancedParen =
168
+ closingParensArray . length - ( endParens - startParens ) - 1 ; // -1 to make it a zero based index
169
+ const balancedClosingParenIndex =
170
+ closingParensArray [ indexOfBalancedParen ] . index ;
171
+
172
+ // remove the extra content at the end that has no opening parentheses to match
173
+ sanitizedValue = value . slice ( 0 , balancedClosingParenIndex + 1 ) ; // +1 to avoid losing the parenthesis we want to keep
174
+ }
175
+ }
176
+ }
177
+
178
+ return sanitizedValue ;
179
+ }
180
+
114
181
/**
115
182
* Recursively drill into a CSS value to find the fallback value
116
183
*
117
184
* @param {string } - CSS property's value
118
185
* @returns {object } - hookName, name of the hook; fallback, value of the fallback
119
186
*/
120
- function findFallbackRecursively ( value ) {
187
+ function findFallbackRecursively ( value , recursionIndex = 0 ) {
121
188
// get the fallback value from the CSS variable clause
122
189
let regex = varRegex ;
123
- let matches = value . match ( regex ) ;
124
190
191
+ // do an initial match check
192
+ const initialMatches = value . match ( regex ) ;
193
+ let matches ;
194
+
195
+ // we have a match
196
+ if ( initialMatches ) {
197
+ // sanitize the returned match value found to ensure we're digging into a properly formatted var(...) value
198
+ const sanitizedValue = sanitizeValue ( initialMatches [ 0 ] ) ;
199
+ matches = sanitizedValue . match ( regex ) ;
200
+ }
201
+
202
+ // if we found a match then begin the recursion
125
203
if ( matches ) {
126
204
let fallbackValue = matches [ 2 ] ;
127
205
206
+ // if we're not on the first iteration then add the valid hook to the list
207
+ if ( recursionIndex > 0 ) {
208
+ addHookToList ( matches [ 1 ] , null ) ;
209
+ }
210
+
128
211
// if we have another CSS variable clause then keep drilling in
129
212
if ( isCssVarClause ( fallbackValue ) ) {
130
- return findFallbackRecursively ( fallbackValue ) ;
213
+ return findFallbackRecursively ( fallbackValue , recursionIndex ++ ) ;
131
214
} else {
132
215
return matches [ 2 ] . trim ( ) ;
133
216
}
@@ -142,6 +225,26 @@ const extractVarsFromCSS = (cssContent, options = {}) => {
142
225
}
143
226
///
144
227
228
+ // Add hook to the list to be shown
229
+ function addHookToList ( hookName , fallbackValue ) {
230
+ // do we have a category match? If so return it
231
+ const matchedCategory = categories . find ( ( option ) => {
232
+ if ( hookName . includes ( option ) ) {
233
+ return option ;
234
+ }
235
+ } ) ;
236
+
237
+ // add the fallback value to the hook's object in the list of results
238
+ list [ hookName ] = { fallbackValue : fallbackValue } ;
239
+
240
+ // if a category was found add the metadata to the hook's object in the list of results
241
+ if ( matchedCategory ) {
242
+ list [ hookName ] . category = propTypes [ matchedCategory ] . type ;
243
+ list [ hookName ] . valueType =
244
+ propTypes [ matchedCategory ] . valueTypes . join ( ', ' ) ;
245
+ }
246
+ }
247
+
145
248
if ( filtered . length > 0 ) {
146
249
filtered . forEach ( ( decl ) => {
147
250
if ( decl . value ) {
@@ -154,25 +257,7 @@ const extractVarsFromCSS = (cssContent, options = {}) => {
154
257
155
258
// if we have a hook then generate an object to add to the list of results
156
259
if ( hookName ) {
157
- // retrieve the category names from the JSON config
158
- const categories = Object . keys ( propTypes ) ;
159
-
160
- // do we have a category match? If so return it
161
- const matchedCategory = categories . find ( ( option ) => {
162
- if ( hookName . includes ( option ) ) {
163
- return option ;
164
- }
165
- } ) ;
166
-
167
- // add the fallback value to the hook's object in the list of results
168
- list [ hookName ] = { fallbackValue : fallback } ;
169
-
170
- // if a category was found add the metadata to the hook's object in the list of results
171
- if ( matchedCategory ) {
172
- list [ hookName ] . category = propTypes [ matchedCategory ] . type ;
173
- list [ hookName ] . valueType =
174
- propTypes [ matchedCategory ] . valueTypes . join ( ', ' ) ;
175
- }
260
+ addHookToList ( hookName , fallback ) ;
176
261
}
177
262
}
178
263
} ) ;
0 commit comments