@@ -218,9 +218,43 @@ class StencilContextAnalyzer {
218218 // Relative to current context
219219 fullPath = contextPath [ contextPath . length - 1 ] + '.' + parts . join ( '.' ) ;
220220 } else if ( original . startsWith ( '../' ) ) {
221- // Parent context reference - keep as is since it's meaningful in loop contexts
222- // e.g., {{#each products}}{{../customer}}{{/each}} should remain as ../customer
223- fullPath = original ;
221+ // Parent context reference - resolve to absolute path to unify with direct access
222+ const upLevels = ( original . match ( / \. \. \/ / g) || [ ] ) . length ;
223+
224+ // Remove the ../ prefixes from parts to get the actual property path
225+ const cleanParts = [ ...parts ] ;
226+ for ( let i = 0 ; i < upLevels ; i += 1 ) {
227+ if ( cleanParts [ 0 ] === '..' ) {
228+ cleanParts . shift ( ) ;
229+ }
230+ }
231+ const remainingPath = cleanParts . join ( '.' ) ;
232+
233+ if ( contextPath . length > upLevels ) {
234+ // Going back to a specific parent context
235+ const targetContextIndex = contextPath . length - upLevels - 1 ;
236+ const parentContext = contextPath [ targetContextIndex ] ;
237+
238+ // Extract the root path from parent context (e.g., "user.attributes[0]" -> "user")
239+ const rootPath = this . extractRootFromContext ( parentContext ) ;
240+ fullPath = remainingPath ? `${ rootPath } .${ remainingPath } ` : rootPath ;
241+ } else if ( contextPath . length === upLevels ) {
242+ // Going back one level from current context
243+ const currentContext = contextPath [ contextPath . length - 1 ] ;
244+ const parentPath = this . getParentContext ( currentContext ) ;
245+
246+ if ( parentPath && remainingPath ) {
247+ fullPath = `${ parentPath } .${ remainingPath } ` ;
248+ } else if ( parentPath ) {
249+ fullPath = parentPath ;
250+ } else {
251+ // No parent context, use remaining path as absolute
252+ fullPath = remainingPath ;
253+ }
254+ } else {
255+ // Can't resolve, treat as absolute path
256+ fullPath = remainingPath ;
257+ }
224258 } else {
225259 // Absolute path from root context
226260 fullPath = parts . join ( '.' ) ;
@@ -229,6 +263,50 @@ class StencilContextAnalyzer {
229263 return fullPath ;
230264 }
231265
266+ /**
267+ * Extracts the root path from a context string
268+ * @param {string } contextPath - Context like "user.attributes[0]" or "products[0].variants[0]"
269+ * @returns {string } Root path like "user" or "products"
270+ */
271+ extractRootFromContext ( contextPath ) {
272+ if ( ! contextPath ) return '' ;
273+
274+ // Handle array contexts like "products[0]" -> "products"
275+ const arrayMatch = contextPath . match ( / ^ ( [ ^ [ . ] + ) / ) ;
276+ if ( arrayMatch ) {
277+ return arrayMatch [ 1 ] ;
278+ }
279+
280+ // Handle nested contexts like "user.profile.settings" -> "user"
281+ const dotIndex = contextPath . indexOf ( '.' ) ;
282+ if ( dotIndex > 0 ) {
283+ return contextPath . substring ( 0 , dotIndex ) ;
284+ }
285+
286+ // Simple context
287+ return contextPath ;
288+ }
289+
290+ /**
291+ * Gets the parent context from a nested context path
292+ * @param {string } contextPath - Context like "products[0].variants[0]" or "user.attributes[0]"
293+ * @returns {string|null } Parent context like "products[0]" or null if going to root
294+ */
295+ getParentContext ( contextPath ) {
296+ if ( ! contextPath ) return null ;
297+
298+ // For nested contexts like "products[0].variants[0]", remove the last segment
299+ const lastDotIndex = contextPath . lastIndexOf ( '.' ) ;
300+ if ( lastDotIndex > 0 ) {
301+ return contextPath . substring ( 0 , lastDotIndex ) ;
302+ }
303+
304+ // For simple array contexts like "user.attributes[0]" or "products[0]",
305+ // the parent is root context (null), not the array name
306+ // This represents going back to the context before entering the #each loop
307+ return null ;
308+ }
309+
232310 /**
233311 * Records variable usage in the statistics
234312 * @param {string } variablePath - Full path of the variable
0 commit comments