Skip to content

refactor(yaml): accept a list of properties#9220

Merged
l0ngvh merged 1 commit intobiomejs:mainfrom
l0ngvh:refactor-yaml-property-schema
Feb 26, 2026
Merged

refactor(yaml): accept a list of properties#9220
l0ngvh merged 1 commit intobiomejs:mainfrom
l0ngvh:refactor-yaml-property-schema

Conversation

@l0ngvh
Copy link
Copy Markdown
Contributor

@l0ngvh l0ngvh commented Feb 24, 2026

Summary

Our current YAML grammar enforces that there can only be at most 2 properties, which causes difficulties in parsing the properties. This PR changes the grammar to accept a list of properties. Checking the number of properties will belong to a syntax rule.
This PR also moves the property outside of block nodes, i.e. outside of the MAPPING_START/MAPPING_END pair, as from a parser's perspective, there is no way to disambiguate between properties of block maps and properties of a key of the block maps as both are inside MAPPING_START/MAPPING_END pair.

This PR is rather large due to being a grammar change. However, most of the changes are no-op, and it contains only the minimal changes that allow it to pass compilation and tests. The actual lexing and parsing the properties will belong to a future PR.

This is part of #8570, split for easier review.

Test Plan

The new test snapshot should only contain changes in the grammar

Docs

N/A

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 24, 2026

⚠️ No Changeset found

Latest commit: 26c133a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added A-Parser Area: parser A-Tooling Area: internal tools labels Feb 24, 2026
@l0ngvh l0ngvh force-pushed the refactor-yaml-property-schema branch from 1a4d8ce to 8d88dc3 Compare February 24, 2026 04:56
@l0ngvh l0ngvh force-pushed the refactor-yaml-property-schema branch from 8d88dc3 to 26c133a Compare February 24, 2026 05:17
@github-actions github-actions Bot added the A-Formatter Area: formatter label Feb 24, 2026
@l0ngvh l0ngvh marked this pull request as ready for review February 24, 2026 05:37
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

Walkthrough

This pull request refactors the YAML formatter and parser to reorganise how properties are handled in YAML block and flow nodes. The main changes involve replacing AnyYamlPropertiesCombination with a dedicated YamlPropertyList type, introducing a new YamlBlockInBlockNode with an explicit properties field, and renaming AnyYamlBlockInBlockNode to AnyYamlBlockInBlockContent. The formatter implementations are updated accordingly, with deprecated property-specific formatters removed and new formatter rules created for the restructured nodes. The parser is modified to parse property lists at the block and flow levels, and the grammar is updated to reflect the new type structure.

Possibly related PRs

Suggested reviewers

  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor(yaml): accept a list of properties' clearly and concisely describes the main change: converting the YAML schema from enforcing at most two properties to accepting a dynamic list of properties.
Description check ✅ Passed The PR description clearly explains the grammar changes: accepting a list of properties instead of at most 2, moving properties outside block nodes, and deferring parsing implementation to future PRs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/biome_yaml_parser/src/parser/property.rs (1)

21-39: recover is unreachable today; the empty token_set![] is a trap for the follow-up PR.

Because is_at_list_end always returns true, parse_element and recover are never invoked — the list immediately completes empty. That's the correct no-op behaviour for now.

However, when parse_element is fleshed out, is_at_list_end must be updated at the same time. If is_at_list_end starts returning false while recover still holds token_set![], there are no synchronisation tokens for recovery to latch onto — which can stall the parser on malformed input. Consider adding a comment alongside token_set![] to signal this dependency, or use a sentinel set (e.g., the property-terminating tokens) even now.

