Repository: goccy/go-yaml
Pull request #492 (2024-10-31 06:33:26)

Fix handling of invalid sequence
fix https://github.com/goccy/go-yaml/issues/359


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/492?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 `79.31034%` with `6 lines` in your changes missing coverage. Please review.
> Project coverage is 77.66%. Comparing base [(`af5147b`)](https://app.codecov.io/gh/goccy/go-yaml/commit/af5147b14c5efb6a793330d6faac4c7aa6decf35?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`2adff2d`)](https://app.codecov.io/gh/goccy/go-yaml/commit/2adff2df253544b774b9b1190acea9499fe6f3df?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     #492   +/-   ##
=======================================
  Coverage   77.66%   77.66%           
=======================================
  Files          18       18           
  Lines        5873     5897   +24     
=======================================
+ Hits         4561     4580   +19     
- Misses       1037     1041    +4     
- Partials      275      276    +1     
```

</details>

Patch:
diff --git a/parser/parser.go b/parser/parser.go
index 93c51e2..54f2946 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -46,6 +46,8 @@ func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) {
 	node := ast.Sequence(ctx.currentToken(), true)
 	node.SetPath(ctx.path)
 	ctx.progress(1) // skip SequenceStart token
+
+	isFirst := true
 	for ctx.next() {
 		tk := ctx.currentToken()
 		if tk.Type == token.SequenceEndType {
@@ -53,15 +55,27 @@ func (p *parser) parseSequence(ctx *context) (*ast.SequenceNode, error) {
 			break
 		} else if tk.Type == token.CollectEntryType {
 			ctx.progress(1)
-			continue
+		} else if !isFirst {
+			return nil, errors.ErrSyntax("',' or ']' must be specified", tk)
+		}
+
+		if tk := ctx.currentToken(); tk != nil && tk.Type == token.SequenceEndType {
+			// this case is here: "[ elem, ]".
+			// In this case, ignore the last element and break sequence parsing.
+			node.End = tk
+			break
 		}
 
-		value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), tk)
+		value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), ctx.currentToken())
 		if err != nil {
 			return nil, err
 		}
 		node.Values = append(node.Values, value)
 		ctx.progress(1)
+		isFirst = false
+	}
+	if node.End == nil || node.End.Type != token.SequenceEndType {
+		return nil, errors.ErrSyntax("sequence end token ']' not found", node.Start)
 	}
 	return node, nil
 }
@@ -227,7 +241,7 @@ func (p *parser) validateMapValue(ctx *context, key, value ast.Node) error {
 	}
 	ntk := ctx.nextToken()
 	if ntk == nil || (ntk.Type != token.MappingValueType && ntk.Type != token.SequenceEntryType) {
-		return errors.ErrSyntax("could not found expected ':' token", value.GetToken())
+		return errors.ErrSyntax("could not find expected ':' token", value.GetToken())
 	}
 	return nil
 }
@@ -272,8 +286,16 @@ func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) {
 
 	ntk := ctx.nextNotCommentToken()
 	antk := ctx.afterNextNotCommentToken()
-	for antk != nil && antk.Type == token.MappingValueType &&
-		ntk.Position.Column == key.GetToken().Position.Column {
+	for ntk != nil && ntk.Position.Column == key.GetToken().Position.Column {
+		if ntk.Type == token.DocumentHeaderType || ntk.Type == token.DocumentEndType {
+			break
+		}
+		if antk == nil {
+			return nil, errors.ErrSyntax("required ':' and map value", ntk)
+		}
+		if antk.Type != token.MappingValueType {
+			return nil, errors.ErrSyntax("required ':' and map value", antk)
+		}
 		ctx.progressIgnoreComment(1)
 		value, err := p.parseToken(ctx, ctx.currentToken())
 		if err != nil {
@@ -667,6 +689,10 @@ func (p *parser) createNodeFromToken(ctx *context, tk *token.Token) (ast.Node, e
 		return p.parseMapping(ctx)
 	case token.SequenceStartType:
 		return p.parseSequence(ctx)
+	case token.SequenceEndType:
+		// SequenceEndType is always validated in parseSequence.
+		// Therefore, if this is found in other cases, it is treated as a syntax error.
+		return nil, errors.ErrSyntax("could not find '[' character corresponding to ']'", tk)
 	case token.SequenceEntryType:
 		return p.parseSequenceEntry(ctx)
 	case token.AnchorType:
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 1329576..224d47b 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -649,7 +649,7 @@ func (s *Scanner) scanFlowArrayStart(ctx *Context) bool {
 }
 
 func (s *Scanner) scanFlowArrayEnd(ctx *Context) bool {
-	if s.startedFlowSequenceNum <= 0 {
+	if ctx.existsBuffer() && s.startedFlowSequenceNum <= 0 {
 		return false
 	}
 


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 5d378b9..c6e3318 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -724,6 +724,56 @@ a: |invalidopt
           ^
    3 |   foo`,
 		},
+		{
+			`
+a: 1
+b
+`,
+			`
+[3:1] required ':' and map value
+   2 | a: 1
+>  3 | b
+       ^
+`,
+		},
+		{
+			`
+a: 1
+b
+- c
+`,
+			`
+[4:1] required ':' and map value
+   2 | a: 1
+   3 | b
+>  4 | - c
+       ^
+`,
+		},
+		{
+			`a: [`,
+			`
+[1:4] sequence end token ']' not found
+>  1 | a: [
+          ^
+`,
+		},
+		{
+			`a: ]`,
+			`
+[1:4] could not find '[' character corresponding to ']'
+>  1 | a: ]
+          ^
+`,
+		},
+		{
+			`a: [ [1] [2] [3] ]`,
+			`
+[1:10] ',' or ']' must be specified
+>  1 | a: [ [1] [2] [3] ]
+                ^
+`,
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.source, func(t *testing.T) {