Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for YAML merge keys (<<) in YamlEditor, allowing keys to be resolved through single, multi, and chained merges under different AliasBehavior configurations (disallow, reference, and copyOnWrite). The changes include updating path resolution, traversal, and mutation logic, along with adding a comprehensive test suite. The review feedback suggests several improvements: simplifying the remove method by eliminating the redundant isRemove flag, correcting a redundant map lookup where map.nodes['<<'] is used instead of retrieving the entry via getYamlMapEntry, and adding a unit test to verify nested merged key removal under copyOnWrite behavior.
| YamlNode remove(Iterable<Object?> path) { | ||
| path = _resolvePath(path); | ||
| path = _resolvePath(path, isRemove: true); |
There was a problem hiding this comment.
Update the remove method to call _resolvePath without the obsolete isRemove parameter.
YamlNode remove(Iterable<Object?> path) {
path = _resolvePath(path);References
- Avoid mutually exclusive or redundant boolean flags in API design. Simplify the API by removing redundant flags to make invalid states unrepresentable.
| final visitedMaps = visited ?? Set<YamlMap>.identity(); | ||
| if (!visitedMaps.add(map)) return null; | ||
|
|
||
| final mergeNode = map.nodes['<<'] ?? getYamlMapEntry(map, '<<').valueNode; |
There was a problem hiding this comment.
In package:yaml, the keys of YamlMap.nodes are always YamlNode objects (such as YamlScalar wrapping '<<'), rather than raw Strings. Therefore, map.nodes['<<'] will always evaluate to null, making the left-hand side of the null-coalescing operator redundant and misleading. We can directly retrieve the value node using getYamlMapEntry.
final mergeNode = getYamlMapEntry(map, '<<').valueNode;| ''', aliasBehavior: AliasBehavior.copyOnWrite); | ||
|
|
||
| expect(() => doc.remove(['service', 'timeout']), throwsPathError); | ||
| expect(doc.parseAt(['service', 'timeout']).value, equals(30)); |
There was a problem hiding this comment.
Add a comprehensive unit test to verify that removing a nested merged key under copyOnWrite correctly materializes the parent map and does not mutate the shared template or other inheriting services.
});
test('removing nested merged key materializes parent and does not mutate template', () {
final doc = YamlEditor('''
defaults: &defaults
db:
port: 5432
host: localhost
service1:
<<: *defaults
service2:
<<: *defaults
''', aliasBehavior: AliasBehavior.copyOnWrite);
doc.remove(['service1', 'db', 'port']);
expect(doc.parseAt(['service1', 'db', 'host']).value, equals('localhost'));
expect(() => doc.parseAt(['service1', 'db', 'port']), throwsPathError);
// Ensure template and other services are untouched
expect(doc.parseAt(['defaults', 'db', 'port']).value, equals(5432));
expect(doc.parseAt(['service2', 'db', 'port']).value, equals(5432));
});8e71085 to
8cfd98f
Compare
Stacked on top of #2528.
This PR adds support for YAML 1.1 Merge Keys (
<<: *anchor) topackage:yaml_editacrossparseAt,update, andremove, honoring the configuredAliasBehavior.Changes
_findMergedOrigin): Resolves merged properties through single merges (<<: *defaults), multi-merges (<<: [*m1, *m2]) with first-map precedence, and chained merges (service -> mid -> base).AliasBehaviorIntegration:disallow: ThrowsAliasExceptionwhen attempting to update or remove a property originating from a merge key, while allowing explicit local keys.reference: Redirects merged property updates and removals to the anchor definition in-place, while updating explicit overrides locally.copyOnWrite: Inserts explicit override keys directly into the target map without mutating the template. Materializes intermediate sub-maps for nested paths (['service', 'db', 'port']). Restores inherited anchor values upon removal of explicit overrides.test/merge_keys_test.dartcovering single, multi, chained, deep (3+ levels), flow style, and malformed merge keys.