Repository: goccy/go-yaml
Pull request #505 (2024-11-05 04:23:32)

Fix flow map node
fix https://github.com/goccy/go-yaml/issues/348

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/505?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) Report
Attention: Patch coverage is `85.71429%` with `8 lines` in your changes missing coverage. Please review.
> Project coverage is 76.81%. Comparing base [(`2e63415`)](https://app.codecov.io/gh/goccy/go-yaml/commit/2e63415e8147fc273d87ac9e91c3a05c6069a8e5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`73824f6`)](https://app.codecov.io/gh/goccy/go-yaml/commit/73824f6903b9b04a47dbf49012960f4579881585?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     #505      +/-   ##
==========================================
- Coverage   76.87%   76.81%   -0.07%     
==========================================
  Files          18       18              
  Lines        6141     6193      +52     
==========================================
+ Hits         4721     4757      +36     
- Misses       1112     1123      +11     
- Partials      308      313       +5     
```

</details>

Patch:
diff --git a/parser/context.go b/parser/context.go
index 39b23cb..cc7d302 100644
--- a/parser/context.go
+++ b/parser/context.go
@@ -7,7 +7,8 @@ import (
 
 // context context at parsing
 type context struct {
-	path string
+	path   string
+	isFlow bool
 }
 
 var pathSpecialChars = []string{
@@ -42,6 +43,12 @@ func (c *context) withIndex(idx uint) *context {
 	return &ctx
 }
 
+func (c *context) withFlow(isFlow bool) *context {
+	ctx := *c
+	ctx.isFlow = isFlow
+	return &ctx
+}
+
 func newContext() *context {
 	return &context{
 		path: "$",
diff --git a/parser/parser.go b/parser/parser.go
index 5d7bfb3..65bff39 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -140,17 +140,27 @@ func (p *parser) parseMapping(ctx *context) (*ast.MappingNode, error) {
 	node := ast.Mapping(mapTk, true)
 	node.SetPath(ctx.path)
 	p.progress(1) // skip MappingStart token
+
+	isFirst := true
 	for p.next() {
 		tk := p.currentToken()
 		if tk.Type == token.MappingEndType {
 			node.End = tk
-			return node, nil
+			break
 		} else if tk.Type == token.CollectEntryType {
 			p.progress(1)
-			continue
+		} else if !isFirst {
+			return nil, errors.ErrSyntax("',' or '}' must be specified", tk)
+		}
+
+		if tk := p.currentToken(); tk != nil && tk.Type == token.MappingEndType {
+			// this case is here: "{ elem, }".
+			// In this case, ignore the last element and break mapping parsing.
+			node.End = tk
+			break
 		}
 
-		value, err := p.parseMappingValue(ctx)
+		value, err := p.parseMappingValue(ctx.withFlow(true))
 		if err != nil {
 			return nil, err
 		}
@@ -160,8 +170,12 @@ func (p *parser) parseMapping(ctx *context) (*ast.MappingNode, error) {
 		}
 		node.Values = append(node.Values, mvnode)
 		p.progress(1)
+		isFirst = false
+	}
+	if node.End == nil || node.End.Type != token.MappingEndType {
+		return nil, errors.ErrSyntax("could not find flow mapping end token '}'", node.Start)
 	}
-	return nil, errors.ErrSyntax("unterminated flow mapping", node.GetToken())
+	return node, nil
 }
 
 func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) {
@@ -382,12 +396,32 @@ func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) {
 	if err := p.validateMapKey(key.GetToken()); err != nil {
 		return nil, err
 	}
-	p.progress(1)          // progress to mapping value token
-	tk := p.currentToken() // get mapping value token
+	p.progress(1) // progress to mapping value token
+	if ctx.isFlow {
+		// if "{key}" or "{key," style, returns MappingValueNode.
+		node, err := p.parseFlowMapNullValue(ctx, key)
+		if err != nil {
+			return nil, err
+		}
+		if node != nil {
+			return node, nil
+		}
+	}
+	tk := p.currentToken() // get mapping value (':') token.
 	if tk == nil {
 		return nil, errors.ErrSyntax("unexpected map", key.GetToken())
 	}
 	p.progress(1) // progress to value token
+	if ctx.isFlow {
+		// if "{key:}" or "{key:," style, returns MappingValueNode.
+		node, err := p.parseFlowMapNullValue(ctx, key)
+		if err != nil {
+			return nil, err
+		}
+		if node != nil {
+			return node, nil
+		}
+	}
 	if err := p.setSameLineCommentIfExists(ctx.withChild(keyText), key); err != nil {
 		return nil, err
 	}
@@ -474,6 +508,25 @@ func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) {
 	return node, nil
 }
 
+func (p *parser) parseFlowMapNullValue(ctx *context, key ast.MapKeyNode) (*ast.MappingValueNode, error) {
+	tk := p.currentToken()
+	if tk == nil {
+		return nil, errors.ErrSyntax("unexpected map", key.GetToken())
+	}
+	if tk.Type != token.MappingEndType && tk.Type != token.CollectEntryType {
+		return nil, nil
+	}
+	nullTk := p.createNullToken(tk)
+	p.insertToken(p.idx, nullTk)
+	value, err := p.parseToken(ctx, nullTk)
+	if err != nil {
+		return nil, err
+	}
+	node := ast.MappingValue(tk, key, value)
+	node.SetPath(ctx.withChild(key.GetToken().Value).path)
+	return node, nil
+}
+
 func (p *parser) parseSequenceEntry(ctx *context) (*ast.SequenceNode, error) {
 	tk := p.currentToken()
 	sequenceNode := ast.Sequence(tk, false)


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 7adfe77..ae2fe63 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -94,6 +94,8 @@ func TestParser(t *testing.T) {
 		"value: >\nother:",
 		"value: >\n\nother:",
 		"a:\n-",
+		"a: {foo}",
+		"a: {foo,bar}",
 	}
 	for _, src := range sources {
 		if _, err := parser.Parse(lexer.Tokenize(src), 0); err != nil {
@@ -968,7 +970,7 @@ a
 		{
 			`{ "key": "value" `,
 			`
-[1:1] unterminated flow mapping
+[1:1] could not find flow mapping end token '}'
 >  1 | { "key": "value"
        ^
 `,