@@ -131,12 +131,16 @@ export default {
131131 const vars = new Map ( ) ;
132132
133133 /**
134- * We need to track this as a stack because we can have nested
135- * rules that use the `var()` function, and we need to
136- * ensure that we validate the innermost rule first.
137- * @type {Array<Map<string,FunctionNodePlain>> }
134+ * @type {Array<{
135+ * valueParts: string[],
136+ * functionPartsStack: string[][],
137+ * valueSegmentLocs: Map<string,CssLocationRange>,
138+ * skipValidation: boolean,
139+ * hadVarSubstitution: boolean,
140+ * resolvedCache: Map<string,string>
141+ * }>}
138142 */
139- const replacements = [ ] ;
143+ const declStack = [ ] ;
140144
141145 const [ { allowUnknownVariables } ] = context . options ;
142146
@@ -245,26 +249,153 @@ export default {
245249 return null ;
246250 }
247251
252+ /**
253+ * Process a var function node and add its resolved value to the value list
254+ * @param {Object } varNode The var() function node
255+ * @param {string[] } valueList Array to collect processed values
256+ * @param {Map<string,CssLocationRange> } valueSegmentLocs Map of rebuilt value segments to their locations
257+ * @param {Map<string, string> } resolvedCache Cache for resolved variable values to prevent redundant lookups
258+ * @returns {boolean } Whether processing was successful
259+ */
260+ function processVarFunction (
261+ varNode ,
262+ valueList ,
263+ valueSegmentLocs ,
264+ resolvedCache ,
265+ ) {
266+ const varValue = vars . get ( varNode . children [ 0 ] . name ) ;
267+
268+ if ( varValue ) {
269+ const resolvedValue = resolveVariable (
270+ varNode . children [ 0 ] . name ,
271+ resolvedCache ,
272+ ) ;
273+ if ( resolvedValue ) {
274+ valueList . push ( resolvedValue ) ;
275+ valueSegmentLocs . set ( resolvedValue , varNode . loc ) ;
276+ return true ;
277+ }
278+ }
279+
280+ // If the variable is not found and doesn't have a fallback value, report it
281+ if ( varNode . children . length === 1 ) {
282+ if ( ! allowUnknownVariables ) {
283+ context . report ( {
284+ loc : varNode . children [ 0 ] . loc ,
285+ messageId : "unknownVar" ,
286+ data : { var : varNode . children [ 0 ] . name } ,
287+ } ) ;
288+ return false ;
289+ }
290+ return true ;
291+ }
292+
293+ // Handle fallback values
294+ if ( varNode . children [ 2 ] . type !== "Raw" ) {
295+ return true ;
296+ }
297+
298+ const fallbackValue = varNode . children [ 2 ] . value . trim ( ) ;
299+ const resolvedFallbackValue = resolveFallback (
300+ fallbackValue ,
301+ resolvedCache ,
302+ ) ;
303+ if ( resolvedFallbackValue ) {
304+ valueList . push ( resolvedFallbackValue ) ;
305+ valueSegmentLocs . set ( resolvedFallbackValue , varNode . loc ) ;
306+ return true ;
307+ }
308+
309+ // No valid fallback found
310+ if ( ! allowUnknownVariables ) {
311+ context . report ( {
312+ loc : varNode . children [ 0 ] . loc ,
313+ messageId : "unknownVar" ,
314+ data : { var : varNode . children [ 0 ] . name } ,
315+ } ) ;
316+ return false ;
317+ }
318+
319+ return true ;
320+ }
321+
248322 return {
249323 "Rule > Block > Declaration" ( ) {
250- replacements . push ( new Map ( ) ) ;
324+ declStack . push ( {
325+ valueParts : [ ] ,
326+ functionPartsStack : [ ] ,
327+ valueSegmentLocs : new Map ( ) ,
328+ skipValidation : false ,
329+ hadVarSubstitution : false ,
330+ /**
331+ * Cache for resolved variable values within this single declaration.
332+ * Prevents re-resolving the same variable and re-walking long `var()` chains.
333+ */
334+ resolvedCache : new Map ( ) ,
335+ } ) ;
251336 } ,
252337
253- "Function[name=/^var$/i]" ( node ) {
254- const map = replacements . at ( - 1 ) ;
255- if ( ! map ) {
338+ "Rule > Block > Declaration > Value > *:not(Function)" ( node ) {
339+ const state = declStack . at ( - 1 ) ;
340+ const text = sourceCode . getText ( node ) . trim ( ) ;
341+ state . valueParts . push ( text ) ;
342+ state . valueSegmentLocs . set ( text , node . loc ) ;
343+ } ,
344+
345+ Function ( ) {
346+ declStack . at ( - 1 ) . functionPartsStack . push ( [ ] ) ;
347+ } ,
348+
349+ "Function > *:not(Function)" ( node ) {
350+ const state = declStack . at ( - 1 ) ;
351+ const parts = state . functionPartsStack . at ( - 1 ) ;
352+ const text = sourceCode . getText ( node ) . trim ( ) ;
353+ parts . push ( text ) ;
354+ state . valueSegmentLocs . set ( text , node . loc ) ;
355+ } ,
356+
357+ "Function:exit" ( node ) {
358+ const state = declStack . at ( - 1 ) ;
359+ if ( state . skipValidation ) {
256360 return ;
257361 }
258362
259- /*
260- * Store the custom property name and the function node
261- * so can use these to validate the value later.
262- */
263- const name = node . children [ 0 ] . name ;
264- map . set ( name , node ) ;
363+ const parts = state . functionPartsStack . pop ( ) ;
364+ let result ;
365+ if ( node . name . toLowerCase ( ) === "var" ) {
366+ const resolvedParts = [ ] ;
367+ const success = processVarFunction (
368+ node ,
369+ resolvedParts ,
370+ state . valueSegmentLocs ,
371+ state . resolvedCache ,
372+ ) ;
373+
374+ if ( ! success ) {
375+ state . skipValidation = true ;
376+ return ;
377+ }
378+
379+ if ( resolvedParts . length === 0 ) {
380+ return ;
381+ }
382+
383+ state . hadVarSubstitution = true ;
384+ result = resolvedParts [ 0 ] ;
385+ } else {
386+ result = `${ node . name } (${ parts . join ( " " ) } )` ;
387+ }
388+
389+ const parentParts = state . functionPartsStack . at ( - 1 ) ;
390+ if ( parentParts ) {
391+ parentParts . push ( result ) ;
392+ } else {
393+ state . valueParts . push ( result ) ;
394+ }
265395 } ,
266396
267397 "Rule > Block > Declaration:exit" ( node ) {
398+ const state = declStack . pop ( ) ;
268399 if ( node . property . startsWith ( "--" ) ) {
269400 // store the custom property name and value to validate later
270401 vars . set ( node . property , node . value ) ;
@@ -273,106 +404,13 @@ export default {
273404 return ;
274405 }
275406
276- const varsFound = replacements . pop ( ) ;
407+ if ( state . skipValidation ) {
408+ return ;
409+ }
277410
278- /** @type {Map<string,CssLocationRange> } */
279- const valuesWithVarLocs = new Map ( ) ;
280- const usingVars = varsFound ?. size > 0 ;
281411 let value = node . value ;
282-
283- if ( usingVars ) {
284- const valueList = [ ] ;
285- /**
286- * Cache for resolved variable values within this single declaration.
287- * Prevents re-resolving the same variable and re-walking long `var()` chains.
288- * @type {Map<string,string> }
289- */
290- const resolvedCache = new Map ( ) ;
291- const valueNodes = node . value . children ;
292-
293- // When `var()` is used, we store all the values to `valueList` with the replacement of `var()` with there values or fallback values
294- for ( const child of valueNodes ) {
295- // If value is a function starts with `var()`
296- if (
297- child . type === "Function" &&
298- child . name . toLowerCase ( ) === "var"
299- ) {
300- const varValue = vars . get ( child . children [ 0 ] . name ) ;
301-
302- // If the variable is found, use its value, otherwise check for fallback values
303- if ( varValue ) {
304- const resolvedValue = resolveVariable (
305- child . children [ 0 ] . name ,
306- resolvedCache ,
307- ) ;
308- if ( resolvedValue !== null ) {
309- valueList . push ( resolvedValue ) ;
310- valuesWithVarLocs . set (
311- resolvedValue ,
312- child . loc ,
313- ) ;
314- } else {
315- if ( ! allowUnknownVariables ) {
316- context . report ( {
317- loc : child . children [ 0 ] . loc ,
318- messageId : "unknownVar" ,
319- data : {
320- var : child . children [ 0 ] . name ,
321- } ,
322- } ) ;
323- return ;
324- }
325- }
326- } else {
327- // If the variable is not found and doesn't have a fallback value, report it
328- if ( child . children . length === 1 ) {
329- if ( ! allowUnknownVariables ) {
330- context . report ( {
331- loc : child . children [ 0 ] . loc ,
332- messageId : "unknownVar" ,
333- data : {
334- var : child . children [ 0 ] . name ,
335- } ,
336- } ) ;
337-
338- return ;
339- }
340- } else {
341- // If it has a fallback value, use that
342- if ( child . children [ 2 ] . type === "Raw" ) {
343- const raw =
344- child . children [ 2 ] . value . trim ( ) ;
345- const resolvedFallbackValue =
346- resolveFallback ( raw , resolvedCache ) ;
347- if ( resolvedFallbackValue !== null ) {
348- valueList . push (
349- resolvedFallbackValue ,
350- ) ;
351- valuesWithVarLocs . set (
352- resolvedFallbackValue ,
353- child . loc ,
354- ) ;
355- } else if ( ! allowUnknownVariables ) {
356- context . report ( {
357- loc : child . children [ 0 ] . loc ,
358- messageId : "unknownVar" ,
359- data : {
360- var : child . children [ 0 ] . name ,
361- } ,
362- } ) ;
363- return ;
364- }
365- }
366- }
367- }
368- } else {
369- // If the child is not a `var()` function, just add its text to the `valueList`
370- const valueText = sourceCode . getText ( child ) . trim ( ) ;
371- valueList . push ( valueText ) ;
372- valuesWithVarLocs . set ( valueText , child . loc ) ;
373- }
374- }
375-
412+ if ( state . hadVarSubstitution ) {
413+ const valueList = state . valueParts ;
376414 value =
377415 valueList . length > 0
378416 ? valueList . join ( " " )
@@ -385,7 +423,7 @@ export default {
385423 // validation failure
386424 if ( isSyntaxMatchError ( error ) ) {
387425 const errorValue =
388- usingVars &&
426+ state . hadVarSubstitution &&
389427 value . slice (
390428 error . mismatchOffset ,
391429 error . mismatchOffset + error . mismatchLength ,
@@ -398,8 +436,8 @@ export default {
398436 * If so, use that location; otherwise, use the error's
399437 * reported location.
400438 */
401- loc : usingVars
402- ? ( valuesWithVarLocs . get ( errorValue ) ??
439+ loc : state . hadVarSubstitution
440+ ? ( state . valueSegmentLocs . get ( errorValue ) ??
403441 node . value . loc )
404442 : error . loc ,
405443 messageId : "invalidPropertyValue" ,
@@ -411,12 +449,8 @@ export default {
411449 * only include the part that caused the error.
412450 * Otherwise, use the full value from the error.
413451 */
414- value : usingVars
415- ? value . slice (
416- error . mismatchOffset ,
417- error . mismatchOffset +
418- error . mismatchLength ,
419- )
452+ value : state . hadVarSubstitution
453+ ? errorValue
420454 : error . css ,
421455 expected : error . syntax ,
422456 } ,
0 commit comments