💡 Suggested guard comment
 fn recover(
     &mut self,
     p: &mut Self::Parser<'_>,
     parsed_element: ParsedSyntax,
 ) -> biome_parser::parse_recovery::RecoveryResult {
+    // TODO: replace token_set![] with actual recovery tokens (e.g. content-start tokens)
+    // when parse_element is implemented. An empty set means no sync point is available.
     parsed_element.or_recover_with_token_set(
         p,
         &ParseRecoveryTokenSet::new(YAML_BOGUS, token_set![]),
         |p, range| p.err_builder("expected property", range),
     )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_yaml_parser/src/parser/property.rs` around lines 21 - 39, The
recover implementation currently uses an empty token_set![] while is_at_list_end
always returns true, which makes recover unreachable and dangerous if
is_at_list_end is later relaxed; update recover (the
ParseRecoveryTokenSet::new(..., token_set![] ) call in recover) to either
include a sentinel set of property-terminating tokens or add a clear TODO
comment next to token_set![] explaining the coupling with is_at_list_end and
that the token set must be kept in sync with any changes to
is_at_list_end/parse_element; refer to the functions parse_element,
is_at_list_end, and recover so the maintainer knows where to change the token
set or add the guard comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_yaml_parser/src/parser/property.rs`:
- Around line 21-39: The recover implementation currently uses an empty
token_set![] while is_at_list_end always returns true, which makes recover
unreachable and dangerous if is_at_list_end is later relaxed; update recover
(the ParseRecoveryTokenSet::new(..., token_set![] ) call in recover) to either
include a sentinel set of property-terminating tokens or add a clear TODO
comment next to token_set![] explaining the coupling with is_at_list_end and
that the token set must be kept in sync with any changes to
is_at_list_end/parse_element; refer to the functions parse_element,
is_at_list_end, and recover so the maintainer knows where to change the token
set or add the guard comment.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 101b3bb and 26c133a.

⛔ Files ignored due to path filters (46)
  • crates/biome_yaml_factory/src/generated/node_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_yaml_factory/src/generated/syntax_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/extra_characters_in_header.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/invalid_headers.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/mapping_followed_plain.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/mapping_key_contains_multiple_values.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/mapping_missing_colon.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/multi_line_flow_collection_broken_out_of_parent_block.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/multi_line_plain_token_broken_out_parent_flow.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/plain_followed_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/plain_separated_by_comments.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/block/sequence_follow_by_map.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/flow/empty_mapping_entry.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/flow/mapping_missing_closing_bracket.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/flow/missing_closing_double_quoted.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/flow/missing_closing_single_quoted.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/flow/plain_start_with_indicator.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/err/flow/sequence_missing_closing_bracket.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/explicit_block_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/explicit_block_mapping_with_compact_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/explicit_block_mapping_with_empty_value.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/explicit_block_mapping_with_empty_value_and_mapping_as_key.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/implicit_block_mapping_empty_value.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/nested_implicit_block_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/simple_block_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/mapping/simple_block_mapping_separated_by_trivia.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/scalar/folded_scalar.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/scalar/literal_scalar.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/sequence/map_nested_in_sequence.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/sequence/nested_block_sequence.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/sequence/sequence_nested_in_map.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/sequence/sequence_with_trailing_trivia.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/block/sequence/simple_block_sequence.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/document/separated_by_doc_end.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/double_quote.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/explicit_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/flow_with_trailing_trivia.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/nested_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/simple_mapping.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/simple_plain.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/simple_sequence.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_parser/tests/yaml_test_suite/ok/flow/single_quote.yaml.snap is excluded by !**/*.snap and included by **
  • crates/biome_yaml_syntax/src/generated/kind.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_yaml_syntax/src/generated/macros.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_yaml_syntax/src/generated/nodes.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_yaml_syntax/src/generated/nodes_mut.rs is excluded by !**/generated/**, !**/generated/** and included by **
📒 Files selected for processing (19)
  • crates/biome_yaml_formatter/src/generated.rs
  • crates/biome_yaml_formatter/src/yaml/any/block_in_block_content.rs
  • crates/biome_yaml_formatter/src/yaml/any/block_in_block_node.rs
  • crates/biome_yaml_formatter/src/yaml/any/block_node.rs
  • crates/biome_yaml_formatter/src/yaml/any/mod.rs
  • crates/biome_yaml_formatter/src/yaml/any/properties_combination.rs
  • crates/biome_yaml_formatter/src/yaml/any/property.rs
  • crates/biome_yaml_formatter/src/yaml/auxiliary/block_in_block_node.rs
  • crates/biome_yaml_formatter/src/yaml/auxiliary/mod.rs
  • crates/biome_yaml_formatter/src/yaml/auxiliary/properties_anchor_first.rs
  • crates/biome_yaml_formatter/src/yaml/auxiliary/properties_tag_first.rs
  • crates/biome_yaml_formatter/src/yaml/lists/mod.rs
  • crates/biome_yaml_formatter/src/yaml/lists/property_list.rs
  • crates/biome_yaml_parser/src/parser/block.rs
  • crates/biome_yaml_parser/src/parser/flow.rs
  • crates/biome_yaml_parser/src/parser/mod.rs
  • crates/biome_yaml_parser/src/parser/property.rs
  • xtask/codegen/src/yaml_kinds_src.rs
  • xtask/codegen/yaml.ungram
💤 Files with no reviewable changes (4)
  • crates/biome_yaml_formatter/src/yaml/auxiliary/properties_tag_first.rs
  • crates/biome_yaml_formatter/src/yaml/any/properties_combination.rs
  • crates/biome_yaml_formatter/src/yaml/any/block_in_block_node.rs
  • crates/biome_yaml_formatter/src/yaml/auxiliary/properties_anchor_first.rs

@ematipico
Copy link
Copy Markdown
Member

When you say schema change, do you mean a change in the grammar?

@l0ngvh
Copy link
Copy Markdown
Contributor Author

l0ngvh commented Feb 24, 2026

My bad. Yes, it's a grammar change. Let me update the PR description

Comment on lines +21 to +23
fn parse_element(&mut self, _p: &mut Self::Parser<'_>) -> ParsedSyntax {
Absent
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of a list that doesn't parse anything?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I wanted to keep the PR lightweight, so it only contains the minimal change that passes compilation and tests. A follow up PR will handle populating the parsing logic later. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok. It's good. I didn't understand that from the PR description

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. Let me update the description to reflect this


pub(crate) fn parse_flow_json_node(p: &mut YamlParser) -> CompletedMarker {
let m = p.start();
PropertyList.parse_list(p);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bug. A list that doesn't parse anything

@l0ngvh l0ngvh merged commit 59cdae2 into biomejs:main Feb 26, 2026
15 checks passed
@l0ngvh l0ngvh deleted the refactor-yaml-property-schema branch February 26, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter A-Parser Area: parser A-Tooling Area: internal tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants