Skip to content

[yaml_edit] Add configurable alias handling semantics via AliasBehavior - #2528

Draft
sigurdm wants to merge 29 commits into
dart-lang:robustness-fixesfrom
sigurdm:handle-aliases
Draft

sigurdm wants to merge 29 commits into
dart-lang:robustness-fixesfrom
sigurdm:handle-aliases

Conversation

@sigurdm

@sigurdm sigurdm commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on top of #2587.

This PR introduces configurable YAML alias handling semantics to package:yaml_edit, allowing developers to opt into reference redirection or lazy Copy-On-Write (COW) materialization instead of throwing exceptions on alias mutations.

Motivation

Previously, any mutation targeting an alias reference (*anchor) or anchor definition (&anchor) in YamlEditor threw an AliasException. This prevented automated YAML editing in real-world files (such as CI/CD workflows, Docker Compose, and Kubernetes manifests) where YAML anchors and aliases are widely used for shared configuration templates.

Changes

  • AliasBehavior Enum: Added an AliasBehavior enum with three options:
    • disallow (default): Any mutation touching an alias or anchor throws an AliasException (100% backwards-compatible).
    • reference: Mutating a property through an alias reference redirects the mutation to the underlying anchor definition, updating the template and automatically propagating the change to all references.
    • copyOnWrite (Lazy COW): Mutating a property through an alias reference unfolds the referenced collection inline at the reference location, decoupling it from the anchor template without modifying the original definition.
      • Lazy / Shallow Unfolding: Unfolds only the top-level container, preserving nested extra-template alias references (*nested) until those subtrees are directly modified.
      • Intra-Template Sub-Anchor Inlining: Strips intra-template sub-anchor definition tags (&sub_anchor) and expands intra-template aliases (*sub_anchor) inline so decoupled copies are completely self-contained and free from dangling references upon anchor deletion.
      • Mutating an anchor definition directly updates the template in place so inheriting references see the change.
  • Anchor Tag Preservation: Added getAnchorTag to extract clean &anchorName tags from AST span text so that updating or replacing an anchor definition leaf preserves its anchor tag.
  • Anchor Definition Detection: Added isAnchorDefinition to inspect whether a child node in a map or list is an anchor definition.
  • Identity-based AST Tracking: AST alias detection and anchor resolution use object identity maps and sets (Set.identity(), Map.identity()) to ensure AST node tracking respects package:yaml object reuse.
  • Block Map Header Preservation: Distinguishes block map headers (anchors and type tags) from keys during entry removal, preventing map-level tags/anchors from being wiped when the first entry is deleted.
  • Alias Reference Pattern: Added aliasReferencePattern using positive lookahead (?=[\s,\[\]{}]|$) to match YAML delimiter boundaries, supporting anchor names with special characters (., /, @, +).
  • Unit Tests: Comprehensive unit tests in test/alias_behavior_test.dart covering reference, copyOnWrite, disallow, intra-template sub-anchors, anchor name character sets, collection headers, and list appends.
  • Example: Added example/alias_behavior_example.dart demonstrating usage.
  • Changelog & Version: Bumped version to 2.3.0-wip in pubspec.yaml and documented changes in CHANGELOG.md.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces configurable alias and anchor mutation behaviors (AliasBehavior) to package:yaml_edit, allowing developers to choose between disallowing, referencing, or copying-on-write when modifying aliased YAML nodes. It also includes out-of-scope changes to package:pub_semver to fix parsing of pre-release and build identifiers. Feedback highlights several critical issues: getAnchorTag can return trailing whitespace leading to invalid YAML formatting in map mutations, _AliasEntryKey violates the == and hashCode contract, and potential NullThrownErrors exist when handling YamlNode keys. Additionally, the public getAnchorTag method lacks required documentation, and the out-of-scope pub_semver changes should be moved to a separate pull request.

