diff --git a/testdata/parseYaml.golden b/testdata/parseYaml.golden index a5eb13dd..0d7fb07a 100644 --- a/testdata/parseYaml.golden +++ b/testdata/parseYaml.golden @@ -54,5 +54,18 @@ ], [ null + ], + [ + { + "foo": "bar" + }, + { + "baz": "cuux" + } + ], + [ + { + "a": 1 + } ] ] diff --git a/testdata/parseYaml.jsonnet b/testdata/parseYaml.jsonnet index 9910f8f2..e5039719 100644 --- a/testdata/parseYaml.jsonnet +++ b/testdata/parseYaml.jsonnet @@ -60,5 +60,24 @@ |||, "---", + + // A leading comment before the first document-start marker must not + // produce a spurious null document (issue #660). + ||| + # Test + --- + foo: bar + --- + baz: cuux + |||, + + // Multiple leading comments and blank lines before the first marker. + ||| + # Comment 1 + # Comment 2 + + --- + a: 1 + |||, ] ] diff --git a/yaml.go b/yaml.go index a2bf3517..dc724b61 100644 --- a/yaml.go +++ b/yaml.go @@ -20,6 +20,7 @@ import ( "bufio" "bytes" "io" + "strings" "sigs.k8s.io/yaml" ) @@ -85,6 +86,21 @@ func NewYAMLReader(r *bufio.Reader) *YAMLReader { var docStartMarker = []byte("---") +// isCommentOrWhitespace reports whether all non-empty lines in b are either +// blank or YAML comments (start with '#'). Such content is valid YAML +// frontmatter/preamble that precedes the first document-start marker and does +// not constitute a document by itself. +func isCommentOrWhitespace(b []byte) bool { + for _, line := range strings.Split(string(b), "\n") { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "#") { + continue + } + return false + } + return true +} + // Read returns a full YAML document. func (r *YAMLReader) read() ([]byte, error) { for { @@ -102,6 +118,15 @@ func (r *YAMLReader) read() ([]byte, error) { if end == len(line) || line[end] == '\n' || line[end] == ' ' || line[end] == '\t' { r.stream = true if r.buffer.Len() != 0 { + // Per YAML spec ยง9.2, comments and blank lines may appear + // before the first document-start marker without forming a + // document. Discard such preamble instead of emitting a + // spurious null document. + if isCommentOrWhitespace(r.buffer.Bytes()) { + r.buffer.Reset() + r.buffer.Write(line) + continue + } out := append([]byte(nil), r.buffer.Bytes()...) r.buffer.Reset() // The document start marker should be included in the next document.