Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The `basic` formatter is the default formatter that takes the data provided, ser
| `indentless_arrays` | bool | false | Render `-` array items (block sequence items) without an increased indent. |
| `drop_merge_tag` | bool | false | Assume that any well formed merge using just a `<<` token will be a merge, and drop the `!!merge` tag from the formatted result. |
| `pad_line_comments` | int | 1 | The number of padding spaces to insert before line comments. |
| `preserve_comment_indents` | bool | false | Keep each line comment at the same column it had in the source. If the reformatted content would collid with the comment, use `pad_line_comments` spaces instead. |
| `trim_trailing_whitespace` | bool | false | Trim trailing whitespace from lines. |
| `eof_newline` | bool | false | Always add a newline at end of file. Useful in the scenario where `retain_line_breaks` is disabled but the trailing newline is still needed. |
| `strip_directives` | bool | false | [YAML Directives](https://yaml.org/spec/1.2.2/#3234-directives) are not supported by this formatter. This feature will attempt to strip the directives before formatting and put them back. [Use this feature at your own risk.](#strip_directives) |
Expand Down
1 change: 1 addition & 0 deletions formatters/basic/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Config struct {
IndentlessArrays bool `mapstructure:"indentless_arrays"`
DropMergeTag bool `mapstructure:"drop_merge_tag"`
PadLineComments int `mapstructure:"pad_line_comments"`
PreserveCommentIndents bool `mapstructure:"preserve_comment_indents"`
TrimTrailingWhitespace bool `mapstructure:"trim_trailing_whitespace"`
EOFNewline bool `mapstructure:"eof_newline"`
StripDirectives bool `mapstructure:"strip_directives"`
Expand Down
1 change: 1 addition & 0 deletions formatters/basic/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func (f *BasicFormatter) getNewEncoder(buf *bytes.Buffer) *yaml.Encoder {
e.SetIndentlessBlockSequence(f.Config.IndentlessArrays)
e.SetDropMergeTag(f.Config.DropMergeTag)
e.SetPadLineComments(f.Config.PadLineComments)
e.SetPreserveCommentIndents(f.Config.PreserveCommentIndents)

if f.Config.ArrayIndent > 0 {
e.SetArrayIndent(f.Config.ArrayIndent)
Expand Down
116 changes: 116 additions & 0 deletions formatters/basic/formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,77 @@ a:`,
input: `a: 1 # line comment`,
expect: `a: 1 # line comment`,
},
{
name: "preserve comment indents keeps manual column alignment",
config: map[string]any{
"preserve_comment_indents": true,
},
input: `steps:
- a # first
- bb # second`,
expect: `steps:
- a # first
- bb # second`,
},
{
name: "without preserve comment indents alignment collapses",
input: `steps:
- a # first
- bb # second`,
expect: `steps:
- a # first
- bb # second`,
},
{
name: "preserve comment indents falls back when content overflows original column",
config: map[string]any{
"indent": 4,
"preserve_comment_indents": true,
},
input: `a:
b: c # x`,
expect: `a:
b: c # x`,
},
{
// Content grows (indent 2 -> 4) but the '#' column (11) is still
// reachable: the column is held and the gap SHRINKS from 4 to 2.
name: "preserve comment indents holds column when content grows within reach",
config: map[string]any{
"indent": 4,
"preserve_comment_indents": true,
},
input: `a:
bb: c # x`,
expect: `a:
bb: c # x`,
},
{
// Content shrinks (indent 4 -> 2): the column (11) is held and the
// gap WIDENS from 2 to 4.
name: "preserve comment indents holds column when content shrinks",
config: map[string]any{
"indent": 2,
"preserve_comment_indents": true,
},
input: `a:
bb: c # x`,
expect: `a:
bb: c # x`,
},
{
// Overflow fallback uses the pad_line_comments floor (3), not 1.
name: "preserve comment indents fallback honors pad_line_comments",
config: map[string]any{
"indent": 4,
"pad_line_comments": 3,
"preserve_comment_indents": true,
},
input: `a:
b: c # x`,
expect: `a:
b: c # x`,
},
{
name: "trim trailing whitespace",
config: map[string]any{
Expand Down Expand Up @@ -268,6 +339,51 @@ map:
}
}

// TestPreserveCommentIndentsIdempotent guards the property lint/check mode
// relies on: formatting an already-formatted file must be a no-op. After the
// first pass each comment's '#' sits at a column the parser reads back as its
// new source column, so a second pass must produce identical bytes.
func TestPreserveCommentIndentsIdempotent(t *testing.T) {
testCases := []struct {
name string
config map[string]any
input string
}{
{
name: "aligned columns",
config: map[string]any{"preserve_comment_indents": true},
input: `steps:
- a # first
- bb # second`,
},
{
name: "content grew, gap shrank",
config: map[string]any{"indent": 4, "preserve_comment_indents": true},
input: `a:
bb: c # x`,
},
{
name: "overflow fallback",
config: map[string]any{"indent": 4, "preserve_comment_indents": true},
input: `a:
b: c # x`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
f, err := factory.NewFormatter(tc.config)
require.NoError(t, err)

once, err := f.Format([]byte(tc.input))
require.NoError(t, err)
twice, err := f.Format(once)
require.NoError(t, err)

require.Equal(t, string(once), string(twice), "second format pass changed the output")
})
}
}

func TestRetainLineBreaks(t *testing.T) {
testCases := []struct {
name string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ formatter:
line_ending: crlf
max_line_length: 0
pad_line_comments: 1
preserve_comment_indents: false
retain_line_breaks: false
retain_line_breaks_single: true
scan_folded_as_literal: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ formatter:
line_ending: lf
max_line_length: 0
pad_line_comments: 1
preserve_comment_indents: false
retain_line_breaks: true
retain_line_breaks_single: false
scan_folded_as_literal: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ formatter:
line_ending: crlf
max_line_length: 0
pad_line_comments: 1
preserve_comment_indents: false
retain_line_breaks: true
retain_line_breaks_single: true
scan_folded_as_literal: false
Expand Down
5 changes: 5 additions & 0 deletions pkg/yaml/apic.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ func yaml_emitter_set_pad_line_comments(emitter *yaml_emitter_t, pad_line_commen
emitter.pad_line_comments = pad_line_comments
}

// Set preserve comment indents.
func yaml_emitter_set_preserve_comment_indents(emitter *yaml_emitter_t, preserve bool) {
emitter.preserve_comment_indents = preserve
}

// Set correct alias keys.
func yaml_emitter_set_correct_alias_keys(emitter *yaml_emitter_t, correct_alias_keys bool) {
emitter.correct_alias_keys = correct_alias_keys
Expand Down
3 changes: 3 additions & 0 deletions pkg/yaml/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node {
n.Column = p.event.start_mark.column + 1
n.HeadComment = string(p.event.head_comment)
n.LineComment = string(p.event.line_comment)
n.LineCommentColumn = p.event.line_comment_column
n.FootComment = string(p.event.foot_comment)
}
return n
Expand Down Expand Up @@ -262,6 +263,7 @@ func (p *parser) sequence() *Node {
p.parseChild(n)
}
n.LineComment = string(p.event.line_comment)
n.LineCommentColumn = p.event.line_comment_column
n.FootComment = string(p.event.foot_comment)
p.expect(yaml_SEQUENCE_END_EVENT)
return n
Expand Down Expand Up @@ -298,6 +300,7 @@ func (p *parser) mapping() *Node {
}
}
n.LineComment = string(p.event.line_comment)
n.LineCommentColumn = p.event.line_comment_column
n.FootComment = string(p.event.foot_comment)
if n.Style&FlowStyle == 0 && n.FootComment != "" && len(n.Content) > 1 {
n.Content[len(n.Content)-2].FootComment = n.FootComment
Expand Down
19 changes: 18 additions & 1 deletion pkg/yaml/emitterc.go
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,9 @@ func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_ev
// scanner associates line comments with the value. Either way,
// save the line comment and render it appropriately later.
emitter.key_line_comment = emitter.line_comment
emitter.key_line_comment_column = emitter.line_comment_column
emitter.line_comment = nil
emitter.line_comment_column = 0
}
if yaml_emitter_check_simple_key(emitter) {
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE)
Expand Down Expand Up @@ -849,15 +851,19 @@ func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_
// so just let it handle the line comment as usual. If it has a
// line comment, we can't have both so the one from the key is lost.
emitter.line_comment = emitter.key_line_comment
emitter.line_comment_column = emitter.key_line_comment_column
emitter.key_line_comment = nil
emitter.key_line_comment_column = 0
}
} else if event.sequence_style() != yaml_FLOW_SEQUENCE_STYLE && (event.typ == yaml_MAPPING_START_EVENT || event.typ == yaml_SEQUENCE_START_EVENT) {
// An indented block follows, so write the comment right now.
emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment
emitter.line_comment_column, emitter.key_line_comment_column = emitter.key_line_comment_column, emitter.line_comment_column
if !yaml_emitter_process_line_comment(emitter) {
return false
}
emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment
emitter.line_comment_column, emitter.key_line_comment_column = emitter.key_line_comment_column, emitter.line_comment_column
}
}
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE)
Expand Down Expand Up @@ -1181,8 +1187,17 @@ func yaml_emitter_process_line_comment(emitter *yaml_emitter_t) bool {
return true
}
if !emitter.whitespace {
pad := emitter.pad_line_comments
// Restore the author's alignment if asked: keep the '#' at the column
// it had in the source.
// If the reformatted content would collide, fallsback to pad_line_comments.
if emitter.preserve_comment_indents && emitter.line_comment_column > emitter.column {
if gap := emitter.line_comment_column - emitter.column; gap > pad {
pad = gap
}
}
// Insert as many spaces before the line comment as requested.
for i := 0; i < emitter.pad_line_comments; i++ {
for i := 0; i < pad; i++ {
if !put(emitter, ' ') {
return false
}
Expand All @@ -1192,6 +1207,7 @@ func yaml_emitter_process_line_comment(emitter *yaml_emitter_t) bool {
return false
}
emitter.line_comment = emitter.line_comment[:0]
emitter.line_comment_column = 0
return true
}

Expand Down Expand Up @@ -1453,6 +1469,7 @@ func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bo
}
if len(event.line_comment) > 0 {
emitter.line_comment = event.line_comment
emitter.line_comment_column = event.line_comment_column
}
if len(event.foot_comment) > 0 {
emitter.foot_comment = event.foot_comment
Expand Down
22 changes: 13 additions & 9 deletions pkg/yaml/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ func (e *encoder) stringv(tag string, in reflect.Value) {
default:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
}
e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
e.emitScalar(s, "", tag, style, nil, nil, nil, nil, 0)
}

func (e *encoder) boolv(tag string, in reflect.Value) {
Expand All @@ -379,23 +379,23 @@ func (e *encoder) boolv(tag string, in reflect.Value) {
} else {
s = "false"
}
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil, 0)
}