Comment thread pkgs/yaml_edit/lib/src/editor.dart
Comment thread pkgs/yaml_edit/lib/src/map_mutations.dart
Comment thread pkgs/yaml_edit/lib/src/map_mutations.dart
Comment thread pkgs/yaml_edit/lib/src/editor.dart Outdated
Comment thread pkgs/yaml_edit/lib/src/editor.dart
Comment thread pkgs/yaml_edit/lib/src/editor.dart
Comment thread pkgs/yaml_edit/lib/src/editor.dart
Comment thread pkgs/pub_semver/lib/src/version.dart
Adds AliasBehavior enum with reference, copyOnWrite, and disallow options to YamlEditor. Implements path redirection for alias references, Asymmetric Copy-On-Write inline materialization, and preserves anchor tags during anchor updates.
…ing, unwrap YamlNode keys in editor.dart, and fix _AliasEntryKey equality
sigurdm added 13 commits August 17, 2026 13:22
- Add _IdentityPair coinductive cycle detection to deepEquals and recursion guard to deepHashCode in equality.dart.
- Add Set<Object?>.identity() recursion guard to wrapAsYamlNode in wrap.dart.
- Add Set<YamlNode>.identity() recursion guards to _hasActiveReferencesToAnchor, _collectSubAnchorTags, and _updateNodeAndAliases in editor.dart.
- Guard getContentSensitiveEnd and getListIndentation against cycles and alias offsets in utils.dart.
- Ensure AliasBehavior.disallow cleanly rejects mutations on cyclic structures with AliasException.
- Ensure AliasBehavior.reference updates cyclic anchor definitions without hanging.
- Add comprehensive unit tests in test/cyclic_anchors_test.dart.
- Differentiate intra-template anchors and aliases vs extra-template aliases during Copy-On-Write shallow unfolding.
- Strip intra-template sub-anchor definition tags and expand intra-template alias references inline to their target value representation.
- Preserve extra-template alias references intact to maintain Lazy COW.
- Update getTrueContentSensitiveEnd to resolve content-sensitive ends across child collections with alias reference leaves.
- Add comprehensive unit tests in test/alias_behavior_test.dart covering intra-template alias expansion, Lazy COW preservation, subsequent base template mutation, and clean base template removal without AliasException.
- Locate key origins in << via _findMergedOrigin, supporting single merges, chained merges, and multi-merges (<<: [*m1, *m2]) with precedence.
- Update _traverse to resolve keys through << when not directly present in map.nodes.
- Update _resolvePath to enforce AliasBehavior across merge keys:
  - AliasBehavior.disallow: throw AliasException on merged key update or removal, while allowing explicit keys and new keys.
  - AliasBehavior.reference: redirect merged key mutations and removals to the anchor definition in-place, preserving explicit local overrides.
  - AliasBehavior.copyOnWrite: insert explicit overrides into target map without mutating template, materialize intermediate sub-maps for nested paths, and restore inherited anchor values on explicit override removal.
- Update _addToBlockMap and _removeFromBlockMap to respect alias spans and preserve anchor tags.
- Propagate chained alias updates through _updateNodeAndAliases.
- Add comprehensive unit tests in test/merge_keys_test.dart.
- Add YamlChar classification constants and predicates in char_codes.dart.
- Introduce comment-aware flow delimiter scanning (findNextFlowDelimiter, findPreviousFlowDelimiter) in utils.dart.
- Use positive lookahead for YAML delimiter boundaries in aliasReferencePattern, supporting anchor names containing special characters (., /, @, +) and avoiding prefix substring collisions.
- Sort intra-template anchor keys by descending length before inline replacement.
- Use true content-sensitive ending in _appendToBlockList and preserve trailing newlines of keep-chomping scalars (|+).
- Locate colon dynamically after map key in _replaceInBlockMap to avoid deleting colons on quoted keys with whitespace.
- Support empty values without trailing spaces in _replaceInFlowMap.
- Use lineEnding in _tryYamlEncodeFolded and _tryYamlEncodeLiteral headers.
- Fall back to list.span.start.column in getListIndentation when a list contains only alias references.
- Add comprehensive robustness tests in test/robustness_test.dart.
@sigurdm
sigurdm changed the base branch from main to robustness-fixes September 9, 2026 12:07

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant