Skip to content

Comment ownership

Removing a key raises a question the parse tree cannot answer: which nearby comments belonged to it?

Get it wrong in one direction and you destroy a section heading that described ten other keys. Get it wrong in the other and you leave comments describing things that no longer exist.

The problem

These two documents put the comment in the same place relative to the key below it:

server:
  host: localhost
  # the port we bind to     ← describes `port`
  port: 8080
server:
  host: localhost

  # ==== TLS SECTION ====   ← describes everything below

  tls_cert: /etc/cert.pem

What distinguishes them is layout: the blank lines. No parser can tell from the words which comment describes what, and yamldoc does not try. It reads the layout, applies a written rule, and gives every comment exactly one owner: the stream, a document, a collection, a mapping entry or a sequence item.

The rules

The rules are decisions in spec 0008 (D39 to D41, D48 to D56 and D113 to D125), with an exact fixture for each. The everyday ones:

Comment Belongs to On removal of the entry below
A block of comment lines directly above an entry, no blank line between them the entry removed with it
A block of comment lines with a blank line between it and the entry below the containing mapping, as a section comment survives, bytes untouched
On the entry's own line the entry removed with it
After the last entry of a block mapping, at the entries' indentation the mapping survives, and stays with the mapping even when it empties
Inside a flow collection after a separator the entry or item before it removed with that entry
On a --- or ... line the document survives root replacement
Before the first document, or between documents the stream survives everything

The dividing line is the blank line between comment and entry. A blank line above the comment does not detach it from the entry below; the fixture in D39 is exactly that shape. A heading you want to keep needs a blank line under it as well.

Sequence items follow the same rules with the dash line as the entry line. An entry whose value is a collection also owns a comment on the line that opens or closes that collection, so replacing the value with a scalar keeps the comment on the line.

The trailing case

server:
  host: localhost
  port: 8080
  # end of the server block
database:
  host: db.internal

A tree-based editor attaches that comment to port, because port is the last entry, and removing port takes it. Here it belongs to the mapping. Removing port leaves it in place, and removing every entry leaves the mapping's {} marker on the former child line so the comment still has a mapping to belong to.

How ownership is enforced

Every transaction records the owner of every comment before it starts, and the re-parsed result is checked against that record before it commits: each retained comment must still be there, once, with the same owner. An edit that would move a comment to a different owner as a side effect fails with ErrPreservation rather than committing, and the file is left as it was. That is why a scalar with an inline comment becomes a flow collection on the same line rather than a block one below it: the block form would hand the comment to the first new entry.

Where an edit's own footprint genuinely cannot keep every comment in place, the library refuses rather than guesses. Orphaning a comment is untidy and immediately visible; destroying one loses work the author did, silently, and they may not notice for months. That asymmetry decides the tie, and it is why the answer to an unrepresentable placement is an error and not a best effort.

When the owner a layout implies is not the one you want, say so: Move or copy a comment gives a comment a new owner as a command of its own, checked the same way.