Repository navigation
Conversation
The explicit indentation indicator emitted for a block scalar whose content starts with a space was always BestIndent. That is only correct when the scalar is indented BestIndent columns past its parent node, but this emitter indents a block sequence item's content by just 2 columns. In that case the emitted indicator was too large and the resulting document failed to parse, or lost the scalar's leading spaces. Compute the indicator from the actual difference between the scalar's indentation and its parent's indentation instead. Fixes go-yaml/yaml#1071
| // A block scalar whose content starts with a space needs an explicit | ||
| // indentation indicator, and that indicator is relative to the indentation of | ||
| // the parent node. | ||
| // See https://github.com/go-yaml/yaml/issues/1071 | ||
| func TestBlockScalarIndentIndicatorRoundTrip(t *testing.T) { | ||
| type params struct { | ||
| Description string `yaml:"description"` | ||
| } | ||
| type spec struct { | ||
| Parameters []params `yaml:"parameters"` | ||
| } | ||
|
|
||
| for _, indent := range []int{0, 1, 2, 3, 4, 8} { | ||
| for _, description := range []string{ | ||
| " a\nb", | ||
| " a\nb", | ||
| " a\n b", | ||
| } { | ||
| in := spec{Parameters: []params{{Description: description}}} | ||
|
|
||
| var buf bytes.Buffer | ||
| enc := yaml.NewEncoder(&buf) | ||
| if indent > 0 { | ||
| enc.SetIndent(indent) | ||
| } | ||
| assert.NoError(t, enc.Encode(&in)) | ||
| assert.NoError(t, enc.Close()) | ||
|
|
||
| var out spec | ||
| err := yaml.Unmarshal(buf.Bytes(), &out) | ||
| assert.NoErrorf(t, err, "indent %d, encoded as:\n%s", indent, buf.String()) | ||
| assert.DeepEqualf(t, in, out, "indent %d, encoded as:\n%s", indent, buf.String()) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
yaml/go-yaml repository chose to use a yaml test suite.
I feel like your test can be added to emitter.yaml file with roundtrip feature.
https://github.com/yaml/go-yaml/blob/main/internal%2Flibyaml%2Ftestdata%2Femitter.yaml
You will find other examples in this file.
I would appreciate if you could try to move your test there.
| parent_indent := 0 | ||
| if len(emitter.indents) > 0 && emitter.indents[len(emitter.indents)-1] > 0 { | ||
| parent_indent = emitter.indents[len(emitter.indents)-1] | ||
| } | ||
| indent := emitter.indent - parent_indent |
There was a problem hiding this comment.
Nitpicking: The fix makes sense, but the new indentation logic is a little hard to read at first glance. I think it would benefit from either a small helper or a brief explanation of why the indicator is based on the parent/current indent difference.
What do you think about the following suggestion ?
| parent_indent := 0 | |
| if len(emitter.indents) > 0 && emitter.indents[len(emitter.indents)-1] > 0 { | |
| parent_indent = emitter.indents[len(emitter.indents)-1] | |
| } | |
| indent := emitter.indent - parent_indent | |
| indent := emitter.indent | |
| if n := len(emitter.indents); n > 0 { | |
| parentIndent := emitter.indents[n-1] | |
| if parentIndent > 0 { | |
| // Subtract the parent node's indent: the explicit indicator is | |
| // relative to the parent, not absolute. | |
| indent -= parentIndent | |
| } | |
| } |
|
It sounds interesting to compare this fix to this one They seem to address the same thing |
Problem
Reported against the archived v3 repo as go-yaml/yaml#1071, and still present in v4.
Marshalling a string whose first line starts with a space, inside a block sequence item, produces a document that cannot be parsed back:
Before this change
outis:and
Unmarshalfails withdid not find expected key.Cause
writeBlockScalarHintsalways emittedBestIndentas the explicit indentation indicator. Per the YAML spec that indicator is the scalar's indentation relative to its parent node, soBestIndentis only right when the scalar is indented exactlyBestIndentcolumns past its parent.This emitter does not always do that:
increaseIndentadds only 2 columns for the content of a block sequence item (the width of the-indicator). In the example above the scalar sits 2 columns past its parent, but the emitted indicator says 4, so the parser looks for content at the wrong column.Fix
Derive the indicator from the actual difference between the scalar's indentation and its parent's indentation. The result is now:
which round-trips.
BestIndentis kept as a fallback if the difference ever falls outside the 1-9 range an indicator can express.Tests
Added
TestBlockScalarIndentIndicatorRoundTripinyaml_test.go, covering several leading-space values across indent settings 0/1/2/3/4/8.make testpasses (ALL TESTS PASS; yaml-test-suite atPASS: 1383 / FAIL: 225 (known: 225), unchanged), as domake fmtandmake lint(0 issues.).Note
I found this issue while working through open reports as part of Anthropic's "Claude for Open Source" program, and used Claude Code while diagnosing and preparing the change. I have reviewed and verified everything here myself, and I am happy to iterate on the approach or the naming if you would prefer something different. If unsolicited AI-assisted contributions are not welcome here, please just close this and accept my apologies for the noise.
Maintainers' edits
Fixes #399