Repository: goccy/go-yaml
Pull request #489 (2024-10-30 10:22:54)

Support recursive anchor
Supports recursive anchor-alias definition, first evaluating to `null`.

fixes https://github.com/goccy/go-yaml/issues/488 https://github.com/goccy/go-yaml/issues/353
fixes https://github.com/goccy/go-yaml/issues/464 : This issue has already been fixed with invalid token feature.

codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/goccy/go-yaml/pull/489?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 77.64%. Comparing base [(`c9f75fe`)](https://app.codecov.io/gh/goccy/go-yaml/commit/c9f75fef0c3f2f9b35366c730148c07c6e08bab2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`f21b15a`)](https://app.codecov.io/gh/goccy/go-yaml/commit/f21b15a695042124d12329f3a85b18cf7d66efa8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima).

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #489      +/-   ##
==========================================
+ Coverage   77.61%   77.64%   +0.03%     
==========================================
  Files          18       18              
  Lines        5860     5870      +10     
==========================================
+ Hits         4548     4558      +10     
  Misses       1038     1038              
  Partials      274      274              
```

</details>

Patch:
diff --git a/decode.go b/decode.go
index 95f7217..9ff9f3e 100644
--- a/decode.go
+++ b/decode.go
@@ -26,6 +26,7 @@ type Decoder struct {
 	reader               io.Reader
 	referenceReaders     []io.Reader
 	anchorNodeMap        map[string]ast.Node
+	aliasValueMap        map[*ast.AliasNode]any
 	anchorValueMap       map[string]reflect.Value
 	customUnmarshalerMap map[reflect.Type]func(interface{}, []byte) error
 	toCommentMap         CommentMap
@@ -48,6 +49,7 @@ func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder {
 	return &Decoder{
 		reader:               r,
 		anchorNodeMap:        map[string]ast.Node{},
+		aliasValueMap:        make(map[*ast.AliasNode]any),
 		anchorValueMap:       map[string]reflect.Value{},
 		customUnmarshalerMap: map[reflect.Type]func(interface{}, []byte) error{},
 		opts:                 opts,
@@ -301,9 +303,19 @@ func (d *Decoder) nodeToValue(node ast.Node) interface{} {
 		d.anchorNodeMap[anchorName] = n.Value
 		return anchorValue
 	case *ast.AliasNode:
+		if v, exists := d.aliasValueMap[n]; exists {
+			return v
+		}
+		// To handle the case where alias is processed recursively, the result of alias can be set to nil in advance.
+		d.aliasValueMap[n] = nil
+
 		aliasName := n.Value.GetToken().Value
 		node := d.anchorNodeMap[aliasName]
-		return d.nodeToValue(node)
+		aliasValue := d.nodeToValue(node)
+
+		// once the correct alias value is obtained, overwrite with that value.
+		d.aliasValueMap[n] = aliasValue
+		return aliasValue
 	case *ast.LiteralNode:
 		return n.Value.GetValue()
 	case *ast.MappingKeyNode:


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 26a7901..36dd3a0 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -948,7 +948,21 @@ func TestDecoder(t *testing.T) {
 			"a: &a [1, 2]\nb: *a\n",
 			struct{ B []int }{[]int{1, 2}},
 		},
-
+		{
+			"&0: *0\n*0:\n*0:",
+			map[string]any{"null": nil},
+		},
+		{
+			"key1: &anchor\n  subkey: *anchor\nkey2: *anchor\n",
+			map[string]any{
+				"key1": map[string]any{
+					"subkey": nil,
+				},
+				"key2": map[string]any{
+					"subkey": nil,
+				},
+			},
+		},
 		{
 			"tags:\n- hello-world\na: foo",
 			struct {
diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go
index b6cc87a..3c9428f 100644
--- a/lexer/lexer_test.go
+++ b/lexer/lexer_test.go
@@ -2400,7 +2400,7 @@ func TestInvalid(t *testing.T) {
 		src  string
 	}{
 		{
-			name: "literal opt",
+			name: "literal opt with content",
 			src: `
 a: |invalid
   foo`,