func (e *encoder) intv(tag string, in reflect.Value) {
s := strconv.FormatInt(in.Int(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil, 0)
}

func (e *encoder) uintv(tag string, in reflect.Value) {
s := strconv.FormatUint(in.Uint(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil, 0)
}

func (e *encoder) timev(tag string, in reflect.Value) {
t := in.Interface().(time.Time)
s := t.Format(time.RFC3339Nano)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil, 0)
}

func (e *encoder) floatv(tag string, in reflect.Value) {
Expand All @@ -414,14 +414,14 @@ func (e *encoder) floatv(tag string, in reflect.Value) {
case "NaN":
s = ".nan"
}
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil, 0)
}

func (e *encoder) nilv() {
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil, 0)
}

func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte, lineColumn int) {
// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
implicit := tag == ""
if !implicit {
Expand All @@ -430,6 +430,7 @@ func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
e.event.head_comment = head
e.event.line_comment = line
e.event.line_comment_column = lineColumn
e.event.foot_comment = foot
e.event.tail_comment = tail
e.emit()
Expand Down Expand Up @@ -507,6 +508,7 @@ func (e *encoder) node(node *Node, tail string) {
}
e.must(yaml_sequence_end_event_initialize(&e.event))
e.event.line_comment = []byte(node.LineComment)
e.event.line_comment_column = node.LineCommentColumn
e.event.foot_comment = []byte(node.FootComment)
e.emit()

Expand Down Expand Up @@ -543,13 +545,15 @@ func (e *encoder) node(node *Node, tail string) {
yaml_mapping_end_event_initialize(&e.event)
e.event.tail_comment = []byte(tail)
e.event.line_comment = []byte(node.LineComment)
e.event.line_comment_column = node.LineCommentColumn
e.event.foot_comment = []byte(node.FootComment)
e.emit()

case AliasNode:
yaml_alias_event_initialize(&e.event, []byte(node.Value))
e.event.head_comment = []byte(node.HeadComment)
e.event.line_comment = []byte(node.LineComment)
e.event.line_comment_column = node.LineCommentColumn
e.event.foot_comment = []byte(node.FootComment)
e.emit()

Expand Down Expand Up @@ -584,7 +588,7 @@ func (e *encoder) node(node *Node, tail string) {
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
}

e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail), node.LineCommentColumn)
default:
failf("cannot encode node with unknown kind %d", node.Kind)
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/yaml/parserc.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ func yaml_parser_unfold_comments(parser *yaml_parser_t, token *yaml_token_t) {
if len(comment.line) > 0 {
if len(parser.line_comment) > 0 {
parser.line_comment = append(parser.line_comment, '\n')
} else {
// Remember the column of the first '#' so the emitter can
// restore the author's alignment.
parser.line_comment_column = comment.start_mark.column
}
parser.line_comment = append(parser.line_comment, comment.line...)
}
Expand Down Expand Up @@ -422,9 +426,11 @@ func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t)
func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t) {
event.head_comment = parser.head_comment
event.line_comment = parser.line_comment
event.line_comment_column = parser.line_comment_column
event.foot_comment = parser.foot_comment
parser.head_comment = nil
parser.line_comment = nil
parser.line_comment_column = 0
parser.foot_comment = nil
parser.tail_comment = nil
parser.stem_comment = nil
Expand Down
Loading