@@ -57,11 +57,20 @@ export function extractSmartValue(parsed: ParsedSource, path: string): SmartCell
5757
5858const PATH_TOKEN_RE = / \. ( [ ^ . [ \] ] + ) | \[ ( \d + ) \] | \[ ' ( [ ^ ' ] * ) ' \] | \[ " ( [ ^ " ] * ) " \] / g;
5959
60+ type PathToken =
61+ | { kind : "dot" ; key : string }
62+ | { kind : "key" ; key : string }
63+ | { kind : "index" ; index : number } ;
64+
6065/**
6166 * Read a value out of a parsed object with dot/bracket notation. Accepts a
6267 * leading `$`, dotted keys, and numeric or quoted bracket indices, e.g.
6368 * `$.failed`, `suites[0].name`, `$['a.b'].c`. Returns undefined when any
6469 * segment is missing.
70+ *
71+ * A dot-accessed `.length` is computed: array/string length, or an object's
72+ * key count. To read a real property literally named `length`, use a bracket
73+ * key (`['length']`).
6574 */
6675export function getAtPath ( root : unknown , path : string ) : unknown {
6776 let normalized = path . trim ( ) ;
@@ -71,26 +80,39 @@ export function getAtPath(root: unknown, path: string): unknown {
7180 normalized = `.${ normalized } ` ;
7281 }
7382
74- const tokens : ( string | number ) [ ] = [ ] ;
83+ const tokens : PathToken [ ] = [ ] ;
7584 let lastIndex = 0 ;
7685 PATH_TOKEN_RE . lastIndex = 0 ;
7786 let match : RegExpExecArray | null ;
7887 while ( ( match = PATH_TOKEN_RE . exec ( normalized ) ) !== null ) {
7988 if ( match . index !== lastIndex ) return undefined ;
8089 lastIndex = PATH_TOKEN_RE . lastIndex ;
8190
82- if ( match [ 1 ] !== undefined ) tokens . push ( match [ 1 ] ) ;
83- else if ( match [ 2 ] !== undefined ) tokens . push ( Number ( match [ 2 ] ) ) ;
84- else if ( match [ 3 ] !== undefined ) tokens . push ( match [ 3 ] ) ;
85- else if ( match [ 4 ] !== undefined ) tokens . push ( match [ 4 ] ) ;
91+ if ( match [ 1 ] !== undefined ) tokens . push ( { kind : "dot" , key : match [ 1 ] } ) ;
92+ else if ( match [ 2 ] !== undefined ) tokens . push ( { kind : "index" , index : Number ( match [ 2 ] ) } ) ;
93+ else if ( match [ 3 ] !== undefined ) tokens . push ( { kind : "key" , key : match [ 3 ] } ) ;
94+ else if ( match [ 4 ] !== undefined ) tokens . push ( { kind : "key" , key : match [ 4 ] } ) ;
8695 }
8796 if ( lastIndex !== normalized . length ) return undefined ;
8897
8998 let current : unknown = root ;
9099 for ( const token of tokens ) {
91100 if ( current === null || current === undefined ) return undefined ;
101+
102+ if ( token . kind === "dot" && token . key === "length" ) {
103+ if ( Array . isArray ( current ) || typeof current === "string" ) {
104+ current = current . length ;
105+ } else if ( typeof current === "object" ) {
106+ current = Object . keys ( current ) . length ;
107+ } else {
108+ return undefined ;
109+ }
110+ continue ;
111+ }
112+
92113 if ( typeof current !== "object" ) return undefined ;
93- current = ( current as Record < string | number , unknown > ) [ token ] ;
114+ const key = token . kind === "index" ? token . index : token . key ;
115+ current = ( current as Record < string | number , unknown > ) [ key ] ;
94116 }
95117 return current ;
96118}
0 commit comments