@@ -29,6 +29,7 @@ import { isValidColor, isParseableDimension, isTokenReference, parseDimensionPar
2929import { parseCssColor } from './color-parser.js' ;
3030
3131const MAX_REFERENCE_DEPTH = 10 ;
32+ const MAX_TOKEN_NESTING_DEPTH = 20 ;
3233
3334const SCHEMA_KEY_SET : ReadonlySet < string > = new Set ( SCHEMA_KEYS ) ;
3435
@@ -68,7 +69,7 @@ export class ModelHandler implements ModelSpec {
6869 // Store as-is for fallback
6970 symbolTable . set ( `colors.${ name } ` , raw ) ;
7071 }
71- } ) ;
72+ } , '' , 0 , findings , 'colors' ) ;
7273 }
7374
7475 // Typography
@@ -106,7 +107,7 @@ export class ModelHandler implements ModelSpec {
106107 symbolTable . set ( `rounded.${ name } ` , raw ) ;
107108 }
108109 }
109- } ) ;
110+ } , '' , 0 , findings , 'rounded' ) ;
110111 }
111112
112113 // Spacing
@@ -119,7 +120,7 @@ export class ModelHandler implements ModelSpec {
119120 } else {
120121 symbolTable . set ( `spacing.${ name } ` , raw ) ;
121122 }
122- } ) ;
123+ } , '' , 0 , findings , 'spacing' ) ;
123124 }
124125
125126 // ── Phase 2: Resolve chained color references ──────────────────
@@ -396,11 +397,31 @@ export function contrastRatio(a: ResolvedColor, b: ResolvedColor): number {
396397 * Recursively iterate over an object and call a function for each leaf node.
397398 * Leaf node paths are dot-separated (e.g. "background.light").
398399 */
399- function forEachLeaf ( obj : Record < string , any > , fn : ( path : string , value : any ) => void , prefix = '' ) {
400+ function forEachLeaf (
401+ obj : Record < string , any > ,
402+ fn : ( path : string , value : any ) => void ,
403+ prefix = '' ,
404+ depth = 0 ,
405+ findings ?: Finding [ ] ,
406+ rootPath ?: string
407+ ) {
408+ if ( depth > MAX_TOKEN_NESTING_DEPTH ) {
409+ if ( findings && rootPath ) {
410+ // Check if we've already reported this rootPath to avoid spamming
411+ if ( ! findings . some ( ( f ) => f . path === rootPath && f . message . includes ( 'nesting depth' ) ) ) {
412+ findings . push ( {
413+ severity : 'error' ,
414+ path : rootPath ,
415+ message : `Token nesting depth exceeds maximum allowed depth of ${ MAX_TOKEN_NESTING_DEPTH } .` ,
416+ } ) ;
417+ }
418+ }
419+ return ;
420+ }
400421 for ( const [ key , value ] of Object . entries ( obj ) ) {
401422 const fullPath = prefix ? `${ prefix } .${ key } ` : key ;
402423 if ( value !== null && typeof value === 'object' && ! Array . isArray ( value ) ) {
403- forEachLeaf ( value , fn , fullPath ) ;
424+ forEachLeaf ( value , fn , fullPath , depth + 1 , findings , rootPath ) ;
404425 } else {
405426 fn ( fullPath , value ) ;
406427 }
0 commit comments