Repository: goccy/go-yaml
Pull request #579 (2024-12-09 12:30:08)

Fix parsing of multiline mapping key
SSIA

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/579?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 `84.61538%` with `6 lines` in your changes missing coverage. Please review.
> Project coverage is 77.42%. Comparing base [(`438f2d0`)](https://app.codecov.io/gh/goccy/go-yaml/commit/438f2d0c3786d61fe505dfbcb6998b0103cb906d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`bdbf1fa`)](https://app.codecov.io/gh/goccy/go-yaml/commit/bdbf1fa4b4ec6adb495637e8cc008b79592faec6?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     #579      +/-   ##
==========================================
+ Coverage   77.39%   77.42%   +0.03%     
==========================================
  Files          21       21              
  Lines        7630     7650      +20     
==========================================
+ Hits         5905     5923      +18     
- Misses       1318     1321       +3     
+ Partials      407      406       -1     
```

</details>

Patch:
diff --git a/parser/parser.go b/parser/parser.go
index b2367eb..959e366 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -241,9 +241,9 @@ func (p *parser) parseToken(ctx *context, tk *Token) (ast.Node, error) {
 	case token.TagType:
 		return p.parseTag(ctx)
 	case token.MappingStartType:
-		return p.parseFlowMap(ctx)
+		return p.parseFlowMap(ctx.withFlow(true))
 	case token.SequenceStartType:
-		return p.parseFlowSequence(ctx)
+		return p.parseFlowSequence(ctx.withFlow(true))
 	case token.SequenceEntryType:
 		return p.parseSequence(ctx)
 	case token.SequenceEndType:
@@ -588,7 +588,7 @@ func (p *parser) parseMapKey(ctx *context, g *TokenGroup) (ast.MapKeyNode, error
 		keyText := p.mapKeyText(scalar)
 		keyPath := ctx.withChild(keyText).path
 		key.SetPath(keyPath)
-		if err := p.validateMapKey(key.GetToken(), keyPath); err != nil {
+		if err := p.validateMapKey(ctx, key.GetToken(), keyPath, g.Last()); err != nil {
 			return nil, err
 		}
 		p.pathMap[keyPath] = key
@@ -609,14 +609,14 @@ func (p *parser) parseMapKey(ctx *context, g *TokenGroup) (ast.MapKeyNode, error
 	keyText := p.mapKeyText(key)
 	keyPath := ctx.withChild(keyText).path
 	key.SetPath(keyPath)
-	if err := p.validateMapKey(key.GetToken(), keyPath); err != nil {
+	if err := p.validateMapKey(ctx, key.GetToken(), keyPath, g.Last()); err != nil {
 		return nil, err
 	}
 	p.pathMap[keyPath] = key
 	return key, nil
 }
 
-func (p *parser) validateMapKey(tk *token.Token, keyPath string) error {
+func (p *parser) validateMapKey(ctx *context, tk *token.Token, keyPath string, colonTk *Token) error {
 	if !p.allowDuplicateMapKey {
 		if n, exists := p.pathMap[keyPath]; exists {
 			pos := n.GetToken().Position
@@ -626,29 +626,57 @@ func (p *parser) validateMapKey(tk *token.Token, keyPath string) error {
 			)
 		}
 	}
-	if tk.Type != token.StringType {
+	origin := p.removeLeftWhiteSpace(tk.Origin)
+	if ctx.isFlow {
+		if tk.Type == token.StringType {
+			origin = p.removeRightWhiteSpace(origin)
+			if tk.Position.Line+p.newLineCharacterNum(origin) != colonTk.Line() {
+				return errors.ErrSyntax("map key definition includes an implicit line break", tk)
+			}
+		}
+		return nil
+	}
+	if tk.Type != token.StringType && tk.Type != token.SingleQuoteType && tk.Type != token.DoubleQuoteType {
 		return nil
 	}
-	origin := p.removeLeftSideNewLineCharacter(tk.Origin)
 	if p.existsNewLineCharacter(origin) {
 		return errors.ErrSyntax("unexpected key name", tk)
 	}
 	return nil
 }
 
-func (p *parser) removeLeftSideNewLineCharacter(src string) string {
+func (p *parser) removeLeftWhiteSpace(src string) string {
+	// CR or LF or CRLF
+	return strings.TrimLeftFunc(src, func(r rune) bool {
+		return r == ' ' || r == '\r' || r == '\n'
+	})
+}
+
+func (p *parser) removeRightWhiteSpace(src string) string {
 	// CR or LF or CRLF
-	return strings.TrimLeft(strings.TrimLeft(strings.TrimLeft(src, "\r"), "\n"), "\r\n")
+	return strings.TrimRightFunc(src, func(r rune) bool {
+		return r == ' ' || r == '\r' || r == '\n'
+	})
 }
 
 func (p *parser) existsNewLineCharacter(src string) bool {
-	if strings.Index(src, "\n") > 0 {
-		return true
-	}
-	if strings.Index(src, "\r") > 0 {
-		return true
+	return p.newLineCharacterNum(src) > 0
+}
+
+func (p *parser) newLineCharacterNum(src string) int {
+	var num int
+	for i := 0; i < len(src); i++ {
+		switch src[i] {
+		case '\r':
+			if len(src) > i+1 && src[i+1] == '\n' {
+				i++
+			}
+			num++
+		case '\n':
+			num++
+		}
 	}
-	return false
+	return num
 }
 
 func (p *parser) mapKeyText(n ast.Node) string {
@@ -900,7 +928,7 @@ func (p *parser) parseTagValue(ctx *context, tagRawTk *token.Token, tk *Token) (
 			return nil, errors.ErrSyntax("could not find map", tk.RawToken())
 		}
 		if tk.Type() == token.MappingStartType {
-			return p.parseFlowMap(ctx)
+			return p.parseFlowMap(ctx.withFlow(true))
 		}
 		return p.parseMap(ctx)
 	case token.IntegerTag, token.FloatTag, token.StringTag, token.BinaryTag, token.TimestampTag, token.BooleanTag, token.NullTag:
@@ -917,7 +945,7 @@ func (p *parser) parseTagValue(ctx *context, tagRawTk *token.Token, tk *Token) (
 		return scalar, nil
 	case token.SequenceTag, token.OrderedMapTag:
 		if tk.Type() == token.SequenceStartType {
-			return p.parseFlowSequence(ctx)
+			return p.parseFlowSequence(ctx.withFlow(true))
 		}
 		return p.parseSequence(ctx)
 	}
@@ -956,7 +984,7 @@ func (p *parser) parseFlowSequence(ctx *context) (*ast.SequenceNode, error) {
 			break
 		}
 
-		value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))).withFlow(true), ctx.currentToken())
+		value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))), ctx.currentToken())
 		if err != nil {
 			return nil, err
 		}


Test patch:
diff --git a/yaml_test_suite_test.go b/yaml_test_suite_test.go
index 16cdeab..187b39f 100644
--- a/yaml_test_suite_test.go
+++ b/yaml_test_suite_test.go
@@ -37,21 +37,17 @@ var failureTestNames = []string{
 	"invalid-comment-after-comma",
 	"invalid-comment-after-end-of-flow-sequence",
 	"invalid-comma-in-tag",
-	"invalid-tag",                                // pass yamlv3.
-	"legal-tab-after-indentation",                // pass yamlv3.
-	"literal-modifers/00",                        // pass yamlv3.
-	"literal-modifers/01",                        // pass yamlv3.
-	"literal-modifers/02",                        // pass yamlv3.
-	"literal-modifers/03",                        // pass yamlv3.
-	"literal-scalars",                            // pass yamlv3.
-	"mapping-key-and-flow-sequence-item-anchors", // no json.
-	"multiline-double-quoted-implicit-keys",      // pass yamlv3.
-	"multiline-plain-flow-mapping-key",
+	"invalid-tag",                                    // pass yamlv3.
+	"legal-tab-after-indentation",                    // pass yamlv3.
+	"literal-modifers/00",                            // pass yamlv3.
+	"literal-modifers/01",                            // pass yamlv3.
+	"literal-modifers/02",                            // pass yamlv3.
+	"literal-modifers/03",                            // pass yamlv3.
+	"literal-scalars",                                // pass yamlv3.
+	"mapping-key-and-flow-sequence-item-anchors",     // no json.
 	"multiline-plain-value-with-tabs-on-empty-lines", // pass yamlv3.
 	"multiline-scalar-at-top-level",                  // pass yamlv3.
 	"multiline-scalar-at-top-level-1-3",              // pass yamlv3.
-	"multiline-single-quoted-implicit-keys",          // pass yamlv3.
-	"multiline-unidented-double-quoted-block-key",    // pass yamlv3.
 	"nested-implicit-complex-keys",                   // no json.
 	"node-anchor-not-indented",                       // pass yamlv3.
 	"plain-dashes-in-flow-sequence",