88 isAlias ,
99 isCollection ,
1010 isPair ,
11+ isScalar ,
1112 Node ,
1213 NodeBase ,
1314 Range
@@ -24,6 +25,8 @@ export declare namespace Alias {
2425 }
2526}
2627
28+ const RESOLVE = Symbol ( '_resolve' )
29+
2730export class Alias extends NodeBase {
2831 source : string
2932
@@ -39,46 +42,72 @@ export class Alias extends NodeBase {
3942 } )
4043 }
4144
42- /**
43- * Resolve the value of this alias within `doc`, finding the last
44- * instance of the `source` anchor before this node.
45- */
46- resolve ( doc : Document ) : Scalar | YAMLMap | YAMLSeq | undefined {
45+ [ RESOLVE ] ( doc : Document ) {
4746 let found : Scalar | YAMLMap | YAMLSeq | undefined = undefined
47+ // @ts -expect-error - TS doesn't notice the assignment in the visitor
48+ let root : Node & { anchor : string } = undefined
49+ const pathLike = this . source . includes ( '/' )
4850 visit ( doc , {
4951 Node : ( _key : unknown , node : Node ) => {
5052 if ( node === this ) return visit . BREAK
51- if ( node . anchor === this . source ) found = node
53+ const { anchor } = node
54+ if ( anchor === this . source ) {
55+ found = node
56+ } else if (
57+ doc . directives ?. yaml . version === 'next' &&
58+ anchor &&
59+ pathLike &&
60+ this . source . startsWith ( anchor + '/' ) &&
61+ ( ! root || root . anchor . length <= anchor . length )
62+ ) {
63+ root = node as Node & { anchor : string }
64+ }
5265 }
5366 } )
54- return found
67+ if ( found ) return { node : found , root : found }
68+
69+ if ( isCollection ( root ) ) {
70+ const parts = this . source . substring ( root . anchor . length + 1 ) . split ( '/' )
71+ const node = root . getIn ( parts , true )
72+ if ( isCollection ( node ) || isScalar ( node ) ) return { node, root }
73+ }
74+
75+ return { node : undefined , root }
76+ }
77+
78+ /**
79+ * Resolve the value of this alias within `doc`, finding the last
80+ * instance of the `source` anchor before this node.
81+ */
82+ resolve ( doc : Document ) : Scalar | YAMLMap | YAMLSeq | undefined {
83+ return this [ RESOLVE ] ( doc ) . node
5584 }
5685
5786 toJSON ( _arg ?: unknown , ctx ?: ToJSContext ) {
5887 if ( ! ctx ) return { source : this . source }
59- const { anchors, doc, maxAliasCount } = ctx
60- const source = this . resolve ( doc )
61- if ( ! source ) {
88+ const { anchors, doc, maxAliasCount, resolved } = ctx
89+ const { node , root } = this [ RESOLVE ] ( doc )
90+ if ( ! node ) {
6291 const msg = `Unresolved alias (the anchor must be set before the alias): ${ this . source } `
6392 throw new ReferenceError ( msg )
6493 }
65- const data = anchors . get ( source )
66- /* istanbul ignore if */
67- if ( ! data || data . res === undefined ) {
68- const msg = 'This should not happen: Alias anchor was not resolved?'
69- throw new ReferenceError ( msg )
70- }
94+
7195 if ( maxAliasCount >= 0 ) {
96+ const data = anchors . get ( root )
97+ if ( ! data ) {
98+ const msg = 'This should not happen: Alias anchor was not resolved?'
99+ throw new ReferenceError ( msg )
100+ }
72101 data . count += 1
73- if ( data . aliasCount === 0 )
74- data . aliasCount = getAliasCount ( doc , source , anchors )
102+ data . aliasCount ||= getAliasCount ( doc , root , anchors )
75103 if ( data . count * data . aliasCount > maxAliasCount ) {
76104 const msg =
77105 'Excessive alias count indicates a resource exhaustion attack'
78106 throw new ReferenceError ( msg )
79107 }
80108 }
81- return data . res
109+
110+ return resolved . get ( node )
82111 }
83112
84113 toString (
@@ -105,8 +134,8 @@ function getAliasCount(
105134 anchors : ToJSContext [ 'anchors' ]
106135) : number {
107136 if ( isAlias ( node ) ) {
108- const source = node . resolve ( doc )
109- const anchor = anchors && source && anchors . get ( source )
137+ const { root } = node [ RESOLVE ] ( doc )
138+ const anchor = root && anchors ? .get ( root )
110139 return anchor ? anchor . count * anchor . aliasCount : 0
111140 } else if ( isCollection ( node ) ) {
112141 let count = 0
0 commit comments